5a0d57757190049faa3f55349d201346c48ce4cf
[rocksndiamonds.git] / src / events.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // events.c
10 // ============================================================================
11
12 #include "libgame/libgame.h"
13
14 #include "events.h"
15 #include "init.h"
16 #include "screens.h"
17 #include "tools.h"
18 #include "game.h"
19 #include "editor.h"
20 #include "files.h"
21 #include "tape.h"
22 #include "anim.h"
23 #include "network.h"
24
25
26 #define DEBUG_EVENTS            0
27
28 #define DEBUG_EVENTS_BUTTON     (DEBUG_EVENTS   * 0)
29 #define DEBUG_EVENTS_MOTION     (DEBUG_EVENTS   * 0)
30 #define DEBUG_EVENTS_WHEEL      (DEBUG_EVENTS   * 1)
31 #define DEBUG_EVENTS_WINDOW     (DEBUG_EVENTS   * 0)
32 #define DEBUG_EVENTS_FINGER     (DEBUG_EVENTS   * 0)
33 #define DEBUG_EVENTS_TEXT       (DEBUG_EVENTS   * 1)
34 #define DEBUG_EVENTS_KEY        (DEBUG_EVENTS   * 1)
35
36
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
41
42
43 // forward declarations for internal use
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
46
47
48 /* event filter especially needed for SDL event filtering due to
49    delay problems with lots of mouse motion events when mouse button
50    not pressed (X11 can handle this with 'PointerMotionHintMask') */
51
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53    or disable keyboard auto-repeat, filter repeated keyboard events instead */
54
55 static int FilterEvents(const Event *event)
56 {
57   MotionEvent *motion;
58
59 #if defined(TARGET_SDL2)
60   // skip repeated key press events if keyboard auto-repeat is disabled
61   if (event->type == EVENT_KEYPRESS &&
62       event->key.repeat &&
63       !keyrepeat_status)
64     return 0;
65 #endif
66
67   if (event->type == EVENT_BUTTONPRESS ||
68       event->type == EVENT_BUTTONRELEASE)
69   {
70     ((ButtonEvent *)event)->x -= video.screen_xoffset;
71     ((ButtonEvent *)event)->y -= video.screen_yoffset;
72   }
73   else if (event->type == EVENT_MOTIONNOTIFY)
74   {
75     ((MotionEvent *)event)->x -= video.screen_xoffset;
76     ((MotionEvent *)event)->y -= video.screen_yoffset;
77   }
78
79   // non-motion events are directly passed to event handler functions
80   if (event->type != EVENT_MOTIONNOTIFY)
81     return 1;
82
83   motion = (MotionEvent *)event;
84   cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85                              motion->y >= SY && motion->y < SY + SYSIZE);
86
87   // do no reset mouse cursor before all pending events have been processed
88   if (gfx.cursor_mode == cursor_mode_last &&
89       ((game_status == GAME_MODE_TITLE &&
90         gfx.cursor_mode == CURSOR_NONE) ||
91        (game_status == GAME_MODE_PLAYING &&
92         gfx.cursor_mode == CURSOR_PLAYFIELD)))
93   {
94     SetMouseCursor(CURSOR_DEFAULT);
95
96     DelayReached(&special_cursor_delay, 0);
97
98     cursor_mode_last = CURSOR_DEFAULT;
99   }
100
101   // skip mouse motion events without pressed button outside level editor
102   if (button_status == MB_RELEASED &&
103       game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
104     return 0;
105
106   return 1;
107 }
108
109 /* to prevent delay problems, skip mouse motion events if the very next
110    event is also a mouse motion event (and therefore effectively only
111    handling the last of a row of mouse motion events in the event queue) */
112
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
114 {
115   // nothing to do if the current event is not a mouse motion event
116   if (event->type != EVENT_MOTIONNOTIFY)
117     return FALSE;
118
119   // only skip motion events with pressed button outside the game
120   if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
121     return FALSE;
122
123   if (PendingEvent())
124   {
125     Event next_event;
126
127     PeekEvent(&next_event);
128
129     // if next event is also a mouse motion event, skip the current one
130     if (next_event.type == EVENT_MOTIONNOTIFY)
131       return TRUE;
132   }
133
134   return FALSE;
135 }
136
137 static boolean WaitValidEvent(Event *event)
138 {
139   WaitEvent(event);
140
141   if (!FilterEvents(event))
142     return FALSE;
143
144   if (SkipPressedMouseMotionEvent(event))
145     return FALSE;
146
147   return TRUE;
148 }
149
150 /* this is especially needed for event modifications for the Android target:
151    if mouse coordinates should be modified in the event filter function,
152    using a properly installed SDL event filter does not work, because in
153    the event filter, mouse coordinates in the event structure are still
154    physical pixel positions, not logical (scaled) screen positions, so this
155    has to be handled at a later stage in the event processing functions
156    (when device pixel positions are already converted to screen positions) */
157
158 boolean NextValidEvent(Event *event)
159 {
160   while (PendingEvent())
161     if (WaitValidEvent(event))
162       return TRUE;
163
164   return FALSE;
165 }
166
167 static void HandleEvents(void)
168 {
169   Event event;
170   unsigned int event_frame_delay = 0;
171   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
172
173   ResetDelayCounter(&event_frame_delay);
174
175   while (NextValidEvent(&event))
176   {
177     switch (event.type)
178     {
179       case EVENT_BUTTONPRESS:
180       case EVENT_BUTTONRELEASE:
181         HandleButtonEvent((ButtonEvent *) &event);
182         break;
183
184       case EVENT_MOTIONNOTIFY:
185         HandleMotionEvent((MotionEvent *) &event);
186         break;
187
188 #if defined(TARGET_SDL2)
189       case EVENT_WHEELMOTION:
190         HandleWheelEvent((WheelEvent *) &event);
191         break;
192
193       case SDL_WINDOWEVENT:
194         HandleWindowEvent((WindowEvent *) &event);
195         break;
196
197       case EVENT_FINGERPRESS:
198       case EVENT_FINGERRELEASE:
199       case EVENT_FINGERMOTION:
200         HandleFingerEvent((FingerEvent *) &event);
201         break;
202
203       case EVENT_TEXTINPUT:
204         HandleTextEvent((TextEvent *) &event);
205         break;
206
207       case SDL_APP_WILLENTERBACKGROUND:
208       case SDL_APP_DIDENTERBACKGROUND:
209       case SDL_APP_WILLENTERFOREGROUND:
210       case SDL_APP_DIDENTERFOREGROUND:
211         HandlePauseResumeEvent((PauseResumeEvent *) &event);
212         break;
213 #endif
214
215       case EVENT_KEYPRESS:
216       case EVENT_KEYRELEASE:
217         HandleKeyEvent((KeyEvent *) &event);
218         break;
219
220       default:
221         HandleOtherEvents(&event);
222         break;
223     }
224
225     // do not handle events for longer than standard frame delay period
226     if (DelayReached(&event_frame_delay, event_frame_delay_value))
227       break;
228   }
229 }
230
231 void HandleOtherEvents(Event *event)
232 {
233   switch (event->type)
234   {
235     case EVENT_EXPOSE:
236       HandleExposeEvent((ExposeEvent *) event);
237       break;
238
239     case EVENT_UNMAPNOTIFY:
240 #if 0
241       /* This causes the game to stop not only when iconified, but also
242          when on another virtual desktop, which might be not desired. */
243       SleepWhileUnmapped();
244 #endif
245       break;
246
247     case EVENT_FOCUSIN:
248     case EVENT_FOCUSOUT:
249       HandleFocusEvent((FocusChangeEvent *) event);
250       break;
251
252     case EVENT_CLIENTMESSAGE:
253       HandleClientMessageEvent((ClientMessageEvent *) event);
254       break;
255
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258     case SDL_CONTROLLERBUTTONDOWN:
259     case SDL_CONTROLLERBUTTONUP:
260       // for any game controller button event, disable overlay buttons
261       SetOverlayEnabled(FALSE);
262
263       HandleSpecialGameControllerButtons(event);
264
265       // FALL THROUGH
266     case SDL_CONTROLLERDEVICEADDED:
267     case SDL_CONTROLLERDEVICEREMOVED:
268     case SDL_CONTROLLERAXISMOTION:
269 #endif
270     case SDL_JOYAXISMOTION:
271     case SDL_JOYBUTTONDOWN:
272     case SDL_JOYBUTTONUP:
273       HandleJoystickEvent(event);
274       break;
275
276     case SDL_SYSWMEVENT:
277       HandleWindowManagerEvent(event);
278       break;
279 #endif
280
281     default:
282       break;
283   }
284 }
285
286 static void HandleMouseCursor(void)
287 {
288   if (game_status == GAME_MODE_TITLE)
289   {
290     // when showing title screens, hide mouse pointer (if not moved)
291
292     if (gfx.cursor_mode != CURSOR_NONE &&
293         DelayReached(&special_cursor_delay, special_cursor_delay_value))
294     {
295       SetMouseCursor(CURSOR_NONE);
296     }
297   }
298   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
299                                                 tape.single_step))
300   {
301     // when playing, display a special mouse pointer inside the playfield
302
303     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304         cursor_inside_playfield &&
305         DelayReached(&special_cursor_delay, special_cursor_delay_value))
306     {
307       if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
308           tile_cursor.enabled)
309         SetMouseCursor(CURSOR_PLAYFIELD);
310     }
311   }
312   else if (gfx.cursor_mode != CURSOR_DEFAULT)
313   {
314     SetMouseCursor(CURSOR_DEFAULT);
315   }
316
317   // this is set after all pending events have been processed
318   cursor_mode_last = gfx.cursor_mode;
319 }
320
321 void EventLoop(void)
322 {
323   while (1)
324   {
325     if (PendingEvent())
326       HandleEvents();
327     else
328       HandleNoEvent();
329
330     // execute event related actions after pending events have been processed
331     HandleEventActions();
332
333     /* don't use all CPU time when idle; the main loop while playing
334        has its own synchronization and is CPU friendly, too */
335
336     if (game_status == GAME_MODE_PLAYING)
337       HandleGameActions();
338
339     // always copy backbuffer to visible screen for every video frame
340     BackToFront();
341
342     // reset video frame delay to default (may change again while playing)
343     SetVideoFrameDelay(MenuFrameDelay);
344
345     if (game_status == GAME_MODE_QUIT)
346       return;
347   }
348 }
349
350 void ClearAutoRepeatKeyEvents(void)
351 {
352 #if defined(TARGET_SDL2)
353   while (PendingEvent())
354   {
355     Event next_event;
356
357     PeekEvent(&next_event);
358
359     // if event is repeated key press event, remove it from event queue
360     if (next_event.type == EVENT_KEYPRESS &&
361         next_event.key.repeat)
362       WaitEvent(&next_event);
363     else
364       break;
365   }
366 #endif
367 }
368
369 void ClearEventQueue(void)
370 {
371   Event event;
372
373   while (NextValidEvent(&event))
374   {
375     switch (event.type)
376     {
377       case EVENT_BUTTONRELEASE:
378         button_status = MB_RELEASED;
379         break;
380
381       case EVENT_KEYRELEASE:
382         ClearPlayerAction();
383         break;
384
385 #if defined(TARGET_SDL2)
386       case SDL_CONTROLLERBUTTONUP:
387         HandleJoystickEvent(&event);
388         ClearPlayerAction();
389         break;
390 #endif
391
392       default:
393         HandleOtherEvents(&event);
394         break;
395     }
396   }
397 }
398
399 static void ClearPlayerMouseAction(void)
400 {
401   local_player->mouse_action.lx = 0;
402   local_player->mouse_action.ly = 0;
403   local_player->mouse_action.button = 0;
404 }
405
406 void ClearPlayerAction(void)
407 {
408   int i;
409
410   // simulate key release events for still pressed keys
411   key_joystick_mapping = 0;
412   for (i = 0; i < MAX_PLAYERS; i++)
413     stored_player[i].action = 0;
414
415   ClearJoystickState();
416   ClearPlayerMouseAction();
417 }
418
419 static void SetPlayerMouseAction(int mx, int my, int button)
420 {
421   int lx = getLevelFromScreenX(mx);
422   int ly = getLevelFromScreenY(my);
423   int new_button = (!local_player->mouse_action.button && button);
424
425   if (local_player->mouse_action.button_hint)
426     button = local_player->mouse_action.button_hint;
427
428   ClearPlayerMouseAction();
429
430   if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
431     return;
432
433   local_player->mouse_action.lx = lx;
434   local_player->mouse_action.ly = ly;
435   local_player->mouse_action.button = button;
436
437   if (tape.recording && tape.pausing && tape.use_mouse)
438   {
439     // un-pause a paused game only if mouse button was newly pressed down
440     if (new_button)
441       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
442   }
443
444   SetTileCursorXY(lx, ly);
445 }
446
447 void SleepWhileUnmapped(void)
448 {
449   boolean window_unmapped = TRUE;
450
451   KeyboardAutoRepeatOn();
452
453   while (window_unmapped)
454   {
455     Event event;
456
457     if (!WaitValidEvent(&event))
458       continue;
459
460     switch (event.type)
461     {
462       case EVENT_BUTTONRELEASE:
463         button_status = MB_RELEASED;
464         break;
465
466       case EVENT_KEYRELEASE:
467         key_joystick_mapping = 0;
468         break;
469
470 #if defined(TARGET_SDL2)
471       case SDL_CONTROLLERBUTTONUP:
472         HandleJoystickEvent(&event);
473         key_joystick_mapping = 0;
474         break;
475 #endif
476
477       case EVENT_MAPNOTIFY:
478         window_unmapped = FALSE;
479         break;
480
481       case EVENT_UNMAPNOTIFY:
482         /* this is only to surely prevent the 'should not happen' case
483          * of recursively looping between 'SleepWhileUnmapped()' and
484          * 'HandleOtherEvents()' which usually calls this funtion.
485          */
486         break;
487
488       default:
489         HandleOtherEvents(&event);
490         break;
491     }
492   }
493
494   if (game_status == GAME_MODE_PLAYING)
495     KeyboardAutoRepeatOffUnlessAutoplay();
496 }
497
498 void HandleExposeEvent(ExposeEvent *event)
499 {
500 }
501
502 void HandleButtonEvent(ButtonEvent *event)
503 {
504 #if DEBUG_EVENTS_BUTTON
505   Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
506         event->button,
507         event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
508         event->x, event->y);
509 #endif
510
511   // for any mouse button event, disable playfield tile cursor
512   SetTileCursorEnabled(FALSE);
513
514 #if defined(HAS_SCREEN_KEYBOARD)
515   if (video.shifted_up)
516     event->y += video.shifted_up_pos;
517 #endif
518
519   motion_status = FALSE;
520
521   if (event->type == EVENT_BUTTONPRESS)
522     button_status = event->button;
523   else
524     button_status = MB_RELEASED;
525
526   HandleButton(event->x, event->y, button_status, event->button);
527 }
528
529 void HandleMotionEvent(MotionEvent *event)
530 {
531   if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
532     return;
533
534   motion_status = TRUE;
535
536 #if DEBUG_EVENTS_MOTION
537   Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
538         button_status, event->x, event->y);
539 #endif
540
541   HandleButton(event->x, event->y, button_status, button_status);
542 }
543
544 #if defined(TARGET_SDL2)
545
546 void HandleWheelEvent(WheelEvent *event)
547 {
548   int button_nr;
549
550 #if DEBUG_EVENTS_WHEEL
551 #if 1
552   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
553         event->which, event->x, event->y);
554 #else
555   // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
556   Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
557         event->which, event->x, event->y,
558         (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
559          "SDL_MOUSEWHEEL_FLIPPED"));
560 #endif
561 #endif
562
563   button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
564                event->x > 0 ? MB_WHEEL_RIGHT :
565                event->y < 0 ? MB_WHEEL_DOWN :
566                event->y > 0 ? MB_WHEEL_UP : 0);
567
568 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
569   // accelerated mouse wheel available on Mac and Windows
570   wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
571 #else
572   // no accelerated mouse wheel available on Unix/Linux
573   wheel_steps = DEFAULT_WHEEL_STEPS;
574 #endif
575
576   motion_status = FALSE;
577
578   button_status = button_nr;
579   HandleButton(0, 0, button_status, -button_nr);
580
581   button_status = MB_RELEASED;
582   HandleButton(0, 0, button_status, -button_nr);
583 }
584
585 void HandleWindowEvent(WindowEvent *event)
586 {
587 #if DEBUG_EVENTS_WINDOW
588   int subtype = event->event;
589
590   char *event_name =
591     (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
592      subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
593      subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
594      subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
595      subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
596      subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
597      subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
598      subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
599      subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
600      subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
601      subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
602      subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
603      subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
604      subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
605      "(UNKNOWN)");
606
607   Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
608         event_name, event->data1, event->data2);
609 #endif
610
611 #if 0
612   // (not needed, as the screen gets redrawn every 20 ms anyway)
613   if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
614       event->event == SDL_WINDOWEVENT_RESIZED ||
615       event->event == SDL_WINDOWEVENT_EXPOSED)
616     SDLRedrawWindow();
617 #endif
618
619   if (event->event == SDL_WINDOWEVENT_RESIZED)
620   {
621     if (!video.fullscreen_enabled)
622     {
623       int new_window_width  = event->data1;
624       int new_window_height = event->data2;
625
626       // if window size has changed after resizing, calculate new scaling factor
627       if (new_window_width  != video.window_width ||
628           new_window_height != video.window_height)
629       {
630         int new_xpercent = 100.0 * new_window_width  / video.screen_width  + .5;
631         int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
632
633         // (extreme window scaling allowed, but cannot be saved permanently)
634         video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
635         setup.window_scaling_percent =
636           MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
637               MAX_WINDOW_SCALING_PERCENT);
638
639         video.window_width  = new_window_width;
640         video.window_height = new_window_height;
641
642         if (game_status == GAME_MODE_SETUP)
643           RedrawSetupScreenAfterFullscreenToggle();
644
645         SetWindowTitle();
646       }
647     }
648 #if defined(PLATFORM_ANDROID)
649     else
650     {
651       int new_display_width  = event->data1;
652       int new_display_height = event->data2;
653
654       // if fullscreen display size has changed, device has been rotated
655       if (new_display_width  != video.display_width ||
656           new_display_height != video.display_height)
657       {
658         int nr = GRID_ACTIVE_NR();      // previous screen orientation
659
660         video.display_width  = new_display_width;
661         video.display_height = new_display_height;
662
663         SDLSetScreenProperties();
664
665         // check if screen orientation has changed (should always be true here)
666         if (nr != GRID_ACTIVE_NR())
667         {
668           int x, y;
669
670           if (game_status == GAME_MODE_SETUP)
671             RedrawSetupScreenAfterScreenRotation(nr);
672
673           nr = GRID_ACTIVE_NR();
674
675           overlay.grid_xsize = setup.touch.grid_xsize[nr];
676           overlay.grid_ysize = setup.touch.grid_ysize[nr];
677
678           for (x = 0; x < MAX_GRID_XSIZE; x++)
679             for (y = 0; y < MAX_GRID_YSIZE; y++)
680               overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
681         }
682       }
683     }
684 #endif
685   }
686 }
687
688 #define NUM_TOUCH_FINGERS               3
689
690 static struct
691 {
692   boolean touched;
693   SDL_FingerID finger_id;
694   int counter;
695   Key key;
696 } touch_info[NUM_TOUCH_FINGERS];
697
698 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
699 {
700 #if 1
701   int x = event->x * overlay.grid_xsize;
702   int y = event->y * overlay.grid_ysize;
703   int grid_button = overlay.grid_button[x][y];
704   int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
705   Key key = (grid_button == CHAR_GRID_BUTTON_LEFT  ? setup.input[0].key.left :
706              grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
707              grid_button == CHAR_GRID_BUTTON_UP    ? setup.input[0].key.up :
708              grid_button == CHAR_GRID_BUTTON_DOWN  ? setup.input[0].key.down :
709              grid_button == CHAR_GRID_BUTTON_SNAP  ? setup.input[0].key.snap :
710              grid_button == CHAR_GRID_BUTTON_DROP  ? setup.input[0].key.drop :
711              KSYM_UNDEFINED);
712 #else
713   float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
714   float event_x = (event->x);
715   float event_y = (event->y - ypos) / (1 - ypos);
716   Key key = (event_x > 0         && event_x < 1.0 / 6.0 &&
717              event_y > 2.0 / 3.0 && event_y < 1 ?
718              setup.input[0].key.snap :
719              event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
720              event_y > 2.0 / 3.0 && event_y < 1 ?
721              setup.input[0].key.drop :
722              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
723              event_y > 0         && event_y < 1.0 / 3.0 ?
724              setup.input[0].key.up :
725              event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
726              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
727              setup.input[0].key.left :
728              event_x > 8.0 / 9.0 && event_x < 1 &&
729              event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
730              setup.input[0].key.right :
731              event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
732              event_y > 2.0 / 3.0 && event_y < 1 ?
733              setup.input[0].key.down :
734              KSYM_UNDEFINED);
735 #endif
736   int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
737                     KEY_PRESSED);
738   char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
739                            "KEY_PRESSED");
740   int i;
741
742   // for any touch input event, enable overlay buttons (if activated)
743   SetOverlayEnabled(TRUE);
744
745   Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
746         getKeyNameFromKey(key), key_status_name, event->fingerId);
747
748   if (key_status == KEY_PRESSED)
749     overlay.grid_button_action |= grid_button_action;
750   else
751     overlay.grid_button_action &= ~grid_button_action;
752
753   // check if we already know this touch event's finger id
754   for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755   {
756     if (touch_info[i].touched &&
757         touch_info[i].finger_id == event->fingerId)
758     {
759       // Error(ERR_DEBUG, "MARK 1: %d", i);
760
761       break;
762     }
763   }
764
765   if (i >= NUM_TOUCH_FINGERS)
766   {
767     if (key_status == KEY_PRESSED)
768     {
769       int oldest_pos = 0, oldest_counter = touch_info[0].counter;
770
771       // unknown finger id -- get new, empty slot, if available
772       for (i = 0; i < NUM_TOUCH_FINGERS; i++)
773       {
774         if (touch_info[i].counter < oldest_counter)
775         {
776           oldest_pos = i;
777           oldest_counter = touch_info[i].counter;
778
779           // Error(ERR_DEBUG, "MARK 2: %d", i);
780         }
781
782         if (!touch_info[i].touched)
783         {
784           // Error(ERR_DEBUG, "MARK 3: %d", i);
785
786           break;
787         }
788       }
789
790       if (i >= NUM_TOUCH_FINGERS)
791       {
792         // all slots allocated -- use oldest slot
793         i = oldest_pos;
794
795         // Error(ERR_DEBUG, "MARK 4: %d", i);
796       }
797     }
798     else
799     {
800       // release of previously unknown key (should not happen)
801
802       if (key != KSYM_UNDEFINED)
803       {
804         HandleKey(key, KEY_RELEASED);
805
806         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
807               getKeyNameFromKey(key), "KEY_RELEASED", i);
808       }
809     }
810   }
811
812   if (i < NUM_TOUCH_FINGERS)
813   {
814     if (key_status == KEY_PRESSED)
815     {
816       if (touch_info[i].key != key)
817       {
818         if (touch_info[i].key != KSYM_UNDEFINED)
819         {
820           HandleKey(touch_info[i].key, KEY_RELEASED);
821
822           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
823                 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
824         }
825
826         if (key != KSYM_UNDEFINED)
827         {
828           HandleKey(key, KEY_PRESSED);
829
830           Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
831                 getKeyNameFromKey(key), "KEY_PRESSED", i);
832         }
833       }
834
835       touch_info[i].touched = TRUE;
836       touch_info[i].finger_id = event->fingerId;
837       touch_info[i].counter = Counter();
838       touch_info[i].key = key;
839     }
840     else
841     {
842       if (touch_info[i].key != KSYM_UNDEFINED)
843       {
844         HandleKey(touch_info[i].key, KEY_RELEASED);
845
846         Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
847               getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
848       }
849
850       touch_info[i].touched = FALSE;
851       touch_info[i].finger_id = 0;
852       touch_info[i].counter = 0;
853       touch_info[i].key = 0;
854     }
855   }
856 }
857
858 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 {
860   static Key motion_key_x = KSYM_UNDEFINED;
861   static Key motion_key_y = KSYM_UNDEFINED;
862   static Key button_key = KSYM_UNDEFINED;
863   static float motion_x1, motion_y1;
864   static float button_x1, button_y1;
865   static SDL_FingerID motion_id = -1;
866   static SDL_FingerID button_id = -1;
867   int move_trigger_distance_percent = setup.touch.move_distance;
868   int drop_trigger_distance_percent = setup.touch.drop_distance;
869   float move_trigger_distance = (float)move_trigger_distance_percent / 100;
870   float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
871   float event_x = event->x;
872   float event_y = event->y;
873
874   if (event->type == EVENT_FINGERPRESS)
875   {
876     if (event_x > 1.0 / 3.0)
877     {
878       // motion area
879
880       motion_id = event->fingerId;
881
882       motion_x1 = event_x;
883       motion_y1 = event_y;
884
885       motion_key_x = KSYM_UNDEFINED;
886       motion_key_y = KSYM_UNDEFINED;
887
888       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
889     }
890     else
891     {
892       // button area
893
894       button_id = event->fingerId;
895
896       button_x1 = event_x;
897       button_y1 = event_y;
898
899       button_key = setup.input[0].key.snap;
900
901       HandleKey(button_key, KEY_PRESSED);
902
903       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
904     }
905   }
906   else if (event->type == EVENT_FINGERRELEASE)
907   {
908     if (event->fingerId == motion_id)
909     {
910       motion_id = -1;
911
912       if (motion_key_x != KSYM_UNDEFINED)
913         HandleKey(motion_key_x, KEY_RELEASED);
914       if (motion_key_y != KSYM_UNDEFINED)
915         HandleKey(motion_key_y, KEY_RELEASED);
916
917       motion_key_x = KSYM_UNDEFINED;
918       motion_key_y = KSYM_UNDEFINED;
919
920       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
921     }
922     else if (event->fingerId == button_id)
923     {
924       button_id = -1;
925
926       if (button_key != KSYM_UNDEFINED)
927         HandleKey(button_key, KEY_RELEASED);
928
929       button_key = KSYM_UNDEFINED;
930
931       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
932     }
933   }
934   else if (event->type == EVENT_FINGERMOTION)
935   {
936     if (event->fingerId == motion_id)
937     {
938       float distance_x = ABS(event_x - motion_x1);
939       float distance_y = ABS(event_y - motion_y1);
940       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
941                               event_x > motion_x1 ? setup.input[0].key.right :
942                               KSYM_UNDEFINED);
943       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
944                               event_y > motion_y1 ? setup.input[0].key.down :
945                               KSYM_UNDEFINED);
946
947       if (distance_x < move_trigger_distance / 2 ||
948           distance_x < distance_y)
949         new_motion_key_x = KSYM_UNDEFINED;
950
951       if (distance_y < move_trigger_distance / 2 ||
952           distance_y < distance_x)
953         new_motion_key_y = KSYM_UNDEFINED;
954
955       if (distance_x > move_trigger_distance ||
956           distance_y > move_trigger_distance)
957       {
958         if (new_motion_key_x != motion_key_x)
959         {
960           if (motion_key_x != KSYM_UNDEFINED)
961             HandleKey(motion_key_x, KEY_RELEASED);
962           if (new_motion_key_x != KSYM_UNDEFINED)
963             HandleKey(new_motion_key_x, KEY_PRESSED);
964         }
965
966         if (new_motion_key_y != motion_key_y)
967         {
968           if (motion_key_y != KSYM_UNDEFINED)
969             HandleKey(motion_key_y, KEY_RELEASED);
970           if (new_motion_key_y != KSYM_UNDEFINED)
971             HandleKey(new_motion_key_y, KEY_PRESSED);
972         }
973
974         motion_x1 = event_x;
975         motion_y1 = event_y;
976
977         motion_key_x = new_motion_key_x;
978         motion_key_y = new_motion_key_y;
979
980         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
981       }
982     }
983     else if (event->fingerId == button_id)
984     {
985       float distance_x = ABS(event_x - button_x1);
986       float distance_y = ABS(event_y - button_y1);
987
988       if (distance_x < drop_trigger_distance / 2 &&
989           distance_y > drop_trigger_distance)
990       {
991         if (button_key == setup.input[0].key.snap)
992           HandleKey(button_key, KEY_RELEASED);
993
994         button_x1 = event_x;
995         button_y1 = event_y;
996
997         button_key = setup.input[0].key.drop;
998
999         HandleKey(button_key, KEY_PRESSED);
1000
1001         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1002       }
1003     }
1004   }
1005 }
1006
1007 void HandleFingerEvent(FingerEvent *event)
1008 {
1009 #if DEBUG_EVENTS_FINGER
1010   Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1011         event->type == EVENT_FINGERPRESS ? "pressed" :
1012         event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1013         event->touchId,
1014         event->fingerId,
1015         event->x, event->y,
1016         event->dx, event->dy,
1017         event->pressure);
1018 #endif
1019
1020   if (game_status != GAME_MODE_PLAYING)
1021     return;
1022
1023   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1024   {
1025     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1026       local_player->mouse_action.button_hint =
1027         (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1028          event->x < 0.5                     ? MB_LEFTBUTTON  :
1029          event->x > 0.5                     ? MB_RIGHTBUTTON :
1030          MB_NOT_PRESSED);
1031
1032     return;
1033   }
1034
1035   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1036     HandleFingerEvent_VirtualButtons(event);
1037   else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1038     HandleFingerEvent_WipeGestures(event);
1039 }
1040
1041 #endif
1042
1043 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 {
1045   static int old_mx = 0, old_my = 0;
1046   static int last_button = MB_LEFTBUTTON;
1047   static boolean touched = FALSE;
1048   static boolean tapped = FALSE;
1049
1050   // screen tile was tapped (but finger not touching the screen anymore)
1051   // (this point will also be reached without receiving a touch event)
1052   if (tapped && !touched)
1053   {
1054     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1055
1056     tapped = FALSE;
1057   }
1058
1059   // stop here if this function was not triggered by a touch event
1060   if (button == -1)
1061     return;
1062
1063   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064   {
1065     // finger started touching the screen
1066
1067     touched = TRUE;
1068     tapped = TRUE;
1069
1070     if (!motion_status)
1071     {
1072       old_mx = mx;
1073       old_my = my;
1074
1075       ClearPlayerMouseAction();
1076
1077       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1078     }
1079   }
1080   else if (button == MB_RELEASED && touched)
1081   {
1082     // finger stopped touching the screen
1083
1084     touched = FALSE;
1085
1086     if (tapped)
1087       SetPlayerMouseAction(old_mx, old_my, last_button);
1088     else
1089       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090
1091     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1092   }
1093
1094   if (touched)
1095   {
1096     // finger moved while touching the screen
1097
1098     int old_x = getLevelFromScreenX(old_mx);
1099     int old_y = getLevelFromScreenY(old_my);
1100     int new_x = getLevelFromScreenX(mx);
1101     int new_y = getLevelFromScreenY(my);
1102
1103     if (new_x != old_x || new_y != old_y)
1104       tapped = FALSE;
1105
1106     if (new_x != old_x)
1107     {
1108       // finger moved left or right from (horizontal) starting position
1109
1110       int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111
1112       SetPlayerMouseAction(old_mx, old_my, button_nr);
1113
1114       last_button = button_nr;
1115
1116       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1117     }
1118     else
1119     {
1120       // finger stays at or returned to (horizontal) starting position
1121
1122       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123
1124       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1125     }
1126   }
1127 }
1128
1129 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 {
1131   static int old_mx = 0, old_my = 0;
1132   static int last_button = MB_LEFTBUTTON;
1133   static boolean touched = FALSE;
1134   static boolean tapped = FALSE;
1135
1136   // screen tile was tapped (but finger not touching the screen anymore)
1137   // (this point will also be reached without receiving a touch event)
1138   if (tapped && !touched)
1139   {
1140     SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1141
1142     tapped = FALSE;
1143   }
1144
1145   // stop here if this function was not triggered by a touch event
1146   if (button == -1)
1147     return;
1148
1149   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150   {
1151     // finger started touching the screen
1152
1153     touched = TRUE;
1154     tapped = TRUE;
1155
1156     if (!motion_status)
1157     {
1158       old_mx = mx;
1159       old_my = my;
1160
1161       ClearPlayerMouseAction();
1162
1163       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1164     }
1165   }
1166   else if (button == MB_RELEASED && touched)
1167   {
1168     // finger stopped touching the screen
1169
1170     touched = FALSE;
1171
1172     if (tapped)
1173       SetPlayerMouseAction(old_mx, old_my, last_button);
1174     else
1175       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176
1177     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1178   }
1179
1180   if (touched)
1181   {
1182     // finger moved while touching the screen
1183
1184     int old_x = getLevelFromScreenX(old_mx);
1185     int old_y = getLevelFromScreenY(old_my);
1186     int new_x = getLevelFromScreenX(mx);
1187     int new_y = getLevelFromScreenY(my);
1188
1189     if (new_x != old_x || new_y != old_y)
1190     {
1191       // finger moved away from starting position
1192
1193       int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194
1195       // quickly alternate between clicking and releasing for maximum speed
1196       if (FrameCounter % 2 == 0)
1197         button_nr = MB_RELEASED;
1198
1199       SetPlayerMouseAction(old_mx, old_my, button_nr);
1200
1201       if (button_nr)
1202         last_button = button_nr;
1203
1204       tapped = FALSE;
1205
1206       Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1207     }
1208     else
1209     {
1210       // finger stays at or returned to starting position
1211
1212       SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213
1214       Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1215     }
1216   }
1217 }
1218
1219 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 {
1221   static int old_mx = 0, old_my = 0;
1222   static Key motion_key_x = KSYM_UNDEFINED;
1223   static Key motion_key_y = KSYM_UNDEFINED;
1224   static boolean touched = FALSE;
1225   static boolean started_on_player = FALSE;
1226   static boolean player_is_dropping = FALSE;
1227   static int player_drop_count = 0;
1228   static int last_player_x = -1;
1229   static int last_player_y = -1;
1230
1231   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1232   {
1233     touched = TRUE;
1234
1235     old_mx = mx;
1236     old_my = my;
1237
1238     if (!motion_status)
1239     {
1240       started_on_player = FALSE;
1241       player_is_dropping = FALSE;
1242       player_drop_count = 0;
1243       last_player_x = -1;
1244       last_player_y = -1;
1245
1246       motion_key_x = KSYM_UNDEFINED;
1247       motion_key_y = KSYM_UNDEFINED;
1248
1249       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1250     }
1251   }
1252   else if (button == MB_RELEASED && touched)
1253   {
1254     touched = FALSE;
1255
1256     old_mx = 0;
1257     old_my = 0;
1258
1259     if (motion_key_x != KSYM_UNDEFINED)
1260       HandleKey(motion_key_x, KEY_RELEASED);
1261     if (motion_key_y != KSYM_UNDEFINED)
1262       HandleKey(motion_key_y, KEY_RELEASED);
1263
1264     if (started_on_player)
1265     {
1266       if (player_is_dropping)
1267       {
1268         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1269
1270         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1271       }
1272       else
1273       {
1274         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1275
1276         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1277       }
1278     }
1279
1280     motion_key_x = KSYM_UNDEFINED;
1281     motion_key_y = KSYM_UNDEFINED;
1282
1283     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1284   }
1285
1286   if (touched)
1287   {
1288     int src_x = local_player->jx;
1289     int src_y = local_player->jy;
1290     int dst_x = getLevelFromScreenX(old_mx);
1291     int dst_y = getLevelFromScreenY(old_my);
1292     int dx = dst_x - src_x;
1293     int dy = dst_y - src_y;
1294     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1295                             dx > 0 ? setup.input[0].key.right :
1296                             KSYM_UNDEFINED);
1297     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1298                             dy > 0 ? setup.input[0].key.down :
1299                             KSYM_UNDEFINED);
1300
1301     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1302         (last_player_x != local_player->jx ||
1303          last_player_y != local_player->jy))
1304     {
1305       // in case of asymmetric diagonal movement, use "preferred" direction
1306
1307       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308
1309       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1310         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1311       else
1312         local_player->last_move_dir = last_move_dir;
1313
1314       // (required to prevent accidentally forcing direction for next movement)
1315       last_player_x = local_player->jx;
1316       last_player_y = local_player->jy;
1317     }
1318
1319     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320     {
1321       started_on_player = TRUE;
1322       player_drop_count = getPlayerInventorySize(0);
1323       player_is_dropping = (player_drop_count > 0);
1324
1325       if (player_is_dropping)
1326       {
1327         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1328
1329         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1330       }
1331       else
1332       {
1333         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1334
1335         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1336       }
1337     }
1338     else if (dx != 0 || dy != 0)
1339     {
1340       if (player_is_dropping &&
1341           player_drop_count == getPlayerInventorySize(0))
1342       {
1343         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1344
1345         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1346         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347
1348         player_is_dropping = FALSE;
1349       }
1350     }
1351
1352     if (new_motion_key_x != motion_key_x)
1353     {
1354       Error(ERR_DEBUG, "---------- %s %s ----------",
1355             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1356             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357
1358       if (motion_key_x != KSYM_UNDEFINED)
1359         HandleKey(motion_key_x, KEY_RELEASED);
1360       if (new_motion_key_x != KSYM_UNDEFINED)
1361         HandleKey(new_motion_key_x, KEY_PRESSED);
1362     }
1363
1364     if (new_motion_key_y != motion_key_y)
1365     {
1366       Error(ERR_DEBUG, "---------- %s %s ----------",
1367             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1368             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369
1370       if (motion_key_y != KSYM_UNDEFINED)
1371         HandleKey(motion_key_y, KEY_RELEASED);
1372       if (new_motion_key_y != KSYM_UNDEFINED)
1373         HandleKey(new_motion_key_y, KEY_PRESSED);
1374     }
1375
1376     motion_key_x = new_motion_key_x;
1377     motion_key_y = new_motion_key_y;
1378   }
1379 }
1380
1381 static void HandleButtonOrFinger(int mx, int my, int button)
1382 {
1383   if (game_status != GAME_MODE_PLAYING)
1384     return;
1385
1386   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1387   {
1388     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1389       HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1390     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1391       HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1392     else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1393       SetPlayerMouseAction(mx, my, button);     // special case
1394   }
1395   else
1396   {
1397     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1398       HandleButtonOrFinger_FollowFinger(mx, my, button);
1399   }
1400 }
1401
1402 #if defined(TARGET_SDL2)
1403
1404 static boolean checkTextInputKeyModState(void)
1405 {
1406   // when playing, only handle raw key events and ignore text input
1407   if (game_status == GAME_MODE_PLAYING)
1408     return FALSE;
1409
1410   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1411 }
1412
1413 void HandleTextEvent(TextEvent *event)
1414 {
1415   char *text = event->text;
1416   Key key = getKeyFromKeyName(text);
1417
1418 #if DEBUG_EVENTS_TEXT
1419   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1420         text,
1421         strlen(text),
1422         text[0], (int)(text[0]),
1423         key,
1424         getKeyNameFromKey(key),
1425         GetKeyModState());
1426 #endif
1427
1428 #if !defined(HAS_SCREEN_KEYBOARD)
1429   // non-mobile devices: only handle key input with modifier keys pressed here
1430   // (every other key input is handled directly as physical key input event)
1431   if (!checkTextInputKeyModState())
1432     return;
1433 #endif
1434
1435   // process text input as "classic" (with uppercase etc.) key input event
1436   HandleKey(key, KEY_PRESSED);
1437   HandleKey(key, KEY_RELEASED);
1438 }
1439
1440 void HandlePauseResumeEvent(PauseResumeEvent *event)
1441 {
1442   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1443   {
1444     Mix_PauseMusic();
1445   }
1446   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1447   {
1448     Mix_ResumeMusic();
1449   }
1450 }
1451
1452 #endif
1453
1454 void HandleKeyEvent(KeyEvent *event)
1455 {
1456   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1457   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1458   Key key = GetEventKey(event, with_modifiers);
1459   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1460
1461 #if DEBUG_EVENTS_KEY
1462   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1463         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1464         event->keysym.scancode,
1465         event->keysym.sym,
1466         keymod,
1467         GetKeyModState(),
1468         key,
1469         getKeyNameFromKey(key));
1470 #endif
1471
1472 #if defined(PLATFORM_ANDROID)
1473   if (key == KSYM_Back)
1474   {
1475     // always map the "back" button to the "escape" key on Android devices
1476     key = KSYM_Escape;
1477   }
1478   else
1479   {
1480     // for any key event other than "back" button, disable overlay buttons
1481     SetOverlayEnabled(FALSE);
1482   }
1483 #endif
1484
1485   HandleKeyModState(keymod, key_status);
1486
1487 #if defined(TARGET_SDL2)
1488   // only handle raw key input without text modifier keys pressed
1489   if (!checkTextInputKeyModState())
1490     HandleKey(key, key_status);
1491 #else
1492   HandleKey(key, key_status);
1493 #endif
1494 }
1495
1496 void HandleFocusEvent(FocusChangeEvent *event)
1497 {
1498   static int old_joystick_status = -1;
1499
1500   if (event->type == EVENT_FOCUSOUT)
1501   {
1502     KeyboardAutoRepeatOn();
1503     old_joystick_status = joystick.status;
1504     joystick.status = JOYSTICK_NOT_AVAILABLE;
1505
1506     ClearPlayerAction();
1507   }
1508   else if (event->type == EVENT_FOCUSIN)
1509   {
1510     /* When there are two Rocks'n'Diamonds windows which overlap and
1511        the player moves the pointer from one game window to the other,
1512        a 'FocusOut' event is generated for the window the pointer is
1513        leaving and a 'FocusIn' event is generated for the window the
1514        pointer is entering. In some cases, it can happen that the
1515        'FocusIn' event is handled by the one game process before the
1516        'FocusOut' event by the other game process. In this case the
1517        X11 environment would end up with activated keyboard auto repeat,
1518        because unfortunately this is a global setting and not (which
1519        would be far better) set for each X11 window individually.
1520        The effect would be keyboard auto repeat while playing the game
1521        (game_status == GAME_MODE_PLAYING), which is not desired.
1522        To avoid this special case, we just wait 1/10 second before
1523        processing the 'FocusIn' event.
1524     */
1525
1526     if (game_status == GAME_MODE_PLAYING)
1527     {
1528       Delay(100);
1529       KeyboardAutoRepeatOffUnlessAutoplay();
1530     }
1531
1532     if (old_joystick_status != -1)
1533       joystick.status = old_joystick_status;
1534   }
1535 }
1536
1537 void HandleClientMessageEvent(ClientMessageEvent *event)
1538 {
1539   if (CheckCloseWindowEvent(event))
1540     CloseAllAndExit(0);
1541 }
1542
1543 void HandleWindowManagerEvent(Event *event)
1544 {
1545 #if defined(TARGET_SDL)
1546   SDLHandleWindowManagerEvent(event);
1547 #endif
1548 }
1549
1550 void HandleButton(int mx, int my, int button, int button_nr)
1551 {
1552   static int old_mx = 0, old_my = 0;
1553   boolean button_hold = FALSE;
1554   boolean handle_gadgets = TRUE;
1555
1556   if (button_nr < 0)
1557   {
1558     mx = old_mx;
1559     my = old_my;
1560     button_nr = -button_nr;
1561     button_hold = TRUE;
1562   }
1563   else
1564   {
1565     old_mx = mx;
1566     old_my = my;
1567   }
1568
1569 #if defined(PLATFORM_ANDROID)
1570   // when playing, only handle gadgets when using "follow finger" controls
1571   // or when using touch controls in combination with the MM game engine
1572   handle_gadgets =
1573     (game_status != GAME_MODE_PLAYING ||
1574      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1575      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1576 #endif
1577
1578   if (HandleGlobalAnimClicks(mx, my, button))
1579   {
1580     // do not handle this button event anymore
1581     return;             // force mouse event not to be handled at all
1582   }
1583
1584   if (handle_gadgets && HandleGadgets(mx, my, button))
1585   {
1586     // do not handle this button event anymore
1587     mx = my = -32;      // force mouse event to be outside screen tiles
1588   }
1589
1590   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1591     return;
1592
1593   // do not use scroll wheel button events for anything other than gadgets
1594   if (IS_WHEEL_BUTTON(button_nr))
1595     return;
1596
1597   switch (game_status)
1598   {
1599     case GAME_MODE_TITLE:
1600       HandleTitleScreen(mx, my, 0, 0, button);
1601       break;
1602
1603     case GAME_MODE_MAIN:
1604       HandleMainMenu(mx, my, 0, 0, button);
1605       break;
1606
1607     case GAME_MODE_PSEUDO_TYPENAME:
1608       HandleTypeName(0, KSYM_Return);
1609       break;
1610
1611     case GAME_MODE_LEVELS:
1612       HandleChooseLevelSet(mx, my, 0, 0, button);
1613       break;
1614
1615     case GAME_MODE_LEVELNR:
1616       HandleChooseLevelNr(mx, my, 0, 0, button);
1617       break;
1618
1619     case GAME_MODE_SCORES:
1620       HandleHallOfFame(0, 0, 0, 0, button);
1621       break;
1622
1623     case GAME_MODE_EDITOR:
1624       HandleLevelEditorIdle();
1625       break;
1626
1627     case GAME_MODE_INFO:
1628       HandleInfoScreen(mx, my, 0, 0, button);
1629       break;
1630
1631     case GAME_MODE_SETUP:
1632       HandleSetupScreen(mx, my, 0, 0, button);
1633       break;
1634
1635     case GAME_MODE_PLAYING:
1636       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1637         HandleButtonOrFinger(mx, my, button);
1638       else
1639         SetPlayerMouseAction(mx, my, button);
1640
1641 #ifdef DEBUG
1642       if (button == MB_PRESSED && !motion_status && !button_hold &&
1643           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1644         DumpTileFromScreen(mx, my);
1645 #endif
1646
1647       break;
1648
1649     default:
1650       break;
1651   }
1652 }
1653
1654 static boolean is_string_suffix(char *string, char *suffix)
1655 {
1656   int string_len = strlen(string);
1657   int suffix_len = strlen(suffix);
1658
1659   if (suffix_len > string_len)
1660     return FALSE;
1661
1662   return (strEqual(&string[string_len - suffix_len], suffix));
1663 }
1664
1665 #define MAX_CHEAT_INPUT_LEN     32
1666
1667 static void HandleKeysSpecial(Key key)
1668 {
1669   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1670   char letter = getCharFromKey(key);
1671   int cheat_input_len = strlen(cheat_input);
1672   int i;
1673
1674   if (letter == 0)
1675     return;
1676
1677   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1678   {
1679     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1680       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1681
1682     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1683   }
1684
1685   cheat_input[cheat_input_len++] = letter;
1686   cheat_input[cheat_input_len] = '\0';
1687
1688 #if DEBUG_EVENTS_KEY
1689   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1690 #endif
1691
1692   if (game_status == GAME_MODE_MAIN)
1693   {
1694     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1695         is_string_suffix(cheat_input, ":ist"))
1696     {
1697       InsertSolutionTape();
1698     }
1699     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1700              is_string_suffix(cheat_input, ":pst"))
1701     {
1702       PlaySolutionTape();
1703     }
1704     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1705              is_string_suffix(cheat_input, ":rg"))
1706     {
1707       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1708       DrawMainMenu();
1709     }
1710     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1711              is_string_suffix(cheat_input, ":rs"))
1712     {
1713       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1714       DrawMainMenu();
1715     }
1716     else if (is_string_suffix(cheat_input, ":reload-music") ||
1717              is_string_suffix(cheat_input, ":rm"))
1718     {
1719       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1720       DrawMainMenu();
1721     }
1722     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1723              is_string_suffix(cheat_input, ":ra"))
1724     {
1725       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1726                           1 << ARTWORK_TYPE_SOUNDS |
1727                           1 << ARTWORK_TYPE_MUSIC);
1728       DrawMainMenu();
1729     }
1730     else if (is_string_suffix(cheat_input, ":dump-level") ||
1731              is_string_suffix(cheat_input, ":dl"))
1732     {
1733       DumpLevel(&level);
1734     }
1735     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1736              is_string_suffix(cheat_input, ":dt"))
1737     {
1738       DumpTape(&tape);
1739     }
1740     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1741              is_string_suffix(cheat_input, ":ft"))
1742     {
1743       /* fix single-player tapes that contain player input for more than one
1744          player (due to a bug in 3.3.1.2 and earlier versions), which results
1745          in playing levels with more than one player in multi-player mode,
1746          even though the tape was originally recorded in single-player mode */
1747
1748       // remove player input actions for all players but the first one
1749       for (i = 1; i < MAX_PLAYERS; i++)
1750         tape.player_participates[i] = FALSE;
1751
1752       tape.changed = TRUE;
1753     }
1754     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1755              is_string_suffix(cheat_input, ":snl"))
1756     {
1757       SaveNativeLevel(&level);
1758     }
1759     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1760              is_string_suffix(cheat_input, ":fps"))
1761     {
1762       global.show_frames_per_second = !global.show_frames_per_second;
1763     }
1764   }
1765   else if (game_status == GAME_MODE_PLAYING)
1766   {
1767 #ifdef DEBUG
1768     if (is_string_suffix(cheat_input, ".q"))
1769       DEBUG_SetMaximumDynamite();
1770 #endif
1771   }
1772   else if (game_status == GAME_MODE_EDITOR)
1773   {
1774     if (is_string_suffix(cheat_input, ":dump-brush") ||
1775         is_string_suffix(cheat_input, ":DB"))
1776     {
1777       DumpBrush();
1778     }
1779     else if (is_string_suffix(cheat_input, ":DDB"))
1780     {
1781       DumpBrush_Small();
1782     }
1783   }
1784
1785   // special key shortcuts for all game modes
1786   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1787       is_string_suffix(cheat_input, ":dea") ||
1788       is_string_suffix(cheat_input, ":DEA"))
1789   {
1790     DumpGadgetIdentifiers();
1791     DumpScreenIdentifiers();
1792   }
1793 }
1794
1795 void HandleKeysDebug(Key key)
1796 {
1797 #ifdef DEBUG
1798   int i;
1799
1800   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1801   {
1802     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1803
1804     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1805     {
1806       if (key == setup.debug.frame_delay_key[i] &&
1807           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1808       {
1809         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1810                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1811
1812         if (!setup.debug.frame_delay_game_only)
1813           MenuFrameDelay = GameFrameDelay;
1814
1815         SetVideoFrameDelay(GameFrameDelay);
1816
1817         if (GameFrameDelay > ONE_SECOND_DELAY)
1818           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1819         else if (GameFrameDelay != 0)
1820           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1821                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1822                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1823         else
1824           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1825
1826         break;
1827       }
1828     }
1829   }
1830
1831   if (game_status == GAME_MODE_PLAYING)
1832   {
1833     if (key == KSYM_d)
1834     {
1835       options.debug = !options.debug;
1836
1837       Error(ERR_DEBUG, "debug mode %s",
1838             (options.debug ? "enabled" : "disabled"));
1839     }
1840     else if (key == KSYM_v)
1841     {
1842       Error(ERR_DEBUG, "currently using game engine version %d",
1843             game.engine_version);
1844     }
1845   }
1846 #endif
1847 }
1848
1849 void HandleKey(Key key, int key_status)
1850 {
1851   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1852   static boolean ignore_repeated_key = FALSE;
1853   static struct SetupKeyboardInfo ski;
1854   static struct SetupShortcutInfo ssi;
1855   static struct
1856   {
1857     Key *key_custom;
1858     Key *key_snap;
1859     Key key_default;
1860     byte action;
1861   } key_info[] =
1862   {
1863     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1864     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1865     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1866     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1867     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1868     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1869   };
1870   int joy = 0;
1871   int i;
1872
1873 #if defined(TARGET_SDL2)
1874   // map special keys (media keys / remote control buttons) to default keys
1875   if (key == KSYM_PlayPause)
1876     key = KSYM_space;
1877   else if (key == KSYM_Select)
1878     key = KSYM_Return;
1879 #endif
1880
1881   HandleSpecialGameControllerKeys(key, key_status);
1882
1883   if (game_status == GAME_MODE_PLAYING)
1884   {
1885     // only needed for single-step tape recording mode
1886     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1887     int pnr;
1888
1889     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1890     {
1891       byte key_action = 0;
1892
1893       if (setup.input[pnr].use_joystick)
1894         continue;
1895
1896       ski = setup.input[pnr].key;
1897
1898       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1899         if (key == *key_info[i].key_custom)
1900           key_action |= key_info[i].action;
1901
1902       // use combined snap+direction keys for the first player only
1903       if (pnr == 0)
1904       {
1905         ssi = setup.shortcut;
1906
1907         for (i = 0; i < NUM_DIRECTIONS; i++)
1908           if (key == *key_info[i].key_snap)
1909             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1910       }
1911
1912       if (key_status == KEY_PRESSED)
1913         stored_player[pnr].action |= key_action;
1914       else
1915         stored_player[pnr].action &= ~key_action;
1916
1917       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1918       {
1919         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1920         {
1921           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1922
1923           // if snap key already pressed, keep pause mode when releasing
1924           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1925             has_snapped[pnr] = TRUE;
1926         }
1927         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1928         {
1929           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1930
1931           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1932               getRedDiskReleaseFlag_SP() == 0)
1933           {
1934             // add a single inactive frame before dropping starts
1935             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1936             stored_player[pnr].force_dropping = TRUE;
1937           }
1938         }
1939         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1940         {
1941           // if snap key was pressed without direction, leave pause mode
1942           if (!has_snapped[pnr])
1943             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1944
1945           has_snapped[pnr] = FALSE;
1946         }
1947       }
1948       else if (tape.recording && tape.pausing && !tape.use_mouse)
1949       {
1950         // prevent key release events from un-pausing a paused game
1951         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1952           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1953       }
1954
1955       // for MM style levels, handle in-game keyboard input in HandleJoystick()
1956       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1957         joy |= key_action;
1958     }
1959   }
1960   else
1961   {
1962     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1963       if (key == key_info[i].key_default)
1964         joy |= key_info[i].action;
1965   }
1966
1967   if (joy)
1968   {
1969     if (key_status == KEY_PRESSED)
1970       key_joystick_mapping |= joy;
1971     else
1972       key_joystick_mapping &= ~joy;
1973
1974     HandleJoystick();
1975   }
1976
1977   if (game_status != GAME_MODE_PLAYING)
1978     key_joystick_mapping = 0;
1979
1980   if (key_status == KEY_RELEASED)
1981   {
1982     // reset flag to ignore repeated "key pressed" events after key release
1983     ignore_repeated_key = FALSE;
1984
1985     return;
1986   }
1987
1988   if ((key == KSYM_F11 ||
1989        ((key == KSYM_Return ||
1990          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1991       video.fullscreen_available &&
1992       !ignore_repeated_key)
1993   {
1994     setup.fullscreen = !setup.fullscreen;
1995
1996     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1997
1998     if (game_status == GAME_MODE_SETUP)
1999       RedrawSetupScreenAfterFullscreenToggle();
2000
2001     // set flag to ignore repeated "key pressed" events
2002     ignore_repeated_key = TRUE;
2003
2004     return;
2005   }
2006
2007   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2008        key == KSYM_minus || key == KSYM_KP_Subtract ||
2009        key == KSYM_plus  || key == KSYM_KP_Add ||
2010        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2011       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2012       video.window_scaling_available &&
2013       !video.fullscreen_enabled)
2014   {
2015     if (key == KSYM_0 || key == KSYM_KP_0)
2016       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2017     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2018       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2019     else
2020       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2021
2022     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2023       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2024     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2025       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2026
2027     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2028
2029     if (game_status == GAME_MODE_SETUP)
2030       RedrawSetupScreenAfterFullscreenToggle();
2031
2032     return;
2033   }
2034
2035   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2036                                       key == KSYM_Return ||
2037                                       key == KSYM_Escape)))
2038   {
2039     // do not handle this key event anymore
2040     if (key != KSYM_Escape)     // always allow ESC key to be handled
2041       return;
2042   }
2043
2044   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2045       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2046   {
2047     GameEnd();
2048
2049     return;
2050   }
2051
2052   if (game_status == GAME_MODE_MAIN &&
2053       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2054   {
2055     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2056
2057     return;
2058   }
2059
2060   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2061   {
2062     if (key == setup.shortcut.save_game)
2063       TapeQuickSave();
2064     else if (key == setup.shortcut.load_game)
2065       TapeQuickLoad();
2066     else if (key == setup.shortcut.toggle_pause)
2067       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2068
2069     HandleTapeButtonKeys(key);
2070     HandleSoundButtonKeys(key);
2071   }
2072
2073   if (game_status == GAME_MODE_PLAYING && !network_playing)
2074   {
2075     int centered_player_nr_next = -999;
2076
2077     if (key == setup.shortcut.focus_player_all)
2078       centered_player_nr_next = -1;
2079     else
2080       for (i = 0; i < MAX_PLAYERS; i++)
2081         if (key == setup.shortcut.focus_player[i])
2082           centered_player_nr_next = i;
2083
2084     if (centered_player_nr_next != -999)
2085     {
2086       game.centered_player_nr_next = centered_player_nr_next;
2087       game.set_centered_player = TRUE;
2088
2089       if (tape.recording)
2090       {
2091         tape.centered_player_nr_next = game.centered_player_nr_next;
2092         tape.set_centered_player = TRUE;
2093       }
2094     }
2095   }
2096
2097   HandleKeysSpecial(key);
2098
2099   if (HandleGadgetsKeyInput(key))
2100   {
2101     if (key != KSYM_Escape)     // always allow ESC key to be handled
2102       key = KSYM_UNDEFINED;
2103   }
2104
2105   switch (game_status)
2106   {
2107     case GAME_MODE_PSEUDO_TYPENAME:
2108       HandleTypeName(0, key);
2109       break;
2110
2111     case GAME_MODE_TITLE:
2112     case GAME_MODE_MAIN:
2113     case GAME_MODE_LEVELS:
2114     case GAME_MODE_LEVELNR:
2115     case GAME_MODE_SETUP:
2116     case GAME_MODE_INFO:
2117     case GAME_MODE_SCORES:
2118
2119       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2120         break;
2121
2122       switch (key)
2123       {
2124         case KSYM_space:
2125         case KSYM_Return:
2126           if (game_status == GAME_MODE_TITLE)
2127             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2128           else if (game_status == GAME_MODE_MAIN)
2129             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2130           else if (game_status == GAME_MODE_LEVELS)
2131             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2132           else if (game_status == GAME_MODE_LEVELNR)
2133             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2134           else if (game_status == GAME_MODE_SETUP)
2135             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2136           else if (game_status == GAME_MODE_INFO)
2137             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2138           else if (game_status == GAME_MODE_SCORES)
2139             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2140           break;
2141
2142         case KSYM_Escape:
2143           if (game_status != GAME_MODE_MAIN)
2144             FadeSkipNextFadeIn();
2145
2146           if (game_status == GAME_MODE_TITLE)
2147             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2148           else if (game_status == GAME_MODE_LEVELS)
2149             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2150           else if (game_status == GAME_MODE_LEVELNR)
2151             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2152           else if (game_status == GAME_MODE_SETUP)
2153             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2154           else if (game_status == GAME_MODE_INFO)
2155             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2156           else if (game_status == GAME_MODE_SCORES)
2157             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2158           break;
2159
2160         case KSYM_Page_Up:
2161           if (game_status == GAME_MODE_LEVELS)
2162             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2163           else if (game_status == GAME_MODE_LEVELNR)
2164             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2165           else if (game_status == GAME_MODE_SETUP)
2166             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2167           else if (game_status == GAME_MODE_INFO)
2168             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2169           else if (game_status == GAME_MODE_SCORES)
2170             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2171           break;
2172
2173         case KSYM_Page_Down:
2174           if (game_status == GAME_MODE_LEVELS)
2175             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2176           else if (game_status == GAME_MODE_LEVELNR)
2177             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2178           else if (game_status == GAME_MODE_SETUP)
2179             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2180           else if (game_status == GAME_MODE_INFO)
2181             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2182           else if (game_status == GAME_MODE_SCORES)
2183             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2184           break;
2185
2186         default:
2187           break;
2188       }
2189       break;
2190
2191     case GAME_MODE_EDITOR:
2192       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2193         HandleLevelEditorKeyInput(key);
2194       break;
2195
2196     case GAME_MODE_PLAYING:
2197     {
2198       switch (key)
2199       {
2200         case KSYM_Escape:
2201           RequestQuitGame(setup.ask_on_escape);
2202           break;
2203
2204         default:
2205           break;
2206       }
2207       break;
2208     }
2209
2210     default:
2211       if (key == KSYM_Escape)
2212       {
2213         SetGameStatus(GAME_MODE_MAIN);
2214
2215         DrawMainMenu();
2216
2217         return;
2218       }
2219   }
2220
2221   HandleKeysDebug(key);
2222 }
2223
2224 void HandleNoEvent(void)
2225 {
2226   HandleMouseCursor();
2227
2228   switch (game_status)
2229   {
2230     case GAME_MODE_PLAYING:
2231       HandleButtonOrFinger(-1, -1, -1);
2232       break;
2233   }
2234 }
2235
2236 void HandleEventActions(void)
2237 {
2238   // if (button_status && game_status != GAME_MODE_PLAYING)
2239   if (button_status && (game_status != GAME_MODE_PLAYING ||
2240                         tape.pausing ||
2241                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2242   {
2243     HandleButton(0, 0, button_status, -button_status);
2244   }
2245   else
2246   {
2247     HandleJoystick();
2248   }
2249
2250   if (network.enabled)
2251     HandleNetworking();
2252
2253   switch (game_status)
2254   {
2255     case GAME_MODE_MAIN:
2256       DrawPreviewLevelAnimation();
2257       break;
2258
2259     case GAME_MODE_EDITOR:
2260       HandleLevelEditorIdle();
2261       break;
2262
2263     default:
2264       break;
2265   }
2266 }
2267
2268 static void HandleTileCursor(int dx, int dy, int button)
2269 {
2270   if (!dx || !button)
2271     ClearPlayerMouseAction();
2272
2273   if (!dx && !dy)
2274     return;
2275
2276   if (button)
2277   {
2278     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2279                          (dx < 0 ? MB_LEFTBUTTON :
2280                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2281   }
2282   else if (!tile_cursor.moving)
2283   {
2284     int old_xpos = tile_cursor.xpos;
2285     int old_ypos = tile_cursor.ypos;
2286     int new_xpos = old_xpos;
2287     int new_ypos = old_ypos;
2288
2289     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2290       new_xpos = old_xpos + dx;
2291
2292     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2293       new_ypos = old_ypos + dy;
2294
2295     SetTileCursorTargetXY(new_xpos, new_ypos);
2296   }
2297 }
2298
2299 static int HandleJoystickForAllPlayers(void)
2300 {
2301   int i;
2302   int result = 0;
2303   boolean no_joysticks_configured = TRUE;
2304   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2305   static byte joy_action_last[MAX_PLAYERS];
2306
2307   for (i = 0; i < MAX_PLAYERS; i++)
2308     if (setup.input[i].use_joystick)
2309       no_joysticks_configured = FALSE;
2310
2311   // if no joysticks configured, map connected joysticks to players
2312   if (no_joysticks_configured)
2313     use_as_joystick_nr = TRUE;
2314
2315   for (i = 0; i < MAX_PLAYERS; i++)
2316   {
2317     byte joy_action = 0;
2318
2319     joy_action = JoystickExt(i, use_as_joystick_nr);
2320     result |= joy_action;
2321
2322     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2323         joy_action != joy_action_last[i])
2324       stored_player[i].action = joy_action;
2325
2326     joy_action_last[i] = joy_action;
2327   }
2328
2329   return result;
2330 }
2331
2332 void HandleJoystick(void)
2333 {
2334   static unsigned int joytest_delay = 0;
2335   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2336   static int joytest_last = 0;
2337   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2338   int delay_value       = GADGET_FRAME_DELAY;
2339   int joystick  = HandleJoystickForAllPlayers();
2340   int keyboard  = key_joystick_mapping;
2341   int joy       = (joystick | keyboard);
2342   int joytest   = joystick;
2343   int left      = joy & JOY_LEFT;
2344   int right     = joy & JOY_RIGHT;
2345   int up        = joy & JOY_UP;
2346   int down      = joy & JOY_DOWN;
2347   int button    = joy & JOY_BUTTON;
2348   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2349   int dx        = (left ? -1    : right ? 1     : 0);
2350   int dy        = (up   ? -1    : down  ? 1     : 0);
2351   boolean use_delay_value_first = (joytest != joytest_last);
2352
2353   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2354   {
2355     // do not handle this button event anymore
2356     return;
2357   }
2358
2359   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2360   {
2361     if (game_status == GAME_MODE_PLAYING)
2362     {
2363       // when playing MM style levels, also use delay for keyboard events
2364       joytest |= keyboard;
2365
2366       // only use first delay value for new events, but not for changed events
2367       use_delay_value_first = (!joytest != !joytest_last);
2368
2369       // only use delay after the initial keyboard event
2370       delay_value = 0;
2371     }
2372
2373     // for any joystick or keyboard event, enable playfield tile cursor
2374     if (dx || dy || button)
2375       SetTileCursorEnabled(TRUE);
2376   }
2377
2378   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2379   {
2380     // delay joystick/keyboard actions if axes/keys continually pressed
2381     newbutton = dx = dy = 0;
2382   }
2383   else
2384   {
2385     // first start with longer delay, then continue with shorter delay
2386     joytest_delay_value =
2387       (use_delay_value_first ? delay_value_first : delay_value);
2388   }
2389
2390   joytest_last = joytest;
2391
2392   switch (game_status)
2393   {
2394     case GAME_MODE_TITLE:
2395     case GAME_MODE_MAIN:
2396     case GAME_MODE_LEVELS:
2397     case GAME_MODE_LEVELNR:
2398     case GAME_MODE_SETUP:
2399     case GAME_MODE_INFO:
2400     case GAME_MODE_SCORES:
2401     {
2402       if (anyTextGadgetActive())
2403         break;
2404
2405       if (game_status == GAME_MODE_TITLE)
2406         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2407       else if (game_status == GAME_MODE_MAIN)
2408         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2409       else if (game_status == GAME_MODE_LEVELS)
2410         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2411       else if (game_status == GAME_MODE_LEVELNR)
2412         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2413       else if (game_status == GAME_MODE_SETUP)
2414         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2415       else if (game_status == GAME_MODE_INFO)
2416         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2417       else if (game_status == GAME_MODE_SCORES)
2418         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2419
2420       break;
2421     }
2422
2423     case GAME_MODE_PLAYING:
2424 #if 0
2425       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2426       if (tape.playing || keyboard)
2427         newbutton = ((joy & JOY_BUTTON) != 0);
2428 #endif
2429
2430       if (newbutton && AllPlayersGone)
2431       {
2432         GameEnd();
2433
2434         return;
2435       }
2436
2437       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2438       {
2439         if (joystick & JOY_ACTION)
2440           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2441       }
2442       else if (tape.recording && tape.pausing && !tape.use_mouse)
2443       {
2444         if (joystick & JOY_ACTION)
2445           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2446       }
2447
2448       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2449         HandleTileCursor(dx, dy, button);
2450
2451       break;
2452
2453     default:
2454       break;
2455   }
2456 }
2457
2458 void HandleSpecialGameControllerButtons(Event *event)
2459 {
2460 #if defined(TARGET_SDL2)
2461   int key_status;
2462   Key key;
2463
2464   switch (event->type)
2465   {
2466     case SDL_CONTROLLERBUTTONDOWN:
2467       key_status = KEY_PRESSED;
2468       break;
2469
2470     case SDL_CONTROLLERBUTTONUP:
2471       key_status = KEY_RELEASED;
2472       break;
2473
2474     default:
2475       return;
2476   }
2477
2478   switch (event->cbutton.button)
2479   {
2480     case SDL_CONTROLLER_BUTTON_START:
2481       key = KSYM_space;
2482       break;
2483
2484     case SDL_CONTROLLER_BUTTON_BACK:
2485       key = KSYM_Escape;
2486       break;
2487
2488     default:
2489       return;
2490   }
2491
2492   HandleKey(key, key_status);
2493 #endif
2494 }
2495
2496 void HandleSpecialGameControllerKeys(Key key, int key_status)
2497 {
2498 #if defined(TARGET_SDL2)
2499 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2500   int button = SDL_CONTROLLER_BUTTON_INVALID;
2501
2502   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2503   if (key == KSYM_Rewind)
2504     button = SDL_CONTROLLER_BUTTON_A;
2505   else if (key == KSYM_FastForward || key == KSYM_Menu)
2506     button = SDL_CONTROLLER_BUTTON_B;
2507
2508   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2509   {
2510     Event event;
2511
2512     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2513                   SDL_CONTROLLERBUTTONUP);
2514
2515     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2516     event.cbutton.button = button;
2517     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2518                            SDL_RELEASED);
2519
2520     HandleJoystickEvent(&event);
2521   }
2522 #endif
2523 #endif
2524 }
2525
2526 boolean DoKeysymAction(int keysym)
2527 {
2528   if (keysym < 0)
2529   {
2530     Key key = (Key)(-keysym);
2531
2532     HandleKey(key, KEY_PRESSED);
2533     HandleKey(key, KEY_RELEASED);
2534
2535     return TRUE;
2536   }
2537
2538   return FALSE;
2539 }