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