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