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