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