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