added touch controls (wipe gestures) for MM game engine on Android
[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
1393   if (button_nr < 0)
1394   {
1395     mx = old_mx;
1396     my = old_my;
1397     button_nr = -button_nr;
1398     button_hold = TRUE;
1399   }
1400   else
1401   {
1402     old_mx = mx;
1403     old_my = my;
1404   }
1405
1406 #if defined(PLATFORM_ANDROID)
1407   // when playing, only handle gadgets when using "follow finger" controls
1408   // or when using touch controls in combination with the MM game engine
1409   boolean handle_gadgets =
1410     (game_status != GAME_MODE_PLAYING ||
1411      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1412      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1413
1414   if (handle_gadgets &&
1415       HandleGadgets(mx, my, button))
1416   {
1417     /* do not handle this button event anymore */
1418     mx = my = -32;      /* force mouse event to be outside screen tiles */
1419   }
1420 #else
1421   if (HandleGadgets(mx, my, button))
1422   {
1423     /* do not handle this button event anymore */
1424     mx = my = -32;      /* force mouse event to be outside screen tiles */
1425   }
1426 #endif
1427
1428   if (HandleGlobalAnimClicks(mx, my, button))
1429   {
1430     /* do not handle this button event anymore */
1431     return;             /* force mouse event not to be handled at all */
1432   }
1433
1434   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1435     return;
1436
1437   /* do not use scroll wheel button events for anything other than gadgets */
1438   if (IS_WHEEL_BUTTON(button_nr))
1439     return;
1440
1441   switch (game_status)
1442   {
1443     case GAME_MODE_TITLE:
1444       HandleTitleScreen(mx, my, 0, 0, button);
1445       break;
1446
1447     case GAME_MODE_MAIN:
1448       HandleMainMenu(mx, my, 0, 0, button);
1449       break;
1450
1451     case GAME_MODE_PSEUDO_TYPENAME:
1452       HandleTypeName(0, KSYM_Return);
1453       break;
1454
1455     case GAME_MODE_LEVELS:
1456       HandleChooseLevelSet(mx, my, 0, 0, button);
1457       break;
1458
1459     case GAME_MODE_LEVELNR:
1460       HandleChooseLevelNr(mx, my, 0, 0, button);
1461       break;
1462
1463     case GAME_MODE_SCORES:
1464       HandleHallOfFame(0, 0, 0, 0, button);
1465       break;
1466
1467     case GAME_MODE_EDITOR:
1468       HandleLevelEditorIdle();
1469       break;
1470
1471     case GAME_MODE_INFO:
1472       HandleInfoScreen(mx, my, 0, 0, button);
1473       break;
1474
1475     case GAME_MODE_SETUP:
1476       HandleSetupScreen(mx, my, 0, 0, button);
1477       break;
1478
1479     case GAME_MODE_PLAYING:
1480       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1481         HandleButtonOrFinger(mx, my, button);
1482       else
1483         SetPlayerMouseAction(mx, my, button);
1484
1485 #ifdef DEBUG
1486       if (button == MB_PRESSED && !motion_status && !button_hold &&
1487           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1488         DumpTileFromScreen(mx, my);
1489 #endif
1490
1491       break;
1492
1493     default:
1494       break;
1495   }
1496 }
1497
1498 static boolean is_string_suffix(char *string, char *suffix)
1499 {
1500   int string_len = strlen(string);
1501   int suffix_len = strlen(suffix);
1502
1503   if (suffix_len > string_len)
1504     return FALSE;
1505
1506   return (strEqual(&string[string_len - suffix_len], suffix));
1507 }
1508
1509 #define MAX_CHEAT_INPUT_LEN     32
1510
1511 static void HandleKeysSpecial(Key key)
1512 {
1513   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1514   char letter = getCharFromKey(key);
1515   int cheat_input_len = strlen(cheat_input);
1516   int i;
1517
1518   if (letter == 0)
1519     return;
1520
1521   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1522   {
1523     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1524       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1525
1526     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1527   }
1528
1529   cheat_input[cheat_input_len++] = letter;
1530   cheat_input[cheat_input_len] = '\0';
1531
1532 #if DEBUG_EVENTS_KEY
1533   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1534 #endif
1535
1536   if (game_status == GAME_MODE_MAIN)
1537   {
1538     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1539         is_string_suffix(cheat_input, ":ist"))
1540     {
1541       InsertSolutionTape();
1542     }
1543     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1544              is_string_suffix(cheat_input, ":rg"))
1545     {
1546       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1547       DrawMainMenu();
1548     }
1549     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1550              is_string_suffix(cheat_input, ":rs"))
1551     {
1552       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1553       DrawMainMenu();
1554     }
1555     else if (is_string_suffix(cheat_input, ":reload-music") ||
1556              is_string_suffix(cheat_input, ":rm"))
1557     {
1558       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1559       DrawMainMenu();
1560     }
1561     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1562              is_string_suffix(cheat_input, ":ra"))
1563     {
1564       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1565                           1 << ARTWORK_TYPE_SOUNDS |
1566                           1 << ARTWORK_TYPE_MUSIC);
1567       DrawMainMenu();
1568     }
1569     else if (is_string_suffix(cheat_input, ":dump-level") ||
1570              is_string_suffix(cheat_input, ":dl"))
1571     {
1572       DumpLevel(&level);
1573     }
1574     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1575              is_string_suffix(cheat_input, ":dt"))
1576     {
1577       DumpTape(&tape);
1578     }
1579     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1580              is_string_suffix(cheat_input, ":ft"))
1581     {
1582       /* fix single-player tapes that contain player input for more than one
1583          player (due to a bug in 3.3.1.2 and earlier versions), which results
1584          in playing levels with more than one player in multi-player mode,
1585          even though the tape was originally recorded in single-player mode */
1586
1587       /* remove player input actions for all players but the first one */
1588       for (i = 1; i < MAX_PLAYERS; i++)
1589         tape.player_participates[i] = FALSE;
1590
1591       tape.changed = TRUE;
1592     }
1593     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1594              is_string_suffix(cheat_input, ":snl"))
1595     {
1596       SaveNativeLevel(&level);
1597     }
1598     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1599              is_string_suffix(cheat_input, ":fps"))
1600     {
1601       global.show_frames_per_second = !global.show_frames_per_second;
1602     }
1603   }
1604   else if (game_status == GAME_MODE_PLAYING)
1605   {
1606 #ifdef DEBUG
1607     if (is_string_suffix(cheat_input, ".q"))
1608       DEBUG_SetMaximumDynamite();
1609 #endif
1610   }
1611   else if (game_status == GAME_MODE_EDITOR)
1612   {
1613     if (is_string_suffix(cheat_input, ":dump-brush") ||
1614         is_string_suffix(cheat_input, ":DB"))
1615     {
1616       DumpBrush();
1617     }
1618     else if (is_string_suffix(cheat_input, ":DDB"))
1619     {
1620       DumpBrush_Small();
1621     }
1622   }
1623 }
1624
1625 void HandleKeysDebug(Key key)
1626 {
1627 #ifdef DEBUG
1628   int i;
1629
1630   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1631   {
1632     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1633
1634     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1635     {
1636       if (key == setup.debug.frame_delay_key[i] &&
1637           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1638       {
1639         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1640                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1641
1642         if (!setup.debug.frame_delay_game_only)
1643           MenuFrameDelay = GameFrameDelay;
1644
1645         SetVideoFrameDelay(GameFrameDelay);
1646
1647         if (GameFrameDelay > ONE_SECOND_DELAY)
1648           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1649         else if (GameFrameDelay != 0)
1650           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1651                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1652                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1653         else
1654           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1655
1656         break;
1657       }
1658     }
1659   }
1660
1661   if (game_status == GAME_MODE_PLAYING)
1662   {
1663     if (key == KSYM_d)
1664     {
1665       options.debug = !options.debug;
1666
1667       Error(ERR_DEBUG, "debug mode %s",
1668             (options.debug ? "enabled" : "disabled"));
1669     }
1670     else if (key == KSYM_v)
1671     {
1672       Error(ERR_DEBUG, "currently using game engine version %d",
1673             game.engine_version);
1674     }
1675   }
1676 #endif
1677 }
1678
1679 void HandleKey(Key key, int key_status)
1680 {
1681   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1682   static boolean ignore_repeated_key = FALSE;
1683   static struct SetupKeyboardInfo ski;
1684   static struct SetupShortcutInfo ssi;
1685   static struct
1686   {
1687     Key *key_custom;
1688     Key *key_snap;
1689     Key key_default;
1690     byte action;
1691   } key_info[] =
1692   {
1693     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1694     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1695     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1696     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1697     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1698     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1699   };
1700   int joy = 0;
1701   int i;
1702
1703 #if defined(TARGET_SDL2)
1704   /* map special keys (media keys / remote control buttons) to default keys */
1705   if (key == KSYM_PlayPause)
1706     key = KSYM_space;
1707   else if (key == KSYM_Select)
1708     key = KSYM_Return;
1709 #endif
1710
1711   HandleSpecialGameControllerKeys(key, key_status);
1712
1713   if (game_status == GAME_MODE_PLAYING)
1714   {
1715     /* only needed for single-step tape recording mode */
1716     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1717     int pnr;
1718
1719     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1720     {
1721       byte key_action = 0;
1722
1723       if (setup.input[pnr].use_joystick)
1724         continue;
1725
1726       ski = setup.input[pnr].key;
1727
1728       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1729         if (key == *key_info[i].key_custom)
1730           key_action |= key_info[i].action;
1731
1732       /* use combined snap+direction keys for the first player only */
1733       if (pnr == 0)
1734       {
1735         ssi = setup.shortcut;
1736
1737         for (i = 0; i < NUM_DIRECTIONS; i++)
1738           if (key == *key_info[i].key_snap)
1739             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1740       }
1741
1742       if (key_status == KEY_PRESSED)
1743         stored_player[pnr].action |= key_action;
1744       else
1745         stored_player[pnr].action &= ~key_action;
1746
1747       if (tape.single_step && tape.recording && tape.pausing)
1748       {
1749         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1750         {
1751           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1752
1753           /* if snap key already pressed, keep pause mode when releasing */
1754           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1755             has_snapped[pnr] = TRUE;
1756         }
1757         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1758         {
1759           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1760
1761           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1762               getRedDiskReleaseFlag_SP() == 0)
1763           {
1764             /* add a single inactive frame before dropping starts */
1765             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1766             stored_player[pnr].force_dropping = TRUE;
1767           }
1768         }
1769         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1770         {
1771           /* if snap key was pressed without direction, leave pause mode */
1772           if (!has_snapped[pnr])
1773             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1774
1775           has_snapped[pnr] = FALSE;
1776         }
1777       }
1778       else if (tape.recording && tape.pausing && !tape.use_mouse)
1779       {
1780         /* prevent key release events from un-pausing a paused game */
1781         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1782           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1783       }
1784     }
1785   }
1786   else
1787   {
1788     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1789       if (key == key_info[i].key_default)
1790         joy |= key_info[i].action;
1791   }
1792
1793   if (joy)
1794   {
1795     if (key_status == KEY_PRESSED)
1796       key_joystick_mapping |= joy;
1797     else
1798       key_joystick_mapping &= ~joy;
1799
1800     HandleJoystick();
1801   }
1802
1803   if (game_status != GAME_MODE_PLAYING)
1804     key_joystick_mapping = 0;
1805
1806   if (key_status == KEY_RELEASED)
1807   {
1808     // reset flag to ignore repeated "key pressed" events after key release
1809     ignore_repeated_key = FALSE;
1810
1811     return;
1812   }
1813
1814   if ((key == KSYM_F11 ||
1815        ((key == KSYM_Return ||
1816          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1817       video.fullscreen_available &&
1818       !ignore_repeated_key)
1819   {
1820     setup.fullscreen = !setup.fullscreen;
1821
1822     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1823
1824     if (game_status == GAME_MODE_SETUP)
1825       RedrawSetupScreenAfterFullscreenToggle();
1826
1827     // set flag to ignore repeated "key pressed" events
1828     ignore_repeated_key = TRUE;
1829
1830     return;
1831   }
1832
1833   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1834        key == KSYM_minus || key == KSYM_KP_Subtract ||
1835        key == KSYM_plus  || key == KSYM_KP_Add ||
1836        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1837       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1838       video.window_scaling_available &&
1839       !video.fullscreen_enabled)
1840   {
1841     if (key == KSYM_0 || key == KSYM_KP_0)
1842       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1843     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1844       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1845     else
1846       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1847
1848     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1849       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1850     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1851       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1852
1853     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1854
1855     if (game_status == GAME_MODE_SETUP)
1856       RedrawSetupScreenAfterFullscreenToggle();
1857
1858     return;
1859   }
1860
1861   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1862                                       key == KSYM_Return ||
1863                                       key == KSYM_Escape)))
1864   {
1865     /* do not handle this key event anymore */
1866     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1867       return;
1868   }
1869
1870   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1871       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1872   {
1873     GameEnd();
1874
1875     return;
1876   }
1877
1878   if (game_status == GAME_MODE_MAIN &&
1879       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1880   {
1881     StartGameActions(options.network, setup.autorecord, level.random_seed);
1882
1883     return;
1884   }
1885
1886   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1887   {
1888     if (key == setup.shortcut.save_game)
1889       TapeQuickSave();
1890     else if (key == setup.shortcut.load_game)
1891       TapeQuickLoad();
1892     else if (key == setup.shortcut.toggle_pause)
1893       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1894
1895     HandleTapeButtonKeys(key);
1896     HandleSoundButtonKeys(key);
1897   }
1898
1899   if (game_status == GAME_MODE_PLAYING && !network_playing)
1900   {
1901     int centered_player_nr_next = -999;
1902
1903     if (key == setup.shortcut.focus_player_all)
1904       centered_player_nr_next = -1;
1905     else
1906       for (i = 0; i < MAX_PLAYERS; i++)
1907         if (key == setup.shortcut.focus_player[i])
1908           centered_player_nr_next = i;
1909
1910     if (centered_player_nr_next != -999)
1911     {
1912       game.centered_player_nr_next = centered_player_nr_next;
1913       game.set_centered_player = TRUE;
1914
1915       if (tape.recording)
1916       {
1917         tape.centered_player_nr_next = game.centered_player_nr_next;
1918         tape.set_centered_player = TRUE;
1919       }
1920     }
1921   }
1922
1923   HandleKeysSpecial(key);
1924
1925   if (HandleGadgetsKeyInput(key))
1926   {
1927     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1928       key = KSYM_UNDEFINED;
1929   }
1930
1931   switch (game_status)
1932   {
1933     case GAME_MODE_PSEUDO_TYPENAME:
1934       HandleTypeName(0, key);
1935       break;
1936
1937     case GAME_MODE_TITLE:
1938     case GAME_MODE_MAIN:
1939     case GAME_MODE_LEVELS:
1940     case GAME_MODE_LEVELNR:
1941     case GAME_MODE_SETUP:
1942     case GAME_MODE_INFO:
1943     case GAME_MODE_SCORES:
1944       switch (key)
1945       {
1946         case KSYM_space:
1947         case KSYM_Return:
1948           if (game_status == GAME_MODE_TITLE)
1949             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1950           else if (game_status == GAME_MODE_MAIN)
1951             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1952           else if (game_status == GAME_MODE_LEVELS)
1953             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1954           else if (game_status == GAME_MODE_LEVELNR)
1955             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1956           else if (game_status == GAME_MODE_SETUP)
1957             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1958           else if (game_status == GAME_MODE_INFO)
1959             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1960           else if (game_status == GAME_MODE_SCORES)
1961             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1962           break;
1963
1964         case KSYM_Escape:
1965           if (game_status != GAME_MODE_MAIN)
1966             FadeSkipNextFadeIn();
1967
1968           if (game_status == GAME_MODE_TITLE)
1969             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1970           else if (game_status == GAME_MODE_LEVELS)
1971             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1972           else if (game_status == GAME_MODE_LEVELNR)
1973             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1974           else if (game_status == GAME_MODE_SETUP)
1975             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1976           else if (game_status == GAME_MODE_INFO)
1977             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1978           else if (game_status == GAME_MODE_SCORES)
1979             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1980           break;
1981
1982         case KSYM_Page_Up:
1983           if (game_status == GAME_MODE_LEVELS)
1984             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1985           else if (game_status == GAME_MODE_LEVELNR)
1986             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1987           else if (game_status == GAME_MODE_SETUP)
1988             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1989           else if (game_status == GAME_MODE_INFO)
1990             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1991           else if (game_status == GAME_MODE_SCORES)
1992             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1993           break;
1994
1995         case KSYM_Page_Down:
1996           if (game_status == GAME_MODE_LEVELS)
1997             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1998           else if (game_status == GAME_MODE_LEVELNR)
1999             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2000           else if (game_status == GAME_MODE_SETUP)
2001             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2002           else if (game_status == GAME_MODE_INFO)
2003             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2004           else if (game_status == GAME_MODE_SCORES)
2005             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2006           break;
2007
2008         default:
2009           break;
2010       }
2011       break;
2012
2013     case GAME_MODE_EDITOR:
2014       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2015         HandleLevelEditorKeyInput(key);
2016       break;
2017
2018     case GAME_MODE_PLAYING:
2019     {
2020       switch (key)
2021       {
2022         case KSYM_Escape:
2023           RequestQuitGame(setup.ask_on_escape);
2024           break;
2025
2026         default:
2027           break;
2028       }
2029       break;
2030     }
2031
2032     default:
2033       if (key == KSYM_Escape)
2034       {
2035         SetGameStatus(GAME_MODE_MAIN);
2036
2037         DrawMainMenu();
2038
2039         return;
2040       }
2041   }
2042
2043   HandleKeysDebug(key);
2044 }
2045
2046 void HandleNoEvent()
2047 {
2048   HandleMouseCursor();
2049
2050   switch (game_status)
2051   {
2052     case GAME_MODE_PLAYING:
2053       HandleButtonOrFinger(-1, -1, -1);
2054       break;
2055   }
2056 }
2057
2058 void HandleEventActions()
2059 {
2060   // if (button_status && game_status != GAME_MODE_PLAYING)
2061   if (button_status && (game_status != GAME_MODE_PLAYING ||
2062                         tape.pausing ||
2063                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2064   {
2065     HandleButton(0, 0, button_status, -button_status);
2066   }
2067   else
2068   {
2069     HandleJoystick();
2070   }
2071
2072 #if defined(NETWORK_AVALIABLE)
2073   if (options.network)
2074     HandleNetworking();
2075 #endif
2076
2077   switch (game_status)
2078   {
2079     case GAME_MODE_MAIN:
2080       DrawPreviewLevelAnimation();
2081       break;
2082
2083     case GAME_MODE_EDITOR:
2084       HandleLevelEditorIdle();
2085       break;
2086
2087     default:
2088       break;
2089   }
2090 }
2091
2092 static int HandleJoystickForAllPlayers()
2093 {
2094   int i;
2095   int result = 0;
2096   boolean no_joysticks_configured = TRUE;
2097   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2098   static byte joy_action_last[MAX_PLAYERS];
2099
2100   for (i = 0; i < MAX_PLAYERS; i++)
2101     if (setup.input[i].use_joystick)
2102       no_joysticks_configured = FALSE;
2103
2104   /* if no joysticks configured, map connected joysticks to players */
2105   if (no_joysticks_configured)
2106     use_as_joystick_nr = TRUE;
2107
2108   for (i = 0; i < MAX_PLAYERS; i++)
2109   {
2110     byte joy_action = 0;
2111
2112     joy_action = JoystickExt(i, use_as_joystick_nr);
2113     result |= joy_action;
2114
2115     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2116         joy_action != joy_action_last[i])
2117       stored_player[i].action = joy_action;
2118
2119     joy_action_last[i] = joy_action;
2120   }
2121
2122   return result;
2123 }
2124
2125 void HandleJoystick()
2126 {
2127   int joystick  = HandleJoystickForAllPlayers();
2128   int keyboard  = key_joystick_mapping;
2129   int joy       = (joystick | keyboard);
2130   int left      = joy & JOY_LEFT;
2131   int right     = joy & JOY_RIGHT;
2132   int up        = joy & JOY_UP;
2133   int down      = joy & JOY_DOWN;
2134   int button    = joy & JOY_BUTTON;
2135   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2136   int dx        = (left ? -1    : right ? 1     : 0);
2137   int dy        = (up   ? -1    : down  ? 1     : 0);
2138
2139   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2140   {
2141     /* do not handle this button event anymore */
2142     return;
2143   }
2144
2145   switch (game_status)
2146   {
2147     case GAME_MODE_TITLE:
2148     case GAME_MODE_MAIN:
2149     case GAME_MODE_LEVELS:
2150     case GAME_MODE_LEVELNR:
2151     case GAME_MODE_SETUP:
2152     case GAME_MODE_INFO:
2153     {
2154       static unsigned int joystickmove_delay = 0;
2155       static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2156       static int joystick_last = 0;
2157
2158       if (joystick && !button &&
2159           !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2160       {
2161         /* delay joystick actions if buttons/axes continually pressed */
2162         newbutton = dx = dy = 0;
2163       }
2164       else
2165       {
2166         /* start with longer delay, then continue with shorter delay */
2167         if (joystick != joystick_last)
2168           joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2169         else
2170           joystickmove_delay_value = GADGET_FRAME_DELAY;
2171       }
2172
2173       if (game_status == GAME_MODE_TITLE)
2174         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2175       else if (game_status == GAME_MODE_MAIN)
2176         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2177       else if (game_status == GAME_MODE_LEVELS)
2178         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2179       else if (game_status == GAME_MODE_LEVELNR)
2180         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2181       else if (game_status == GAME_MODE_SETUP)
2182         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2183       else if (game_status == GAME_MODE_INFO)
2184         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2185
2186       joystick_last = joystick;
2187
2188       break;
2189     }
2190
2191     case GAME_MODE_SCORES:
2192       HandleHallOfFame(0, 0, dx, dy, !newbutton);
2193       break;
2194
2195     case GAME_MODE_PLAYING:
2196       if (tape.playing || keyboard)
2197         newbutton = ((joy & JOY_BUTTON) != 0);
2198
2199       if (newbutton && AllPlayersGone)
2200       {
2201         GameEnd();
2202
2203         return;
2204       }
2205
2206       if (tape.recording && tape.pausing)
2207       {
2208         if (joystick & JOY_ACTION)
2209           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2210       }
2211
2212       break;
2213
2214     default:
2215       break;
2216   }
2217 }
2218
2219 void HandleSpecialGameControllerButtons(Event *event)
2220 {
2221 #if defined(TARGET_SDL2)
2222   switch (event->type)
2223   {
2224     case SDL_CONTROLLERBUTTONDOWN:
2225       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2226         HandleKey(KSYM_space, KEY_PRESSED);
2227       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2228         HandleKey(KSYM_Escape, KEY_PRESSED);
2229
2230       break;
2231
2232     case SDL_CONTROLLERBUTTONUP:
2233       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2234         HandleKey(KSYM_space, KEY_RELEASED);
2235       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2236         HandleKey(KSYM_Escape, KEY_RELEASED);
2237
2238       break;
2239   }
2240 #endif
2241 }
2242
2243 void HandleSpecialGameControllerKeys(Key key, int key_status)
2244 {
2245 #if defined(TARGET_SDL2)
2246 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2247   int button = SDL_CONTROLLER_BUTTON_INVALID;
2248
2249   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2250   if (key == KSYM_Rewind)
2251     button = SDL_CONTROLLER_BUTTON_A;
2252   else if (key == KSYM_FastForward || key == KSYM_Menu)
2253     button = SDL_CONTROLLER_BUTTON_B;
2254
2255   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2256   {
2257     Event event;
2258
2259     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2260                   SDL_CONTROLLERBUTTONUP);
2261
2262     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2263     event.cbutton.button = button;
2264     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2265                            SDL_RELEASED);
2266
2267     HandleJoystickEvent(&event);
2268   }
2269 #endif
2270 #endif
2271 }