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