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