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