6f6d41faa580f6ddf393613752fcb6434c91e725
[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     /* initialize unifying snap and drop buttons (EM engine) */
1564     game_em.use_single_button = game_em.use_single_button_initial;
1565
1566     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1567     {
1568       byte key_action = 0;
1569
1570       if (setup.input[pnr].use_joystick)
1571         continue;
1572
1573       ski = setup.input[pnr].key;
1574
1575       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1576         if (key == *key_info[i].key_custom)
1577           key_action |= key_info[i].action;
1578
1579       /* use combined snap+direction keys for the first player only */
1580       if (pnr == 0)
1581       {
1582         ssi = setup.shortcut;
1583
1584         for (i = 0; i < NUM_DIRECTIONS; i++)
1585           if (key == *key_info[i].key_snap)
1586             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1587       }
1588
1589       /* clear delayed snap and drop actions in single step mode (see below) */
1590       if (tape.single_step)
1591       {
1592         if (clear_snap_button[pnr])
1593         {
1594           stored_player[pnr].action &= ~KEY_BUTTON_SNAP;
1595           clear_snap_button[pnr] = FALSE;
1596         }
1597
1598         if (clear_drop_button[pnr])
1599         {
1600           stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1601           clear_drop_button[pnr] = FALSE;
1602         }
1603       }
1604
1605       if (key_status == KEY_PRESSED)
1606         stored_player[pnr].action |= key_action;
1607       else
1608         stored_player[pnr].action &= ~key_action;
1609
1610       if (tape.single_step && tape.recording && tape.pausing)
1611       {
1612         /* do not unify snap and drop buttons in single-step mode (EM engine) */
1613         game_em.use_single_button = FALSE;
1614
1615         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1616         {
1617           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1618
1619           /* if snap key already pressed, don't snap when releasing (below) */
1620           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1621             element_snapped[pnr] = TRUE;
1622
1623           /* if drop key already pressed, don't drop when releasing (below) */
1624           if (stored_player[pnr].action & KEY_BUTTON_DROP)
1625             element_dropped[pnr] = TRUE;
1626         }
1627         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1628         {
1629           if (level.game_engine_type == GAME_ENGINE_TYPE_EM ||
1630               level.game_engine_type == GAME_ENGINE_TYPE_SP)
1631           {
1632
1633             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1634                 getRedDiskReleaseFlag_SP() == 0)
1635               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1636
1637             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1638           }
1639         }
1640         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON)
1641         {
1642           if (key_action & KEY_BUTTON_SNAP)
1643           {
1644             /* if snap key was released without moving (see above), snap now */
1645             if (!element_snapped[pnr])
1646             {
1647               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1648
1649               stored_player[pnr].action |= KEY_BUTTON_SNAP;
1650
1651               /* clear delayed snap button on next event */
1652               clear_snap_button[pnr] = TRUE;
1653             }
1654
1655             element_snapped[pnr] = FALSE;
1656           }
1657
1658           if (key_action & KEY_BUTTON_DROP &&
1659               level.game_engine_type == GAME_ENGINE_TYPE_RND)
1660           {
1661             /* if drop key was released without moving (see above), drop now */
1662             if (!element_dropped[pnr])
1663             {
1664               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1665
1666               if (level.game_engine_type != GAME_ENGINE_TYPE_SP ||
1667                   getRedDiskReleaseFlag_SP() != 0)
1668                 stored_player[pnr].action |= KEY_BUTTON_DROP;
1669
1670               /* clear delayed drop button on next event */
1671               clear_drop_button[pnr] = TRUE;
1672             }
1673
1674             element_dropped[pnr] = FALSE;
1675           }
1676         }
1677       }
1678       else if (tape.recording && tape.pausing)
1679       {
1680         /* prevent key release events from un-pausing a paused game */
1681         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1682           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1683       }
1684     }
1685   }
1686   else
1687   {
1688     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1689       if (key == key_info[i].key_default)
1690         joy |= key_info[i].action;
1691   }
1692
1693   if (joy)
1694   {
1695     if (key_status == KEY_PRESSED)
1696       key_joystick_mapping |= joy;
1697     else
1698       key_joystick_mapping &= ~joy;
1699
1700     HandleJoystick();
1701   }
1702
1703   if (game_status != GAME_MODE_PLAYING)
1704     key_joystick_mapping = 0;
1705
1706   if (key_status == KEY_RELEASED)
1707   {
1708     // reset flag to ignore repeated "key pressed" events after key release
1709     ignore_repeated_key = FALSE;
1710
1711     return;
1712   }
1713
1714   if ((key == KSYM_F11 ||
1715        ((key == KSYM_Return ||
1716          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1717       video.fullscreen_available &&
1718       !ignore_repeated_key)
1719   {
1720     setup.fullscreen = !setup.fullscreen;
1721
1722     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1723
1724     if (game_status == GAME_MODE_SETUP)
1725       RedrawSetupScreenAfterFullscreenToggle();
1726
1727     // set flag to ignore repeated "key pressed" events
1728     ignore_repeated_key = TRUE;
1729
1730     return;
1731   }
1732
1733   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1734        key == KSYM_minus || key == KSYM_KP_Subtract ||
1735        key == KSYM_plus  || key == KSYM_KP_Add ||
1736        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1737       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1738       video.window_scaling_available &&
1739       !video.fullscreen_enabled)
1740   {
1741     if (key == KSYM_0 || key == KSYM_KP_0)
1742       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1743     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1744       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1745     else
1746       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1747
1748     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1749       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1750     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1751       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1752
1753     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1754
1755     if (game_status == GAME_MODE_SETUP)
1756       RedrawSetupScreenAfterFullscreenToggle();
1757
1758     return;
1759   }
1760
1761   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1762                                       key == KSYM_Return ||
1763                                       key == KSYM_Escape)))
1764   {
1765     /* do not handle this key event anymore */
1766     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1767       return;
1768   }
1769
1770   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1771       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1772   {
1773     GameEnd();
1774
1775     return;
1776   }
1777
1778   if (game_status == GAME_MODE_MAIN &&
1779       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1780   {
1781     StartGameActions(options.network, setup.autorecord, level.random_seed);
1782
1783     return;
1784   }
1785
1786   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1787   {
1788     if (key == setup.shortcut.save_game)
1789       TapeQuickSave();
1790     else if (key == setup.shortcut.load_game)
1791       TapeQuickLoad();
1792     else if (key == setup.shortcut.toggle_pause)
1793       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1794
1795     HandleTapeButtonKeys(key);
1796     HandleSoundButtonKeys(key);
1797   }
1798
1799   if (game_status == GAME_MODE_PLAYING && !network_playing)
1800   {
1801     int centered_player_nr_next = -999;
1802
1803     if (key == setup.shortcut.focus_player_all)
1804       centered_player_nr_next = -1;
1805     else
1806       for (i = 0; i < MAX_PLAYERS; i++)
1807         if (key == setup.shortcut.focus_player[i])
1808           centered_player_nr_next = i;
1809
1810     if (centered_player_nr_next != -999)
1811     {
1812       game.centered_player_nr_next = centered_player_nr_next;
1813       game.set_centered_player = TRUE;
1814
1815       if (tape.recording)
1816       {
1817         tape.centered_player_nr_next = game.centered_player_nr_next;
1818         tape.set_centered_player = TRUE;
1819       }
1820     }
1821   }
1822
1823   HandleKeysSpecial(key);
1824
1825   if (HandleGadgetsKeyInput(key))
1826   {
1827     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1828       key = KSYM_UNDEFINED;
1829   }
1830
1831   switch (game_status)
1832   {
1833     case GAME_MODE_PSEUDO_TYPENAME:
1834       HandleTypeName(0, key);
1835       break;
1836
1837     case GAME_MODE_TITLE:
1838     case GAME_MODE_MAIN:
1839     case GAME_MODE_LEVELS:
1840     case GAME_MODE_LEVELNR:
1841     case GAME_MODE_SETUP:
1842     case GAME_MODE_INFO:
1843     case GAME_MODE_SCORES:
1844       switch (key)
1845       {
1846         case KSYM_space:
1847         case KSYM_Return:
1848           if (game_status == GAME_MODE_TITLE)
1849             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1850           else if (game_status == GAME_MODE_MAIN)
1851             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1852           else if (game_status == GAME_MODE_LEVELS)
1853             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1854           else if (game_status == GAME_MODE_LEVELNR)
1855             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1856           else if (game_status == GAME_MODE_SETUP)
1857             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1858           else if (game_status == GAME_MODE_INFO)
1859             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1860           else if (game_status == GAME_MODE_SCORES)
1861             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1862           break;
1863
1864         case KSYM_Escape:
1865           if (game_status != GAME_MODE_MAIN)
1866             FadeSkipNextFadeIn();
1867
1868           if (game_status == GAME_MODE_TITLE)
1869             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1870           else if (game_status == GAME_MODE_LEVELS)
1871             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1872           else if (game_status == GAME_MODE_LEVELNR)
1873             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1874           else if (game_status == GAME_MODE_SETUP)
1875             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1876           else if (game_status == GAME_MODE_INFO)
1877             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1878           else if (game_status == GAME_MODE_SCORES)
1879             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1880           break;
1881
1882         case KSYM_Page_Up:
1883           if (game_status == GAME_MODE_LEVELS)
1884             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1885           else if (game_status == GAME_MODE_LEVELNR)
1886             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1887           else if (game_status == GAME_MODE_SETUP)
1888             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1889           else if (game_status == GAME_MODE_INFO)
1890             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1891           else if (game_status == GAME_MODE_SCORES)
1892             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1893           break;
1894
1895         case KSYM_Page_Down:
1896           if (game_status == GAME_MODE_LEVELS)
1897             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1898           else if (game_status == GAME_MODE_LEVELNR)
1899             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1900           else if (game_status == GAME_MODE_SETUP)
1901             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1902           else if (game_status == GAME_MODE_INFO)
1903             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1904           else if (game_status == GAME_MODE_SCORES)
1905             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1906           break;
1907
1908         default:
1909           break;
1910       }
1911       break;
1912
1913     case GAME_MODE_EDITOR:
1914       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1915         HandleLevelEditorKeyInput(key);
1916       break;
1917
1918     case GAME_MODE_PLAYING:
1919     {
1920       switch (key)
1921       {
1922         case KSYM_Escape:
1923           RequestQuitGame(setup.ask_on_escape);
1924           break;
1925
1926         default:
1927           break;
1928       }
1929       break;
1930     }
1931
1932     default:
1933       if (key == KSYM_Escape)
1934       {
1935         SetGameStatus(GAME_MODE_MAIN);
1936
1937         DrawMainMenu();
1938
1939         return;
1940       }
1941   }
1942
1943   HandleKeysDebug(key);
1944 }
1945
1946 void HandleNoEvent()
1947 {
1948   // if (button_status && game_status != GAME_MODE_PLAYING)
1949   if (button_status && (game_status != GAME_MODE_PLAYING || tape.pausing))
1950   {
1951     HandleButton(0, 0, button_status, -button_status);
1952   }
1953   else
1954   {
1955     HandleJoystick();
1956   }
1957
1958 #if defined(NETWORK_AVALIABLE)
1959   if (options.network)
1960     HandleNetworking();
1961 #endif
1962
1963   switch (game_status)
1964   {
1965     case GAME_MODE_MAIN:
1966       DrawPreviewLevelAnimation();
1967       break;
1968
1969     case GAME_MODE_EDITOR:
1970       HandleLevelEditorIdle();
1971       break;
1972
1973 #if defined(TARGET_SDL2)
1974     case GAME_MODE_PLAYING:
1975       HandleFollowFinger(-1, -1, -1);
1976       break;
1977 #endif
1978
1979     default:
1980       break;
1981   }
1982 }
1983
1984 static int HandleJoystickForAllPlayers()
1985 {
1986   int i;
1987   int result = 0;
1988   boolean no_joysticks_configured = TRUE;
1989   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1990   static byte joy_action_last[MAX_PLAYERS];
1991
1992   for (i = 0; i < MAX_PLAYERS; i++)
1993     if (setup.input[i].use_joystick)
1994       no_joysticks_configured = FALSE;
1995
1996   /* if no joysticks configured, map connected joysticks to players */
1997   if (no_joysticks_configured)
1998     use_as_joystick_nr = TRUE;
1999
2000   for (i = 0; i < MAX_PLAYERS; i++)
2001   {
2002     byte joy_action = 0;
2003
2004     joy_action = JoystickExt(i, use_as_joystick_nr);
2005     result |= joy_action;
2006
2007     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2008         joy_action != joy_action_last[i])
2009       stored_player[i].action = joy_action;
2010
2011     joy_action_last[i] = joy_action;
2012   }
2013
2014   return result;
2015 }
2016
2017 void HandleJoystick()
2018 {
2019   int joystick  = HandleJoystickForAllPlayers();
2020   int keyboard  = key_joystick_mapping;
2021   int joy       = (joystick | keyboard);
2022   int left      = joy & JOY_LEFT;
2023   int right     = joy & JOY_RIGHT;
2024   int up        = joy & JOY_UP;
2025   int down      = joy & JOY_DOWN;
2026   int button    = joy & JOY_BUTTON;
2027   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2028   int dx        = (left ? -1    : right ? 1     : 0);
2029   int dy        = (up   ? -1    : down  ? 1     : 0);
2030
2031   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2032   {
2033     /* do not handle this button event anymore */
2034     return;
2035   }
2036
2037   switch (game_status)
2038   {
2039     case GAME_MODE_TITLE:
2040     case GAME_MODE_MAIN:
2041     case GAME_MODE_LEVELS:
2042     case GAME_MODE_LEVELNR:
2043     case GAME_MODE_SETUP:
2044     case GAME_MODE_INFO:
2045     {
2046       static unsigned int joystickmove_delay = 0;
2047       static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2048       static int joystick_last = 0;
2049
2050       if (joystick && !button &&
2051           !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2052       {
2053         /* delay joystick actions if buttons/axes continually pressed */
2054         newbutton = dx = dy = 0;
2055       }
2056       else
2057       {
2058         /* start with longer delay, then continue with shorter delay */
2059         if (joystick != joystick_last)
2060           joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2061         else
2062           joystickmove_delay_value = GADGET_FRAME_DELAY;
2063       }
2064
2065       if (game_status == GAME_MODE_TITLE)
2066         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2067       else if (game_status == GAME_MODE_MAIN)
2068         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2069       else if (game_status == GAME_MODE_LEVELS)
2070         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2071       else if (game_status == GAME_MODE_LEVELNR)
2072         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2073       else if (game_status == GAME_MODE_SETUP)
2074         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2075       else if (game_status == GAME_MODE_INFO)
2076         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2077
2078       joystick_last = joystick;
2079
2080       break;
2081     }
2082
2083     case GAME_MODE_SCORES:
2084       HandleHallOfFame(0, 0, dx, dy, !newbutton);
2085       break;
2086
2087     case GAME_MODE_PLAYING:
2088       if (tape.playing || keyboard)
2089         newbutton = ((joy & JOY_BUTTON) != 0);
2090
2091       if (newbutton && AllPlayersGone)
2092       {
2093         GameEnd();
2094
2095         return;
2096       }
2097
2098       if (tape.recording && tape.pausing)
2099       {
2100         if (joystick & JOY_ACTION)
2101           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2102       }
2103
2104       break;
2105
2106     default:
2107       break;
2108   }
2109 }
2110
2111 void HandleSpecialGameControllerButtons(Event *event)
2112 {
2113 #if defined(TARGET_SDL2)
2114   switch (event->type)
2115   {
2116     case SDL_CONTROLLERBUTTONDOWN:
2117       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2118         HandleKey(KSYM_space, KEY_PRESSED);
2119       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2120         HandleKey(KSYM_Escape, KEY_PRESSED);
2121
2122       break;
2123
2124     case SDL_CONTROLLERBUTTONUP:
2125       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2126         HandleKey(KSYM_space, KEY_RELEASED);
2127       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2128         HandleKey(KSYM_Escape, KEY_RELEASED);
2129
2130       break;
2131   }
2132 #endif
2133 }
2134
2135 void HandleSpecialGameControllerKeys(Key key, int key_status)
2136 {
2137 #if defined(TARGET_SDL2)
2138 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2139   int button = SDL_CONTROLLER_BUTTON_INVALID;
2140
2141   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2142   if (key == KSYM_Rewind)
2143     button = SDL_CONTROLLER_BUTTON_A;
2144   else if (key == KSYM_FastForward || key == KSYM_Menu)
2145     button = SDL_CONTROLLER_BUTTON_B;
2146
2147   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2148   {
2149     Event event;
2150
2151     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2152                   SDL_CONTROLLERBUTTONUP);
2153
2154     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2155     event.cbutton.button = button;
2156     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2157                            SDL_RELEASED);
2158
2159     HandleJoystickEvent(&event);
2160   }
2161 #endif
2162 #endif
2163 }