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