fixed dumping tiles when holding mouse button (MM engine)
[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       if (level.game_engine_type != GAME_ENGINE_TYPE_MM)
302         SetMouseCursor(CURSOR_PLAYFIELD);
303     }
304   }
305   else if (gfx.cursor_mode != CURSOR_DEFAULT)
306   {
307     SetMouseCursor(CURSOR_DEFAULT);
308   }
309
310   /* this is set after all pending events have been processed */
311   cursor_mode_last = gfx.cursor_mode;
312 }
313
314 void EventLoop(void)
315 {
316   while (1)
317   {
318     if (PendingEvent())
319       HandleEvents();
320     else
321       HandleMouseCursor();
322
323     /* also execute after pending events have been processed before */
324     HandleNoEvent();
325
326     /* don't use all CPU time when idle; the main loop while playing
327        has its own synchronization and is CPU friendly, too */
328
329     if (game_status == GAME_MODE_PLAYING)
330       HandleGameActions();
331
332     /* always copy backbuffer to visible screen for every video frame */
333     BackToFront();
334
335     /* reset video frame delay to default (may change again while playing) */
336     SetVideoFrameDelay(MenuFrameDelay);
337
338     if (game_status == GAME_MODE_QUIT)
339       return;
340   }
341 }
342
343 void ClearEventQueue()
344 {
345   Event event;
346
347   while (NextValidEvent(&event))
348   {
349     switch (event.type)
350     {
351       case EVENT_BUTTONRELEASE:
352         button_status = MB_RELEASED;
353         break;
354
355       case EVENT_KEYRELEASE:
356         ClearPlayerAction();
357         break;
358
359 #if defined(TARGET_SDL2)
360       case SDL_CONTROLLERBUTTONUP:
361         HandleJoystickEvent(&event);
362         ClearPlayerAction();
363         break;
364 #endif
365
366       default:
367         HandleOtherEvents(&event);
368         break;
369     }
370   }
371 }
372
373 void ClearPlayerAction()
374 {
375   int i;
376
377   /* simulate key release events for still pressed keys */
378   key_joystick_mapping = 0;
379   for (i = 0; i < MAX_PLAYERS; i++)
380     stored_player[i].action = 0;
381
382   ClearJoystickState();
383 }
384
385 void SleepWhileUnmapped()
386 {
387   boolean window_unmapped = TRUE;
388
389   KeyboardAutoRepeatOn();
390
391   while (window_unmapped)
392   {
393     Event event;
394
395     if (!WaitValidEvent(&event))
396       continue;
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     case GAME_MODE_PLAYING:
1328       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1329         ClickElement(mx, my, button);
1330 #if defined(TARGET_SDL2)
1331       else
1332         HandleFollowFinger(mx, my, button);
1333 #endif
1334
1335 #ifdef DEBUG
1336       if (button == MB_PRESSED && !motion_status && !button_hold &&
1337           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1338         DumpTileFromScreen(mx, my);
1339 #endif
1340
1341       break;
1342
1343     default:
1344       break;
1345   }
1346 }
1347
1348 static boolean is_string_suffix(char *string, char *suffix)
1349 {
1350   int string_len = strlen(string);
1351   int suffix_len = strlen(suffix);
1352
1353   if (suffix_len > string_len)
1354     return FALSE;
1355
1356   return (strEqual(&string[string_len - suffix_len], suffix));
1357 }
1358
1359 #define MAX_CHEAT_INPUT_LEN     32
1360
1361 static void HandleKeysSpecial(Key key)
1362 {
1363   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1364   char letter = getCharFromKey(key);
1365   int cheat_input_len = strlen(cheat_input);
1366   int i;
1367
1368   if (letter == 0)
1369     return;
1370
1371   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1372   {
1373     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1374       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1375
1376     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1377   }
1378
1379   cheat_input[cheat_input_len++] = letter;
1380   cheat_input[cheat_input_len] = '\0';
1381
1382 #if DEBUG_EVENTS_KEY
1383   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1384 #endif
1385
1386   if (game_status == GAME_MODE_MAIN)
1387   {
1388     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1389         is_string_suffix(cheat_input, ":ist"))
1390     {
1391       InsertSolutionTape();
1392     }
1393     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1394              is_string_suffix(cheat_input, ":rg"))
1395     {
1396       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1397       DrawMainMenu();
1398     }
1399     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1400              is_string_suffix(cheat_input, ":rs"))
1401     {
1402       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1403       DrawMainMenu();
1404     }
1405     else if (is_string_suffix(cheat_input, ":reload-music") ||
1406              is_string_suffix(cheat_input, ":rm"))
1407     {
1408       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1409       DrawMainMenu();
1410     }
1411     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1412              is_string_suffix(cheat_input, ":ra"))
1413     {
1414       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1415                           1 << ARTWORK_TYPE_SOUNDS |
1416                           1 << ARTWORK_TYPE_MUSIC);
1417       DrawMainMenu();
1418     }
1419     else if (is_string_suffix(cheat_input, ":dump-level") ||
1420              is_string_suffix(cheat_input, ":dl"))
1421     {
1422       DumpLevel(&level);
1423     }
1424     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1425              is_string_suffix(cheat_input, ":dt"))
1426     {
1427       DumpTape(&tape);
1428     }
1429     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1430              is_string_suffix(cheat_input, ":ft"))
1431     {
1432       /* fix single-player tapes that contain player input for more than one
1433          player (due to a bug in 3.3.1.2 and earlier versions), which results
1434          in playing levels with more than one player in multi-player mode,
1435          even though the tape was originally recorded in single-player mode */
1436
1437       /* remove player input actions for all players but the first one */
1438       for (i = 1; i < MAX_PLAYERS; i++)
1439         tape.player_participates[i] = FALSE;
1440
1441       tape.changed = TRUE;
1442     }
1443     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1444              is_string_suffix(cheat_input, ":snl"))
1445     {
1446       SaveNativeLevel(&level);
1447     }
1448     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1449              is_string_suffix(cheat_input, ":fps"))
1450     {
1451       global.show_frames_per_second = !global.show_frames_per_second;
1452     }
1453   }
1454   else if (game_status == GAME_MODE_PLAYING)
1455   {
1456 #ifdef DEBUG
1457     if (is_string_suffix(cheat_input, ".q"))
1458       DEBUG_SetMaximumDynamite();
1459 #endif
1460   }
1461   else if (game_status == GAME_MODE_EDITOR)
1462   {
1463     if (is_string_suffix(cheat_input, ":dump-brush") ||
1464         is_string_suffix(cheat_input, ":DB"))
1465     {
1466       DumpBrush();
1467     }
1468     else if (is_string_suffix(cheat_input, ":DDB"))
1469     {
1470       DumpBrush_Small();
1471     }
1472   }
1473 }
1474
1475 void HandleKeysDebug(Key key)
1476 {
1477 #ifdef DEBUG
1478   int i;
1479
1480   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1481   {
1482     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1483
1484     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1485     {
1486       if (key == setup.debug.frame_delay_key[i] &&
1487           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1488       {
1489         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1490                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1491
1492         if (!setup.debug.frame_delay_game_only)
1493           MenuFrameDelay = GameFrameDelay;
1494
1495         SetVideoFrameDelay(GameFrameDelay);
1496
1497         if (GameFrameDelay > ONE_SECOND_DELAY)
1498           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1499         else if (GameFrameDelay != 0)
1500           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1501                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1502                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1503         else
1504           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1505
1506         break;
1507       }
1508     }
1509   }
1510
1511   if (game_status == GAME_MODE_PLAYING)
1512   {
1513     if (key == KSYM_d)
1514     {
1515       options.debug = !options.debug;
1516
1517       Error(ERR_DEBUG, "debug mode %s",
1518             (options.debug ? "enabled" : "disabled"));
1519     }
1520     else if (key == KSYM_v)
1521     {
1522       Error(ERR_DEBUG, "currently using game engine version %d",
1523             game.engine_version);
1524     }
1525   }
1526 #endif
1527 }
1528
1529 void HandleKey(Key key, int key_status)
1530 {
1531   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1532   static boolean ignore_repeated_key = FALSE;
1533   static struct SetupKeyboardInfo ski;
1534   static struct SetupShortcutInfo ssi;
1535   static struct
1536   {
1537     Key *key_custom;
1538     Key *key_snap;
1539     Key key_default;
1540     byte action;
1541   } key_info[] =
1542   {
1543     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1544     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1545     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1546     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1547     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1548     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1549   };
1550   int joy = 0;
1551   int i;
1552
1553 #if defined(TARGET_SDL2)
1554   /* map special keys (media keys / remote control buttons) to default keys */
1555   if (key == KSYM_PlayPause)
1556     key = KSYM_space;
1557   else if (key == KSYM_Select)
1558     key = KSYM_Return;
1559 #endif
1560
1561   HandleSpecialGameControllerKeys(key, key_status);
1562
1563   if (game_status == GAME_MODE_PLAYING)
1564   {
1565     /* only needed for single-step tape recording mode */
1566     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1567     int pnr;
1568
1569     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1570     {
1571       byte key_action = 0;
1572
1573       if (setup.input[pnr].use_joystick)
1574         continue;
1575
1576       ski = setup.input[pnr].key;
1577
1578       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1579         if (key == *key_info[i].key_custom)
1580           key_action |= key_info[i].action;
1581
1582       /* use combined snap+direction keys for the first player only */
1583       if (pnr == 0)
1584       {
1585         ssi = setup.shortcut;
1586
1587         for (i = 0; i < NUM_DIRECTIONS; i++)
1588           if (key == *key_info[i].key_snap)
1589             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1590       }
1591
1592       if (key_status == KEY_PRESSED)
1593         stored_player[pnr].action |= key_action;
1594       else
1595         stored_player[pnr].action &= ~key_action;
1596
1597       if (tape.single_step && tape.recording && tape.pausing)
1598       {
1599         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1600         {
1601           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1602
1603           /* if snap key already pressed, keep pause mode when releasing */
1604           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1605             has_snapped[pnr] = TRUE;
1606         }
1607         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1608         {
1609           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1610
1611           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1612               getRedDiskReleaseFlag_SP() == 0)
1613           {
1614             /* add a single inactive frame before dropping starts */
1615             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1616             stored_player[pnr].force_dropping = TRUE;
1617           }
1618         }
1619         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1620         {
1621           /* if snap key was pressed without direction, leave pause mode */
1622           if (!has_snapped[pnr])
1623             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1624
1625           has_snapped[pnr] = FALSE;
1626         }
1627       }
1628       else if (tape.recording && tape.pausing)
1629       {
1630         /* prevent key release events from un-pausing a paused game */
1631         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1632           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1633       }
1634     }
1635   }
1636   else
1637   {
1638     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1639       if (key == key_info[i].key_default)
1640         joy |= key_info[i].action;
1641   }
1642
1643   if (joy)
1644   {
1645     if (key_status == KEY_PRESSED)
1646       key_joystick_mapping |= joy;
1647     else
1648       key_joystick_mapping &= ~joy;
1649
1650     HandleJoystick();
1651   }
1652
1653   if (game_status != GAME_MODE_PLAYING)
1654     key_joystick_mapping = 0;
1655
1656   if (key_status == KEY_RELEASED)
1657   {
1658     // reset flag to ignore repeated "key pressed" events after key release
1659     ignore_repeated_key = FALSE;
1660
1661     return;
1662   }
1663
1664   if ((key == KSYM_F11 ||
1665        ((key == KSYM_Return ||
1666          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1667       video.fullscreen_available &&
1668       !ignore_repeated_key)
1669   {
1670     setup.fullscreen = !setup.fullscreen;
1671
1672     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1673
1674     if (game_status == GAME_MODE_SETUP)
1675       RedrawSetupScreenAfterFullscreenToggle();
1676
1677     // set flag to ignore repeated "key pressed" events
1678     ignore_repeated_key = TRUE;
1679
1680     return;
1681   }
1682
1683   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1684        key == KSYM_minus || key == KSYM_KP_Subtract ||
1685        key == KSYM_plus  || key == KSYM_KP_Add ||
1686        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1687       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1688       video.window_scaling_available &&
1689       !video.fullscreen_enabled)
1690   {
1691     if (key == KSYM_0 || key == KSYM_KP_0)
1692       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1693     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1694       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1695     else
1696       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1697
1698     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1699       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1700     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1701       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1702
1703     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1704
1705     if (game_status == GAME_MODE_SETUP)
1706       RedrawSetupScreenAfterFullscreenToggle();
1707
1708     return;
1709   }
1710
1711   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1712                                       key == KSYM_Return ||
1713                                       key == KSYM_Escape)))
1714   {
1715     /* do not handle this key event anymore */
1716     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1717       return;
1718   }
1719
1720   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1721       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1722   {
1723     GameEnd();
1724
1725     return;
1726   }
1727
1728   if (game_status == GAME_MODE_MAIN &&
1729       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1730   {
1731     StartGameActions(options.network, setup.autorecord, level.random_seed);
1732
1733     return;
1734   }
1735
1736   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1737   {
1738     if (key == setup.shortcut.save_game)
1739       TapeQuickSave();
1740     else if (key == setup.shortcut.load_game)
1741       TapeQuickLoad();
1742     else if (key == setup.shortcut.toggle_pause)
1743       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1744
1745     HandleTapeButtonKeys(key);
1746     HandleSoundButtonKeys(key);
1747   }
1748
1749   if (game_status == GAME_MODE_PLAYING && !network_playing)
1750   {
1751     int centered_player_nr_next = -999;
1752
1753     if (key == setup.shortcut.focus_player_all)
1754       centered_player_nr_next = -1;
1755     else
1756       for (i = 0; i < MAX_PLAYERS; i++)
1757         if (key == setup.shortcut.focus_player[i])
1758           centered_player_nr_next = i;
1759
1760     if (centered_player_nr_next != -999)
1761     {
1762       game.centered_player_nr_next = centered_player_nr_next;
1763       game.set_centered_player = TRUE;
1764
1765       if (tape.recording)
1766       {
1767         tape.centered_player_nr_next = game.centered_player_nr_next;
1768         tape.set_centered_player = TRUE;
1769       }
1770     }
1771   }
1772
1773   HandleKeysSpecial(key);
1774
1775   if (HandleGadgetsKeyInput(key))
1776   {
1777     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1778       key = KSYM_UNDEFINED;
1779   }
1780
1781   switch (game_status)
1782   {
1783     case GAME_MODE_PSEUDO_TYPENAME:
1784       HandleTypeName(0, key);
1785       break;
1786
1787     case GAME_MODE_TITLE:
1788     case GAME_MODE_MAIN:
1789     case GAME_MODE_LEVELS:
1790     case GAME_MODE_LEVELNR:
1791     case GAME_MODE_SETUP:
1792     case GAME_MODE_INFO:
1793     case GAME_MODE_SCORES:
1794       switch (key)
1795       {
1796         case KSYM_space:
1797         case KSYM_Return:
1798           if (game_status == GAME_MODE_TITLE)
1799             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1800           else if (game_status == GAME_MODE_MAIN)
1801             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1802           else if (game_status == GAME_MODE_LEVELS)
1803             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1804           else if (game_status == GAME_MODE_LEVELNR)
1805             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1806           else if (game_status == GAME_MODE_SETUP)
1807             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1808           else if (game_status == GAME_MODE_INFO)
1809             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1810           else if (game_status == GAME_MODE_SCORES)
1811             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1812           break;
1813
1814         case KSYM_Escape:
1815           if (game_status != GAME_MODE_MAIN)
1816             FadeSkipNextFadeIn();
1817
1818           if (game_status == GAME_MODE_TITLE)
1819             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1820           else if (game_status == GAME_MODE_LEVELS)
1821             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1822           else if (game_status == GAME_MODE_LEVELNR)
1823             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1824           else if (game_status == GAME_MODE_SETUP)
1825             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1826           else if (game_status == GAME_MODE_INFO)
1827             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1828           else if (game_status == GAME_MODE_SCORES)
1829             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1830           break;
1831
1832         case KSYM_Page_Up:
1833           if (game_status == GAME_MODE_LEVELS)
1834             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1835           else if (game_status == GAME_MODE_LEVELNR)
1836             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1837           else if (game_status == GAME_MODE_SETUP)
1838             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1839           else if (game_status == GAME_MODE_INFO)
1840             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1841           else if (game_status == GAME_MODE_SCORES)
1842             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1843           break;
1844
1845         case KSYM_Page_Down:
1846           if (game_status == GAME_MODE_LEVELS)
1847             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1848           else if (game_status == GAME_MODE_LEVELNR)
1849             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1850           else if (game_status == GAME_MODE_SETUP)
1851             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1852           else if (game_status == GAME_MODE_INFO)
1853             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1854           else if (game_status == GAME_MODE_SCORES)
1855             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1856           break;
1857
1858         default:
1859           break;
1860       }
1861       break;
1862
1863     case GAME_MODE_EDITOR:
1864       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1865         HandleLevelEditorKeyInput(key);
1866       break;
1867
1868     case GAME_MODE_PLAYING:
1869     {
1870       switch (key)
1871       {
1872         case KSYM_Escape:
1873           RequestQuitGame(setup.ask_on_escape);
1874           break;
1875
1876         default:
1877           break;
1878       }
1879       break;
1880     }
1881
1882     default:
1883       if (key == KSYM_Escape)
1884       {
1885         SetGameStatus(GAME_MODE_MAIN);
1886
1887         DrawMainMenu();
1888
1889         return;
1890       }
1891   }
1892
1893   HandleKeysDebug(key);
1894 }
1895
1896 void HandleNoEvent()
1897 {
1898   // if (button_status && game_status != GAME_MODE_PLAYING)
1899   if (button_status && (game_status != GAME_MODE_PLAYING ||
1900                         tape.pausing ||
1901                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
1902   {
1903     HandleButton(0, 0, button_status, -button_status);
1904   }
1905   else
1906   {
1907     HandleJoystick();
1908   }
1909
1910 #if defined(NETWORK_AVALIABLE)
1911   if (options.network)
1912     HandleNetworking();
1913 #endif
1914
1915   switch (game_status)
1916   {
1917     case GAME_MODE_MAIN:
1918       DrawPreviewLevelAnimation();
1919       break;
1920
1921     case GAME_MODE_EDITOR:
1922       HandleLevelEditorIdle();
1923       break;
1924
1925 #if defined(TARGET_SDL2)
1926     case GAME_MODE_PLAYING:
1927       HandleFollowFinger(-1, -1, -1);
1928       break;
1929 #endif
1930
1931     default:
1932       break;
1933   }
1934 }
1935
1936 static int HandleJoystickForAllPlayers()
1937 {
1938   int i;
1939   int result = 0;
1940   boolean no_joysticks_configured = TRUE;
1941   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1942   static byte joy_action_last[MAX_PLAYERS];
1943
1944   for (i = 0; i < MAX_PLAYERS; i++)
1945     if (setup.input[i].use_joystick)
1946       no_joysticks_configured = FALSE;
1947
1948   /* if no joysticks configured, map connected joysticks to players */
1949   if (no_joysticks_configured)
1950     use_as_joystick_nr = TRUE;
1951
1952   for (i = 0; i < MAX_PLAYERS; i++)
1953   {
1954     byte joy_action = 0;
1955
1956     joy_action = JoystickExt(i, use_as_joystick_nr);
1957     result |= joy_action;
1958
1959     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
1960         joy_action != joy_action_last[i])
1961       stored_player[i].action = joy_action;
1962
1963     joy_action_last[i] = joy_action;
1964   }
1965
1966   return result;
1967 }
1968
1969 void HandleJoystick()
1970 {
1971   int joystick  = HandleJoystickForAllPlayers();
1972   int keyboard  = key_joystick_mapping;
1973   int joy       = (joystick | keyboard);
1974   int left      = joy & JOY_LEFT;
1975   int right     = joy & JOY_RIGHT;
1976   int up        = joy & JOY_UP;
1977   int down      = joy & JOY_DOWN;
1978   int button    = joy & JOY_BUTTON;
1979   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
1980   int dx        = (left ? -1    : right ? 1     : 0);
1981   int dy        = (up   ? -1    : down  ? 1     : 0);
1982
1983   if (HandleGlobalAnimClicks(-1, -1, newbutton))
1984   {
1985     /* do not handle this button event anymore */
1986     return;
1987   }
1988
1989   switch (game_status)
1990   {
1991     case GAME_MODE_TITLE:
1992     case GAME_MODE_MAIN:
1993     case GAME_MODE_LEVELS:
1994     case GAME_MODE_LEVELNR:
1995     case GAME_MODE_SETUP:
1996     case GAME_MODE_INFO:
1997     {
1998       static unsigned int joystickmove_delay = 0;
1999       static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2000       static int joystick_last = 0;
2001
2002       if (joystick && !button &&
2003           !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2004       {
2005         /* delay joystick actions if buttons/axes continually pressed */
2006         newbutton = dx = dy = 0;
2007       }
2008       else
2009       {
2010         /* start with longer delay, then continue with shorter delay */
2011         if (joystick != joystick_last)
2012           joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2013         else
2014           joystickmove_delay_value = GADGET_FRAME_DELAY;
2015       }
2016
2017       if (game_status == GAME_MODE_TITLE)
2018         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2019       else if (game_status == GAME_MODE_MAIN)
2020         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2021       else if (game_status == GAME_MODE_LEVELS)
2022         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2023       else if (game_status == GAME_MODE_LEVELNR)
2024         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2025       else if (game_status == GAME_MODE_SETUP)
2026         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2027       else if (game_status == GAME_MODE_INFO)
2028         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2029
2030       joystick_last = joystick;
2031
2032       break;
2033     }
2034
2035     case GAME_MODE_SCORES:
2036       HandleHallOfFame(0, 0, dx, dy, !newbutton);
2037       break;
2038
2039     case GAME_MODE_PLAYING:
2040       if (tape.playing || keyboard)
2041         newbutton = ((joy & JOY_BUTTON) != 0);
2042
2043       if (newbutton && AllPlayersGone)
2044       {
2045         GameEnd();
2046
2047         return;
2048       }
2049
2050       if (tape.recording && tape.pausing)
2051       {
2052         if (joystick & JOY_ACTION)
2053           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2054       }
2055
2056       break;
2057
2058     default:
2059       break;
2060   }
2061 }
2062
2063 void HandleSpecialGameControllerButtons(Event *event)
2064 {
2065 #if defined(TARGET_SDL2)
2066   switch (event->type)
2067   {
2068     case SDL_CONTROLLERBUTTONDOWN:
2069       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2070         HandleKey(KSYM_space, KEY_PRESSED);
2071       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2072         HandleKey(KSYM_Escape, KEY_PRESSED);
2073
2074       break;
2075
2076     case SDL_CONTROLLERBUTTONUP:
2077       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2078         HandleKey(KSYM_space, KEY_RELEASED);
2079       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2080         HandleKey(KSYM_Escape, KEY_RELEASED);
2081
2082       break;
2083   }
2084 #endif
2085 }
2086
2087 void HandleSpecialGameControllerKeys(Key key, int key_status)
2088 {
2089 #if defined(TARGET_SDL2)
2090 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2091   int button = SDL_CONTROLLER_BUTTON_INVALID;
2092
2093   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2094   if (key == KSYM_Rewind)
2095     button = SDL_CONTROLLER_BUTTON_A;
2096   else if (key == KSYM_FastForward || key == KSYM_Menu)
2097     button = SDL_CONTROLLER_BUTTON_B;
2098
2099   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2100   {
2101     Event event;
2102
2103     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2104                   SDL_CONTROLLERBUTTONUP);
2105
2106     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2107     event.cbutton.button = button;
2108     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2109                            SDL_RELEASED);
2110
2111     HandleJoystickEvent(&event);
2112   }
2113 #endif
2114 #endif
2115 }