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