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