renamed function 'WaitEventFiltered' to 'WaitValidEvent'
[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 static boolean WaitValidEvent(Event *event)
132 {
133   WaitEvent(event);
134
135   if (!FilterEvents(event))
136     return FALSE;
137
138   if (SkipPressedMouseMotionEvent(event))
139     return FALSE;
140
141   return TRUE;
142 }
143
144 /* this is especially needed for event modifications for the Android target:
145    if mouse coordinates should be modified in the event filter function,
146    using a properly installed SDL event filter does not work, because in
147    the event filter, mouse coordinates in the event structure are still
148    physical pixel positions, not logical (scaled) screen positions, so this
149    has to be handled at a later stage in the event processing functions
150    (when device pixel positions are already converted to screen positions) */
151
152 boolean NextValidEvent(Event *event)
153 {
154   while (PendingEvent())
155     if (WaitValidEvent(event))
156       return TRUE;
157
158   return FALSE;
159 }
160
161 void HandleEvents()
162 {
163   Event event;
164   unsigned int event_frame_delay = 0;
165   unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
166
167   ResetDelayCounter(&event_frame_delay);
168
169   while (NextValidEvent(&event))
170   {
171     switch (event.type)
172     {
173       case EVENT_BUTTONPRESS:
174       case EVENT_BUTTONRELEASE:
175         HandleButtonEvent((ButtonEvent *) &event);
176         break;
177
178       case EVENT_MOTIONNOTIFY:
179         HandleMotionEvent((MotionEvent *) &event);
180         break;
181
182 #if defined(TARGET_SDL2)
183       case EVENT_WHEELMOTION:
184         HandleWheelEvent((WheelEvent *) &event);
185         break;
186
187       case SDL_WINDOWEVENT:
188         HandleWindowEvent((WindowEvent *) &event);
189         break;
190
191       case EVENT_FINGERPRESS:
192       case EVENT_FINGERRELEASE:
193       case EVENT_FINGERMOTION:
194         HandleFingerEvent((FingerEvent *) &event);
195         break;
196
197       case EVENT_TEXTINPUT:
198         HandleTextEvent((TextEvent *) &event);
199         break;
200
201       case SDL_APP_WILLENTERBACKGROUND:
202       case SDL_APP_DIDENTERBACKGROUND:
203       case SDL_APP_WILLENTERFOREGROUND:
204       case SDL_APP_DIDENTERFOREGROUND:
205         HandlePauseResumeEvent((PauseResumeEvent *) &event);
206         break;
207 #endif
208
209       case EVENT_KEYPRESS:
210       case EVENT_KEYRELEASE:
211         HandleKeyEvent((KeyEvent *) &event);
212         break;
213
214       default:
215         HandleOtherEvents(&event);
216         break;
217     }
218
219     // do not handle events for longer than standard frame delay period
220     if (DelayReached(&event_frame_delay, event_frame_delay_value))
221       break;
222   }
223 }
224
225 void HandleOtherEvents(Event *event)
226 {
227   switch (event->type)
228   {
229     case EVENT_EXPOSE:
230       HandleExposeEvent((ExposeEvent *) event);
231       break;
232
233     case EVENT_UNMAPNOTIFY:
234 #if 0
235       /* This causes the game to stop not only when iconified, but also
236          when on another virtual desktop, which might be not desired. */
237       SleepWhileUnmapped();
238 #endif
239       break;
240
241     case EVENT_FOCUSIN:
242     case EVENT_FOCUSOUT:
243       HandleFocusEvent((FocusChangeEvent *) event);
244       break;
245
246     case EVENT_CLIENTMESSAGE:
247       HandleClientMessageEvent((ClientMessageEvent *) event);
248       break;
249
250 #if defined(TARGET_SDL)
251 #if defined(TARGET_SDL2)
252     case SDL_CONTROLLERBUTTONDOWN:
253     case SDL_CONTROLLERBUTTONUP:
254       // for any game controller button event, disable overlay buttons
255       SetOverlayEnabled(FALSE);
256
257       HandleSpecialGameControllerButtons(event);
258
259       /* FALL THROUGH */
260     case SDL_CONTROLLERDEVICEADDED:
261     case SDL_CONTROLLERDEVICEREMOVED:
262     case SDL_CONTROLLERAXISMOTION:
263 #endif
264     case SDL_JOYAXISMOTION:
265     case SDL_JOYBUTTONDOWN:
266     case SDL_JOYBUTTONUP:
267       HandleJoystickEvent(event);
268       break;
269
270     case SDL_SYSWMEVENT:
271       HandleWindowManagerEvent(event);
272       break;
273 #endif
274
275     default:
276       break;
277   }
278 }
279
280 void HandleMouseCursor()
281 {
282   if (game_status == GAME_MODE_TITLE)
283   {
284     /* when showing title screens, hide mouse pointer (if not moved) */
285
286     if (gfx.cursor_mode != CURSOR_NONE &&
287         DelayReached(&special_cursor_delay, special_cursor_delay_value))
288     {
289       SetMouseCursor(CURSOR_NONE);
290     }
291   }
292   else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
293                                                 tape.single_step))
294   {
295     /* when playing, display a special mouse pointer inside the playfield */
296
297     if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
298         cursor_inside_playfield &&
299         DelayReached(&special_cursor_delay, special_cursor_delay_value))
300     {
301       SetMouseCursor(CURSOR_PLAYFIELD);
302     }
303   }
304   else if (gfx.cursor_mode != CURSOR_DEFAULT)
305   {
306     SetMouseCursor(CURSOR_DEFAULT);
307   }
308
309   /* this is set after all pending events have been processed */
310   cursor_mode_last = gfx.cursor_mode;
311 }
312
313 void EventLoop(void)
314 {
315   while (1)
316   {
317     if (PendingEvent())
318       HandleEvents();
319     else
320       HandleMouseCursor();
321
322     /* also execute after pending events have been processed before */
323     HandleNoEvent();
324
325     /* don't use all CPU time when idle; the main loop while playing
326        has its own synchronization and is CPU friendly, too */
327
328     if (game_status == GAME_MODE_PLAYING)
329       HandleGameActions();
330
331     /* always copy backbuffer to visible screen for every video frame */
332     BackToFront();
333
334     /* reset video frame delay to default (may change again while playing) */
335     SetVideoFrameDelay(MenuFrameDelay);
336
337     if (game_status == GAME_MODE_QUIT)
338       return;
339   }
340 }
341
342 void ClearEventQueue()
343 {
344   Event event;
345
346   while (NextValidEvent(&event))
347   {
348     switch (event.type)
349     {
350       case EVENT_BUTTONRELEASE:
351         button_status = MB_RELEASED;
352         break;
353
354       case EVENT_KEYRELEASE:
355         ClearPlayerAction();
356         break;
357
358 #if defined(TARGET_SDL2)
359       case SDL_CONTROLLERBUTTONUP:
360         HandleJoystickEvent(&event);
361         ClearPlayerAction();
362         break;
363 #endif
364
365       default:
366         HandleOtherEvents(&event);
367         break;
368     }
369   }
370 }
371
372 void ClearPlayerAction()
373 {
374   int i;
375
376   /* simulate key release events for still pressed keys */
377   key_joystick_mapping = 0;
378   for (i = 0; i < MAX_PLAYERS; i++)
379     stored_player[i].action = 0;
380
381   ClearJoystickState();
382 }
383
384 void SleepWhileUnmapped()
385 {
386   boolean window_unmapped = TRUE;
387
388   KeyboardAutoRepeatOn();
389
390   while (window_unmapped)
391   {
392     Event event;
393
394     if (!WaitValidEvent(&event))
395       continue;
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     return;             /* force mouse event not to be handled at all */
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     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1445              is_string_suffix(cheat_input, ":fps"))
1446     {
1447       global.show_frames_per_second = !global.show_frames_per_second;
1448     }
1449   }
1450   else if (game_status == GAME_MODE_PLAYING)
1451   {
1452 #ifdef DEBUG
1453     if (is_string_suffix(cheat_input, ".q"))
1454       DEBUG_SetMaximumDynamite();
1455 #endif
1456   }
1457   else if (game_status == GAME_MODE_EDITOR)
1458   {
1459     if (is_string_suffix(cheat_input, ":dump-brush") ||
1460         is_string_suffix(cheat_input, ":DB"))
1461     {
1462       DumpBrush();
1463     }
1464     else if (is_string_suffix(cheat_input, ":DDB"))
1465     {
1466       DumpBrush_Small();
1467     }
1468   }
1469 }
1470
1471 void HandleKeysDebug(Key key)
1472 {
1473 #ifdef DEBUG
1474   int i;
1475
1476   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1477   {
1478     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1479
1480     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1481     {
1482       if (key == setup.debug.frame_delay_key[i] &&
1483           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1484       {
1485         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1486                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1487
1488         if (!setup.debug.frame_delay_game_only)
1489           MenuFrameDelay = GameFrameDelay;
1490
1491         SetVideoFrameDelay(GameFrameDelay);
1492
1493         if (GameFrameDelay > ONE_SECOND_DELAY)
1494           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1495         else if (GameFrameDelay != 0)
1496           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1497                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1498                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1499         else
1500           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1501
1502         break;
1503       }
1504     }
1505   }
1506
1507   if (game_status == GAME_MODE_PLAYING)
1508   {
1509     if (key == KSYM_d)
1510     {
1511       options.debug = !options.debug;
1512
1513       Error(ERR_DEBUG, "debug mode %s",
1514             (options.debug ? "enabled" : "disabled"));
1515     }
1516     else if (key == KSYM_v)
1517     {
1518       Error(ERR_DEBUG, "currently using game engine version %d",
1519             game.engine_version);
1520     }
1521   }
1522 #endif
1523 }
1524
1525 void HandleKey(Key key, int key_status)
1526 {
1527   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1528   static boolean ignore_repeated_key = FALSE;
1529   static struct SetupKeyboardInfo ski;
1530   static struct SetupShortcutInfo ssi;
1531   static struct
1532   {
1533     Key *key_custom;
1534     Key *key_snap;
1535     Key key_default;
1536     byte action;
1537   } key_info[] =
1538   {
1539     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1540     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1541     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1542     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1543     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1544     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1545   };
1546   int joy = 0;
1547   int i;
1548
1549 #if defined(TARGET_SDL2)
1550   /* map special keys (media keys / remote control buttons) to default keys */
1551   if (key == KSYM_PlayPause)
1552     key = KSYM_space;
1553   else if (key == KSYM_Select)
1554     key = KSYM_Return;
1555 #endif
1556
1557   HandleSpecialGameControllerKeys(key, key_status);
1558
1559   if (game_status == GAME_MODE_PLAYING)
1560   {
1561     /* only needed for single-step tape recording mode */
1562     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1563     int pnr;
1564
1565     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1566     {
1567       byte key_action = 0;
1568
1569       if (setup.input[pnr].use_joystick)
1570         continue;
1571
1572       ski = setup.input[pnr].key;
1573
1574       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1575         if (key == *key_info[i].key_custom)
1576           key_action |= key_info[i].action;
1577
1578       /* use combined snap+direction keys for the first player only */
1579       if (pnr == 0)
1580       {
1581         ssi = setup.shortcut;
1582
1583         for (i = 0; i < NUM_DIRECTIONS; i++)
1584           if (key == *key_info[i].key_snap)
1585             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1586       }
1587
1588       if (key_status == KEY_PRESSED)
1589         stored_player[pnr].action |= key_action;
1590       else
1591         stored_player[pnr].action &= ~key_action;
1592
1593       if (tape.single_step && tape.recording && tape.pausing)
1594       {
1595         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1596         {
1597           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1598
1599           /* if snap key already pressed, keep pause mode when releasing */
1600           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1601             has_snapped[pnr] = TRUE;
1602         }
1603         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1604         {
1605           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1606
1607           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1608               getRedDiskReleaseFlag_SP() == 0)
1609           {
1610             /* add a single inactive frame before dropping starts */
1611             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1612             stored_player[pnr].force_dropping = TRUE;
1613           }
1614         }
1615         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1616         {
1617           /* if snap key was pressed without direction, leave pause mode */
1618           if (!has_snapped[pnr])
1619             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1620
1621           has_snapped[pnr] = FALSE;
1622         }
1623       }
1624       else if (tape.recording && tape.pausing)
1625       {
1626         /* prevent key release events from un-pausing a paused game */
1627         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1628           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1629       }
1630     }
1631   }
1632   else
1633   {
1634     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1635       if (key == key_info[i].key_default)
1636         joy |= key_info[i].action;
1637   }
1638
1639   if (joy)
1640   {
1641     if (key_status == KEY_PRESSED)
1642       key_joystick_mapping |= joy;
1643     else
1644       key_joystick_mapping &= ~joy;
1645
1646     HandleJoystick();
1647   }
1648
1649   if (game_status != GAME_MODE_PLAYING)
1650     key_joystick_mapping = 0;
1651
1652   if (key_status == KEY_RELEASED)
1653   {
1654     // reset flag to ignore repeated "key pressed" events after key release
1655     ignore_repeated_key = FALSE;
1656
1657     return;
1658   }
1659
1660   if ((key == KSYM_F11 ||
1661        ((key == KSYM_Return ||
1662          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1663       video.fullscreen_available &&
1664       !ignore_repeated_key)
1665   {
1666     setup.fullscreen = !setup.fullscreen;
1667
1668     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1669
1670     if (game_status == GAME_MODE_SETUP)
1671       RedrawSetupScreenAfterFullscreenToggle();
1672
1673     // set flag to ignore repeated "key pressed" events
1674     ignore_repeated_key = TRUE;
1675
1676     return;
1677   }
1678
1679   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1680        key == KSYM_minus || key == KSYM_KP_Subtract ||
1681        key == KSYM_plus  || key == KSYM_KP_Add ||
1682        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1683       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1684       video.window_scaling_available &&
1685       !video.fullscreen_enabled)
1686   {
1687     if (key == KSYM_0 || key == KSYM_KP_0)
1688       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1689     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1690       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1691     else
1692       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1693
1694     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1695       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1696     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1697       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1698
1699     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1700
1701     if (game_status == GAME_MODE_SETUP)
1702       RedrawSetupScreenAfterFullscreenToggle();
1703
1704     return;
1705   }
1706
1707   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1708                                       key == KSYM_Return ||
1709                                       key == KSYM_Escape)))
1710   {
1711     /* do not handle this key event anymore */
1712     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1713       return;
1714   }
1715
1716   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1717       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1718   {
1719     GameEnd();
1720
1721     return;
1722   }
1723
1724   if (game_status == GAME_MODE_MAIN &&
1725       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1726   {
1727     StartGameActions(options.network, setup.autorecord, level.random_seed);
1728
1729     return;
1730   }
1731
1732   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1733   {
1734     if (key == setup.shortcut.save_game)
1735       TapeQuickSave();
1736     else if (key == setup.shortcut.load_game)
1737       TapeQuickLoad();
1738     else if (key == setup.shortcut.toggle_pause)
1739       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1740
1741     HandleTapeButtonKeys(key);
1742     HandleSoundButtonKeys(key);
1743   }
1744
1745   if (game_status == GAME_MODE_PLAYING && !network_playing)
1746   {
1747     int centered_player_nr_next = -999;
1748
1749     if (key == setup.shortcut.focus_player_all)
1750       centered_player_nr_next = -1;
1751     else
1752       for (i = 0; i < MAX_PLAYERS; i++)
1753         if (key == setup.shortcut.focus_player[i])
1754           centered_player_nr_next = i;
1755
1756     if (centered_player_nr_next != -999)
1757     {
1758       game.centered_player_nr_next = centered_player_nr_next;
1759       game.set_centered_player = TRUE;
1760
1761       if (tape.recording)
1762       {
1763         tape.centered_player_nr_next = game.centered_player_nr_next;
1764         tape.set_centered_player = TRUE;
1765       }
1766     }
1767   }
1768
1769   HandleKeysSpecial(key);
1770
1771   if (HandleGadgetsKeyInput(key))
1772   {
1773     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1774       key = KSYM_UNDEFINED;
1775   }
1776
1777   switch (game_status)
1778   {
1779     case GAME_MODE_PSEUDO_TYPENAME:
1780       HandleTypeName(0, key);
1781       break;
1782
1783     case GAME_MODE_TITLE:
1784     case GAME_MODE_MAIN:
1785     case GAME_MODE_LEVELS:
1786     case GAME_MODE_LEVELNR:
1787     case GAME_MODE_SETUP:
1788     case GAME_MODE_INFO:
1789     case GAME_MODE_SCORES:
1790       switch (key)
1791       {
1792         case KSYM_space:
1793         case KSYM_Return:
1794           if (game_status == GAME_MODE_TITLE)
1795             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1796           else if (game_status == GAME_MODE_MAIN)
1797             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1798           else if (game_status == GAME_MODE_LEVELS)
1799             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1800           else if (game_status == GAME_MODE_LEVELNR)
1801             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1802           else if (game_status == GAME_MODE_SETUP)
1803             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1804           else if (game_status == GAME_MODE_INFO)
1805             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1806           else if (game_status == GAME_MODE_SCORES)
1807             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1808           break;
1809
1810         case KSYM_Escape:
1811           if (game_status != GAME_MODE_MAIN)
1812             FadeSkipNextFadeIn();
1813
1814           if (game_status == GAME_MODE_TITLE)
1815             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1816           else if (game_status == GAME_MODE_LEVELS)
1817             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1818           else if (game_status == GAME_MODE_LEVELNR)
1819             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1820           else if (game_status == GAME_MODE_SETUP)
1821             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1822           else if (game_status == GAME_MODE_INFO)
1823             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1824           else if (game_status == GAME_MODE_SCORES)
1825             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1826           break;
1827
1828         case KSYM_Page_Up:
1829           if (game_status == GAME_MODE_LEVELS)
1830             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1831           else if (game_status == GAME_MODE_LEVELNR)
1832             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1833           else if (game_status == GAME_MODE_SETUP)
1834             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1835           else if (game_status == GAME_MODE_INFO)
1836             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1837           else if (game_status == GAME_MODE_SCORES)
1838             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1839           break;
1840
1841         case KSYM_Page_Down:
1842           if (game_status == GAME_MODE_LEVELS)
1843             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1844           else if (game_status == GAME_MODE_LEVELNR)
1845             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1846           else if (game_status == GAME_MODE_SETUP)
1847             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1848           else if (game_status == GAME_MODE_INFO)
1849             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1850           else if (game_status == GAME_MODE_SCORES)
1851             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1852           break;
1853
1854         default:
1855           break;
1856       }
1857       break;
1858
1859     case GAME_MODE_EDITOR:
1860       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1861         HandleLevelEditorKeyInput(key);
1862       break;
1863
1864     case GAME_MODE_PLAYING:
1865     {
1866       switch (key)
1867       {
1868         case KSYM_Escape:
1869           RequestQuitGame(setup.ask_on_escape);
1870           break;
1871
1872         default:
1873           break;
1874       }
1875       break;
1876     }
1877
1878     default:
1879       if (key == KSYM_Escape)
1880       {
1881         SetGameStatus(GAME_MODE_MAIN);
1882
1883         DrawMainMenu();
1884
1885         return;
1886       }
1887   }
1888
1889   HandleKeysDebug(key);
1890 }
1891
1892 void HandleNoEvent()
1893 {
1894   // if (button_status && game_status != GAME_MODE_PLAYING)
1895   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1896   {
1897     HandleButton(0, 0, button_status, -button_status);
1898   }
1899   else
1900   {
1901     HandleJoystick();
1902   }
1903
1904 #if defined(NETWORK_AVALIABLE)
1905   if (options.network)
1906     HandleNetworking();
1907 #endif
1908
1909   switch (game_status)
1910   {
1911     case GAME_MODE_MAIN:
1912       DrawPreviewLevelAnimation();
1913       break;
1914
1915     case GAME_MODE_EDITOR:
1916       HandleLevelEditorIdle();
1917       break;
1918
1919 #if defined(TARGET_SDL2)
1920     case GAME_MODE_PLAYING:
1921       HandleFollowFinger(-1, -1, -1);
1922       break;
1923 #endif
1924
1925     default:
1926       break;
1927   }
1928 }
1929
1930 static int HandleJoystickForAllPlayers()
1931 {
1932   int i;
1933   int result = 0;
1934   boolean no_joysticks_configured = TRUE;
1935   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1936   static byte joy_action_last[MAX_PLAYERS];
1937
1938   for (i = 0; i < MAX_PLAYERS; i++)
1939     if (setup.input[i].use_joystick)
1940       no_joysticks_configured = FALSE;
1941
1942   /* if no joysticks configured, map connected joysticks to players */
1943   if (no_joysticks_configured)
1944     use_as_joystick_nr = TRUE;
1945
1946   for (i = 0; i < MAX_PLAYERS; i++)
1947   {
1948     byte joy_action = 0;
1949
1950     joy_action = JoystickExt(i, use_as_joystick_nr);
1951     result |= joy_action;
1952
1953     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
1954         joy_action != joy_action_last[i])
1955       stored_player[i].action = joy_action;
1956
1957     joy_action_last[i] = joy_action;
1958   }
1959
1960   return result;
1961 }
1962
1963 void HandleJoystick()
1964 {
1965   int joystick  = HandleJoystickForAllPlayers();
1966   int keyboard  = key_joystick_mapping;
1967   int joy       = (joystick | keyboard);
1968   int left      = joy & JOY_LEFT;
1969   int right     = joy & JOY_RIGHT;
1970   int up        = joy & JOY_UP;
1971   int down      = joy & JOY_DOWN;
1972   int button    = joy & JOY_BUTTON;
1973   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1974   int dx        = (left ? -1    : right ? 1     : 0);
1975   int dy        = (up   ? -1    : down  ? 1     : 0);
1976
1977   if (HandleGlobalAnimClicks(-1, -1, newbutton))
1978   {
1979     /* do not handle this button event anymore */
1980     return;
1981   }
1982
1983   switch (game_status)
1984   {
1985     case GAME_MODE_TITLE:
1986     case GAME_MODE_MAIN:
1987     case GAME_MODE_LEVELS:
1988     case GAME_MODE_LEVELNR:
1989     case GAME_MODE_SETUP:
1990     case GAME_MODE_INFO:
1991     {
1992       static unsigned int joystickmove_delay = 0;
1993       static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
1994       static int joystick_last = 0;
1995
1996       if (joystick && !button &&
1997           !DelayReached(&joystickmove_delay, joystickmove_delay_value))
1998       {
1999         /* delay joystick actions if buttons/axes continually pressed */
2000         newbutton = dx = dy = 0;
2001       }
2002       else
2003       {
2004         /* start with longer delay, then continue with shorter delay */
2005         if (joystick != joystick_last)
2006           joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2007         else
2008           joystickmove_delay_value = GADGET_FRAME_DELAY;
2009       }
2010
2011       if (game_status == GAME_MODE_TITLE)
2012         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2013       else if (game_status == GAME_MODE_MAIN)
2014         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2015       else if (game_status == GAME_MODE_LEVELS)
2016         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2017       else if (game_status == GAME_MODE_LEVELNR)
2018         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2019       else if (game_status == GAME_MODE_SETUP)
2020         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2021       else if (game_status == GAME_MODE_INFO)
2022         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2023
2024       joystick_last = joystick;
2025
2026       break;
2027     }
2028
2029     case GAME_MODE_SCORES:
2030       HandleHallOfFame(0, 0, dx, dy, !newbutton);
2031       break;
2032
2033     case GAME_MODE_PLAYING:
2034       if (tape.playing || keyboard)
2035         newbutton = ((joy & JOY_BUTTON) != 0);
2036
2037       if (newbutton && AllPlayersGone)
2038       {
2039         GameEnd();
2040
2041         return;
2042       }
2043
2044       if (tape.recording && tape.pausing)
2045       {
2046         if (joystick & JOY_ACTION)
2047           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2048       }
2049
2050       break;
2051
2052     default:
2053       break;
2054   }
2055 }
2056
2057 void HandleSpecialGameControllerButtons(Event *event)
2058 {
2059 #if defined(TARGET_SDL2)
2060   switch (event->type)
2061   {
2062     case SDL_CONTROLLERBUTTONDOWN:
2063       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2064         HandleKey(KSYM_space, KEY_PRESSED);
2065       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2066         HandleKey(KSYM_Escape, KEY_PRESSED);
2067
2068       break;
2069
2070     case SDL_CONTROLLERBUTTONUP:
2071       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2072         HandleKey(KSYM_space, KEY_RELEASED);
2073       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2074         HandleKey(KSYM_Escape, KEY_RELEASED);
2075
2076       break;
2077   }
2078 #endif
2079 }
2080
2081 void HandleSpecialGameControllerKeys(Key key, int key_status)
2082 {
2083 #if defined(TARGET_SDL2)
2084 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2085   int button = SDL_CONTROLLER_BUTTON_INVALID;
2086
2087   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2088   if (key == KSYM_Rewind)
2089     button = SDL_CONTROLLER_BUTTON_A;
2090   else if (key == KSYM_FastForward || key == KSYM_Menu)
2091     button = SDL_CONTROLLER_BUTTON_B;
2092
2093   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2094   {
2095     Event event;
2096
2097     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2098                   SDL_CONTROLLERBUTTONUP);
2099
2100     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2101     event.cbutton.button = button;
2102     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2103                            SDL_RELEASED);
2104
2105     HandleJoystickEvent(&event);
2106   }
2107 #endif
2108 #endif
2109 }