moved function for processing 'follow finger' controls if no events pending
[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_FOLLOW_FINGER))
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   // use touch direction control
828
829   if (event->type == EVENT_FINGERPRESS)
830   {
831     if (event_x > 1.0 / 3.0)
832     {
833       // motion area
834
835       motion_id = event->fingerId;
836
837       motion_x1 = event_x;
838       motion_y1 = event_y;
839
840       motion_key_x = KSYM_UNDEFINED;
841       motion_key_y = KSYM_UNDEFINED;
842
843       Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
844     }
845     else
846     {
847       // button area
848
849       button_id = event->fingerId;
850
851       button_x1 = event_x;
852       button_y1 = event_y;
853
854       button_key = setup.input[0].key.snap;
855
856       HandleKey(button_key, KEY_PRESSED);
857
858       Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
859     }
860   }
861   else if (event->type == EVENT_FINGERRELEASE)
862   {
863     if (event->fingerId == motion_id)
864     {
865       motion_id = -1;
866
867       if (motion_key_x != KSYM_UNDEFINED)
868         HandleKey(motion_key_x, KEY_RELEASED);
869       if (motion_key_y != KSYM_UNDEFINED)
870         HandleKey(motion_key_y, KEY_RELEASED);
871
872       motion_key_x = KSYM_UNDEFINED;
873       motion_key_y = KSYM_UNDEFINED;
874
875       Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
876     }
877     else if (event->fingerId == button_id)
878     {
879       button_id = -1;
880
881       if (button_key != KSYM_UNDEFINED)
882         HandleKey(button_key, KEY_RELEASED);
883
884       button_key = KSYM_UNDEFINED;
885
886       Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
887     }
888   }
889   else if (event->type == EVENT_FINGERMOTION)
890   {
891     if (event->fingerId == motion_id)
892     {
893       float distance_x = ABS(event_x - motion_x1);
894       float distance_y = ABS(event_y - motion_y1);
895       Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
896                               event_x > motion_x1 ? setup.input[0].key.right :
897                               KSYM_UNDEFINED);
898       Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
899                               event_y > motion_y1 ? setup.input[0].key.down :
900                               KSYM_UNDEFINED);
901
902       if (distance_x < move_trigger_distance / 2 ||
903           distance_x < distance_y)
904         new_motion_key_x = KSYM_UNDEFINED;
905
906       if (distance_y < move_trigger_distance / 2 ||
907           distance_y < distance_x)
908         new_motion_key_y = KSYM_UNDEFINED;
909
910       if (distance_x > move_trigger_distance ||
911           distance_y > move_trigger_distance)
912       {
913         if (new_motion_key_x != motion_key_x)
914         {
915           if (motion_key_x != KSYM_UNDEFINED)
916             HandleKey(motion_key_x, KEY_RELEASED);
917           if (new_motion_key_x != KSYM_UNDEFINED)
918             HandleKey(new_motion_key_x, KEY_PRESSED);
919         }
920
921         if (new_motion_key_y != motion_key_y)
922         {
923           if (motion_key_y != KSYM_UNDEFINED)
924             HandleKey(motion_key_y, KEY_RELEASED);
925           if (new_motion_key_y != KSYM_UNDEFINED)
926             HandleKey(new_motion_key_y, KEY_PRESSED);
927         }
928
929         motion_x1 = event_x;
930         motion_y1 = event_y;
931
932         motion_key_x = new_motion_key_x;
933         motion_key_y = new_motion_key_y;
934
935         Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
936       }
937     }
938     else if (event->fingerId == button_id)
939     {
940       float distance_x = ABS(event_x - button_x1);
941       float distance_y = ABS(event_y - button_y1);
942
943       if (distance_x < drop_trigger_distance / 2 &&
944           distance_y > drop_trigger_distance)
945       {
946         if (button_key == setup.input[0].key.snap)
947           HandleKey(button_key, KEY_RELEASED);
948
949         button_x1 = event_x;
950         button_y1 = event_y;
951
952         button_key = setup.input[0].key.drop;
953
954         HandleKey(button_key, KEY_PRESSED);
955
956         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
957       }
958     }
959   }
960 }
961
962 static void HandleFollowFinger(int mx, int my, int button)
963 {
964   static int old_mx = 0, old_my = 0;
965   static Key motion_key_x = KSYM_UNDEFINED;
966   static Key motion_key_y = KSYM_UNDEFINED;
967   static boolean started_on_player = FALSE;
968   static boolean player_is_dropping = FALSE;
969   static int player_drop_count = 0;
970   static int last_player_x = -1;
971   static int last_player_y = -1;
972
973   if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
974     return;
975
976   if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
977   {
978     touch_info[0].touched = TRUE;
979     touch_info[0].key = 0;
980
981     old_mx = mx;
982     old_my = my;
983
984     if (!motion_status)
985     {
986       started_on_player = FALSE;
987       player_is_dropping = FALSE;
988       player_drop_count = 0;
989       last_player_x = -1;
990       last_player_y = -1;
991
992       motion_key_x = KSYM_UNDEFINED;
993       motion_key_y = KSYM_UNDEFINED;
994
995       Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
996     }
997   }
998   else if (button == MB_RELEASED && touch_info[0].touched)
999   {
1000     touch_info[0].touched = FALSE;
1001     touch_info[0].key = 0;
1002
1003     old_mx = 0;
1004     old_my = 0;
1005
1006     if (motion_key_x != KSYM_UNDEFINED)
1007       HandleKey(motion_key_x, KEY_RELEASED);
1008     if (motion_key_y != KSYM_UNDEFINED)
1009       HandleKey(motion_key_y, KEY_RELEASED);
1010
1011     if (started_on_player)
1012     {
1013       if (player_is_dropping)
1014       {
1015         Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1016
1017         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1018       }
1019       else
1020       {
1021         Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1022
1023         HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1024       }
1025     }
1026
1027     motion_key_x = KSYM_UNDEFINED;
1028     motion_key_y = KSYM_UNDEFINED;
1029
1030     Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1031   }
1032
1033   if (touch_info[0].touched)
1034   {
1035     int src_x = local_player->jx;
1036     int src_y = local_player->jy;
1037     int dst_x = getLevelFromScreenX(old_mx);
1038     int dst_y = getLevelFromScreenY(old_my);
1039     int dx = dst_x - src_x;
1040     int dy = dst_y - src_y;
1041     Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1042                             dx > 0 ? setup.input[0].key.right :
1043                             KSYM_UNDEFINED);
1044     Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1045                             dy > 0 ? setup.input[0].key.down :
1046                             KSYM_UNDEFINED);
1047
1048     if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1049         (last_player_x != local_player->jx ||
1050          last_player_y != local_player->jy))
1051     {
1052       // in case of asymmetric diagonal movement, use "preferred" direction
1053
1054       int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1055
1056       if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1057         level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1058       else
1059         local_player->last_move_dir = last_move_dir;
1060
1061       // (required to prevent accidentally forcing direction for next movement)
1062       last_player_x = local_player->jx;
1063       last_player_y = local_player->jy;
1064     }
1065
1066     if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1067     {
1068       started_on_player = TRUE;
1069       player_drop_count = getPlayerInventorySize(0);
1070       player_is_dropping = (player_drop_count > 0);
1071
1072       if (player_is_dropping)
1073       {
1074         Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1075
1076         HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1077       }
1078       else
1079       {
1080         Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1081
1082         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1083       }
1084     }
1085     else if (dx != 0 || dy != 0)
1086     {
1087       if (player_is_dropping &&
1088           player_drop_count == getPlayerInventorySize(0))
1089       {
1090         Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1091
1092         HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1093         HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1094
1095         player_is_dropping = FALSE;
1096       }
1097     }
1098
1099     if (new_motion_key_x != motion_key_x)
1100     {
1101       Error(ERR_DEBUG, "---------- %s %s ----------",
1102             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1103             dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1104
1105       if (motion_key_x != KSYM_UNDEFINED)
1106         HandleKey(motion_key_x, KEY_RELEASED);
1107       if (new_motion_key_x != KSYM_UNDEFINED)
1108         HandleKey(new_motion_key_x, KEY_PRESSED);
1109     }
1110
1111     if (new_motion_key_y != motion_key_y)
1112     {
1113       Error(ERR_DEBUG, "---------- %s %s ----------",
1114             started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1115             dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1116
1117       if (motion_key_y != KSYM_UNDEFINED)
1118         HandleKey(motion_key_y, KEY_RELEASED);
1119       if (new_motion_key_y != KSYM_UNDEFINED)
1120         HandleKey(new_motion_key_y, KEY_PRESSED);
1121     }
1122
1123     motion_key_x = new_motion_key_x;
1124     motion_key_y = new_motion_key_y;
1125   }
1126 }
1127
1128 static boolean checkTextInputKeyModState()
1129 {
1130   // when playing, only handle raw key events and ignore text input
1131   if (game_status == GAME_MODE_PLAYING)
1132     return FALSE;
1133
1134   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1135 }
1136
1137 void HandleTextEvent(TextEvent *event)
1138 {
1139   char *text = event->text;
1140   Key key = getKeyFromKeyName(text);
1141
1142 #if DEBUG_EVENTS_TEXT
1143   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1144         text,
1145         strlen(text),
1146         text[0], (int)(text[0]),
1147         key,
1148         getKeyNameFromKey(key),
1149         GetKeyModState());
1150 #endif
1151
1152 #if !defined(HAS_SCREEN_KEYBOARD)
1153   // non-mobile devices: only handle key input with modifier keys pressed here
1154   // (every other key input is handled directly as physical key input event)
1155   if (!checkTextInputKeyModState())
1156     return;
1157 #endif
1158
1159   // process text input as "classic" (with uppercase etc.) key input event
1160   HandleKey(key, KEY_PRESSED);
1161   HandleKey(key, KEY_RELEASED);
1162 }
1163
1164 void HandlePauseResumeEvent(PauseResumeEvent *event)
1165 {
1166   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1167   {
1168     Mix_PauseMusic();
1169   }
1170   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1171   {
1172     Mix_ResumeMusic();
1173   }
1174 }
1175
1176 #endif
1177
1178 void HandleKeyEvent(KeyEvent *event)
1179 {
1180   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1181   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1182   Key key = GetEventKey(event, with_modifiers);
1183   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1184
1185 #if DEBUG_EVENTS_KEY
1186   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1187         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1188         event->keysym.scancode,
1189         event->keysym.sym,
1190         keymod,
1191         GetKeyModState(),
1192         key,
1193         getKeyNameFromKey(key));
1194 #endif
1195
1196 #if defined(PLATFORM_ANDROID)
1197   if (key == KSYM_Back)
1198   {
1199     // always map the "back" button to the "escape" key on Android devices
1200     key = KSYM_Escape;
1201   }
1202   else
1203   {
1204     // for any key event other than "back" button, disable overlay buttons
1205     SetOverlayEnabled(FALSE);
1206   }
1207 #endif
1208
1209   HandleKeyModState(keymod, key_status);
1210
1211 #if defined(TARGET_SDL2)
1212   // only handle raw key input without text modifier keys pressed
1213   if (!checkTextInputKeyModState())
1214     HandleKey(key, key_status);
1215 #else
1216   HandleKey(key, key_status);
1217 #endif
1218 }
1219
1220 void HandleFocusEvent(FocusChangeEvent *event)
1221 {
1222   static int old_joystick_status = -1;
1223
1224   if (event->type == EVENT_FOCUSOUT)
1225   {
1226     KeyboardAutoRepeatOn();
1227     old_joystick_status = joystick.status;
1228     joystick.status = JOYSTICK_NOT_AVAILABLE;
1229
1230     ClearPlayerAction();
1231   }
1232   else if (event->type == EVENT_FOCUSIN)
1233   {
1234     /* When there are two Rocks'n'Diamonds windows which overlap and
1235        the player moves the pointer from one game window to the other,
1236        a 'FocusOut' event is generated for the window the pointer is
1237        leaving and a 'FocusIn' event is generated for the window the
1238        pointer is entering. In some cases, it can happen that the
1239        'FocusIn' event is handled by the one game process before the
1240        'FocusOut' event by the other game process. In this case the
1241        X11 environment would end up with activated keyboard auto repeat,
1242        because unfortunately this is a global setting and not (which
1243        would be far better) set for each X11 window individually.
1244        The effect would be keyboard auto repeat while playing the game
1245        (game_status == GAME_MODE_PLAYING), which is not desired.
1246        To avoid this special case, we just wait 1/10 second before
1247        processing the 'FocusIn' event.
1248     */
1249
1250     if (game_status == GAME_MODE_PLAYING)
1251     {
1252       Delay(100);
1253       KeyboardAutoRepeatOffUnlessAutoplay();
1254     }
1255
1256     if (old_joystick_status != -1)
1257       joystick.status = old_joystick_status;
1258   }
1259 }
1260
1261 void HandleClientMessageEvent(ClientMessageEvent *event)
1262 {
1263   if (CheckCloseWindowEvent(event))
1264     CloseAllAndExit(0);
1265 }
1266
1267 void HandleWindowManagerEvent(Event *event)
1268 {
1269 #if defined(TARGET_SDL)
1270   SDLHandleWindowManagerEvent(event);
1271 #endif
1272 }
1273
1274 void HandleButton(int mx, int my, int button, int button_nr)
1275 {
1276   static int old_mx = 0, old_my = 0;
1277   boolean button_hold = FALSE;
1278
1279   if (button_nr < 0)
1280   {
1281     mx = old_mx;
1282     my = old_my;
1283     button_nr = -button_nr;
1284     button_hold = TRUE;
1285   }
1286   else
1287   {
1288     old_mx = mx;
1289     old_my = my;
1290   }
1291
1292 #if defined(PLATFORM_ANDROID)
1293   // when playing, only handle gadgets when using "follow finger" controls
1294   boolean handle_gadgets =
1295     (game_status != GAME_MODE_PLAYING ||
1296      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1297
1298   if (handle_gadgets &&
1299       HandleGadgets(mx, my, button))
1300   {
1301     /* do not handle this button event anymore */
1302     mx = my = -32;      /* force mouse event to be outside screen tiles */
1303   }
1304 #else
1305   if (HandleGadgets(mx, my, button))
1306   {
1307     /* do not handle this button event anymore */
1308     mx = my = -32;      /* force mouse event to be outside screen tiles */
1309   }
1310 #endif
1311
1312   if (HandleGlobalAnimClicks(mx, my, button))
1313   {
1314     /* do not handle this button event anymore */
1315     return;             /* force mouse event not to be handled at all */
1316   }
1317
1318   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1319     return;
1320
1321   /* do not use scroll wheel button events for anything other than gadgets */
1322   if (IS_WHEEL_BUTTON(button_nr))
1323     return;
1324
1325   switch (game_status)
1326   {
1327     case GAME_MODE_TITLE:
1328       HandleTitleScreen(mx, my, 0, 0, button);
1329       break;
1330
1331     case GAME_MODE_MAIN:
1332       HandleMainMenu(mx, my, 0, 0, button);
1333       break;
1334
1335     case GAME_MODE_PSEUDO_TYPENAME:
1336       HandleTypeName(0, KSYM_Return);
1337       break;
1338
1339     case GAME_MODE_LEVELS:
1340       HandleChooseLevelSet(mx, my, 0, 0, button);
1341       break;
1342
1343     case GAME_MODE_LEVELNR:
1344       HandleChooseLevelNr(mx, my, 0, 0, button);
1345       break;
1346
1347     case GAME_MODE_SCORES:
1348       HandleHallOfFame(0, 0, 0, 0, button);
1349       break;
1350
1351     case GAME_MODE_EDITOR:
1352       HandleLevelEditorIdle();
1353       break;
1354
1355     case GAME_MODE_INFO:
1356       HandleInfoScreen(mx, my, 0, 0, button);
1357       break;
1358
1359     case GAME_MODE_SETUP:
1360       HandleSetupScreen(mx, my, 0, 0, button);
1361       break;
1362
1363     case GAME_MODE_PLAYING:
1364       SetPlayerMouseAction(mx, my, button);
1365
1366 #if defined(TARGET_SDL2)
1367       HandleFollowFinger(mx, my, button);
1368 #endif
1369
1370 #ifdef DEBUG
1371       if (button == MB_PRESSED && !motion_status && !button_hold &&
1372           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1373         DumpTileFromScreen(mx, my);
1374 #endif
1375
1376       break;
1377
1378     default:
1379       break;
1380   }
1381 }
1382
1383 static boolean is_string_suffix(char *string, char *suffix)
1384 {
1385   int string_len = strlen(string);
1386   int suffix_len = strlen(suffix);
1387
1388   if (suffix_len > string_len)
1389     return FALSE;
1390
1391   return (strEqual(&string[string_len - suffix_len], suffix));
1392 }
1393
1394 #define MAX_CHEAT_INPUT_LEN     32
1395
1396 static void HandleKeysSpecial(Key key)
1397 {
1398   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1399   char letter = getCharFromKey(key);
1400   int cheat_input_len = strlen(cheat_input);
1401   int i;
1402
1403   if (letter == 0)
1404     return;
1405
1406   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1407   {
1408     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1409       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1410
1411     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1412   }
1413
1414   cheat_input[cheat_input_len++] = letter;
1415   cheat_input[cheat_input_len] = '\0';
1416
1417 #if DEBUG_EVENTS_KEY
1418   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1419 #endif
1420
1421   if (game_status == GAME_MODE_MAIN)
1422   {
1423     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1424         is_string_suffix(cheat_input, ":ist"))
1425     {
1426       InsertSolutionTape();
1427     }
1428     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1429              is_string_suffix(cheat_input, ":rg"))
1430     {
1431       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1432       DrawMainMenu();
1433     }
1434     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1435              is_string_suffix(cheat_input, ":rs"))
1436     {
1437       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1438       DrawMainMenu();
1439     }
1440     else if (is_string_suffix(cheat_input, ":reload-music") ||
1441              is_string_suffix(cheat_input, ":rm"))
1442     {
1443       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1444       DrawMainMenu();
1445     }
1446     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1447              is_string_suffix(cheat_input, ":ra"))
1448     {
1449       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1450                           1 << ARTWORK_TYPE_SOUNDS |
1451                           1 << ARTWORK_TYPE_MUSIC);
1452       DrawMainMenu();
1453     }
1454     else if (is_string_suffix(cheat_input, ":dump-level") ||
1455              is_string_suffix(cheat_input, ":dl"))
1456     {
1457       DumpLevel(&level);
1458     }
1459     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1460              is_string_suffix(cheat_input, ":dt"))
1461     {
1462       DumpTape(&tape);
1463     }
1464     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1465              is_string_suffix(cheat_input, ":ft"))
1466     {
1467       /* fix single-player tapes that contain player input for more than one
1468          player (due to a bug in 3.3.1.2 and earlier versions), which results
1469          in playing levels with more than one player in multi-player mode,
1470          even though the tape was originally recorded in single-player mode */
1471
1472       /* remove player input actions for all players but the first one */
1473       for (i = 1; i < MAX_PLAYERS; i++)
1474         tape.player_participates[i] = FALSE;
1475
1476       tape.changed = TRUE;
1477     }
1478     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1479              is_string_suffix(cheat_input, ":snl"))
1480     {
1481       SaveNativeLevel(&level);
1482     }
1483     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1484              is_string_suffix(cheat_input, ":fps"))
1485     {
1486       global.show_frames_per_second = !global.show_frames_per_second;
1487     }
1488   }
1489   else if (game_status == GAME_MODE_PLAYING)
1490   {
1491 #ifdef DEBUG
1492     if (is_string_suffix(cheat_input, ".q"))
1493       DEBUG_SetMaximumDynamite();
1494 #endif
1495   }
1496   else if (game_status == GAME_MODE_EDITOR)
1497   {
1498     if (is_string_suffix(cheat_input, ":dump-brush") ||
1499         is_string_suffix(cheat_input, ":DB"))
1500     {
1501       DumpBrush();
1502     }
1503     else if (is_string_suffix(cheat_input, ":DDB"))
1504     {
1505       DumpBrush_Small();
1506     }
1507   }
1508 }
1509
1510 void HandleKeysDebug(Key key)
1511 {
1512 #ifdef DEBUG
1513   int i;
1514
1515   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1516   {
1517     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1518
1519     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1520     {
1521       if (key == setup.debug.frame_delay_key[i] &&
1522           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1523       {
1524         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1525                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1526
1527         if (!setup.debug.frame_delay_game_only)
1528           MenuFrameDelay = GameFrameDelay;
1529
1530         SetVideoFrameDelay(GameFrameDelay);
1531
1532         if (GameFrameDelay > ONE_SECOND_DELAY)
1533           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1534         else if (GameFrameDelay != 0)
1535           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1536                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1537                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1538         else
1539           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1540
1541         break;
1542       }
1543     }
1544   }
1545
1546   if (game_status == GAME_MODE_PLAYING)
1547   {
1548     if (key == KSYM_d)
1549     {
1550       options.debug = !options.debug;
1551
1552       Error(ERR_DEBUG, "debug mode %s",
1553             (options.debug ? "enabled" : "disabled"));
1554     }
1555     else if (key == KSYM_v)
1556     {
1557       Error(ERR_DEBUG, "currently using game engine version %d",
1558             game.engine_version);
1559     }
1560   }
1561 #endif
1562 }
1563
1564 void HandleKey(Key key, int key_status)
1565 {
1566   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1567   static boolean ignore_repeated_key = FALSE;
1568   static struct SetupKeyboardInfo ski;
1569   static struct SetupShortcutInfo ssi;
1570   static struct
1571   {
1572     Key *key_custom;
1573     Key *key_snap;
1574     Key key_default;
1575     byte action;
1576   } key_info[] =
1577   {
1578     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1579     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1580     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1581     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1582     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1583     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1584   };
1585   int joy = 0;
1586   int i;
1587
1588 #if defined(TARGET_SDL2)
1589   /* map special keys (media keys / remote control buttons) to default keys */
1590   if (key == KSYM_PlayPause)
1591     key = KSYM_space;
1592   else if (key == KSYM_Select)
1593     key = KSYM_Return;
1594 #endif
1595
1596   HandleSpecialGameControllerKeys(key, key_status);
1597
1598   if (game_status == GAME_MODE_PLAYING)
1599   {
1600     /* only needed for single-step tape recording mode */
1601     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1602     int pnr;
1603
1604     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1605     {
1606       byte key_action = 0;
1607
1608       if (setup.input[pnr].use_joystick)
1609         continue;
1610
1611       ski = setup.input[pnr].key;
1612
1613       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1614         if (key == *key_info[i].key_custom)
1615           key_action |= key_info[i].action;
1616
1617       /* use combined snap+direction keys for the first player only */
1618       if (pnr == 0)
1619       {
1620         ssi = setup.shortcut;
1621
1622         for (i = 0; i < NUM_DIRECTIONS; i++)
1623           if (key == *key_info[i].key_snap)
1624             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1625       }
1626
1627       if (key_status == KEY_PRESSED)
1628         stored_player[pnr].action |= key_action;
1629       else
1630         stored_player[pnr].action &= ~key_action;
1631
1632       if (tape.single_step && tape.recording && tape.pausing)
1633       {
1634         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1635         {
1636           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1637
1638           /* if snap key already pressed, keep pause mode when releasing */
1639           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1640             has_snapped[pnr] = TRUE;
1641         }
1642         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1643         {
1644           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1645
1646           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1647               getRedDiskReleaseFlag_SP() == 0)
1648           {
1649             /* add a single inactive frame before dropping starts */
1650             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1651             stored_player[pnr].force_dropping = TRUE;
1652           }
1653         }
1654         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1655         {
1656           /* if snap key was pressed without direction, leave pause mode */
1657           if (!has_snapped[pnr])
1658             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1659
1660           has_snapped[pnr] = FALSE;
1661         }
1662       }
1663       else if (tape.recording && tape.pausing && !tape.use_mouse)
1664       {
1665         /* prevent key release events from un-pausing a paused game */
1666         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1667           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1668       }
1669     }
1670   }
1671   else
1672   {
1673     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1674       if (key == key_info[i].key_default)
1675         joy |= key_info[i].action;
1676   }
1677
1678   if (joy)
1679   {
1680     if (key_status == KEY_PRESSED)
1681       key_joystick_mapping |= joy;
1682     else
1683       key_joystick_mapping &= ~joy;
1684
1685     HandleJoystick();
1686   }
1687
1688   if (game_status != GAME_MODE_PLAYING)
1689     key_joystick_mapping = 0;
1690
1691   if (key_status == KEY_RELEASED)
1692   {
1693     // reset flag to ignore repeated "key pressed" events after key release
1694     ignore_repeated_key = FALSE;
1695
1696     return;
1697   }
1698
1699   if ((key == KSYM_F11 ||
1700        ((key == KSYM_Return ||
1701          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1702       video.fullscreen_available &&
1703       !ignore_repeated_key)
1704   {
1705     setup.fullscreen = !setup.fullscreen;
1706
1707     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1708
1709     if (game_status == GAME_MODE_SETUP)
1710       RedrawSetupScreenAfterFullscreenToggle();
1711
1712     // set flag to ignore repeated "key pressed" events
1713     ignore_repeated_key = TRUE;
1714
1715     return;
1716   }
1717
1718   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1719        key == KSYM_minus || key == KSYM_KP_Subtract ||
1720        key == KSYM_plus  || key == KSYM_KP_Add ||
1721        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1722       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1723       video.window_scaling_available &&
1724       !video.fullscreen_enabled)
1725   {
1726     if (key == KSYM_0 || key == KSYM_KP_0)
1727       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1728     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1729       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1730     else
1731       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1732
1733     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1734       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1735     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1736       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1737
1738     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1739
1740     if (game_status == GAME_MODE_SETUP)
1741       RedrawSetupScreenAfterFullscreenToggle();
1742
1743     return;
1744   }
1745
1746   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1747                                       key == KSYM_Return ||
1748                                       key == KSYM_Escape)))
1749   {
1750     /* do not handle this key event anymore */
1751     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1752       return;
1753   }
1754
1755   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1756       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1757   {
1758     GameEnd();
1759
1760     return;
1761   }
1762
1763   if (game_status == GAME_MODE_MAIN &&
1764       (key == setup.shortcut.toggle_pause || key == KSYM_space))
1765   {
1766     StartGameActions(options.network, setup.autorecord, level.random_seed);
1767
1768     return;
1769   }
1770
1771   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1772   {
1773     if (key == setup.shortcut.save_game)
1774       TapeQuickSave();
1775     else if (key == setup.shortcut.load_game)
1776       TapeQuickLoad();
1777     else if (key == setup.shortcut.toggle_pause)
1778       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1779
1780     HandleTapeButtonKeys(key);
1781     HandleSoundButtonKeys(key);
1782   }
1783
1784   if (game_status == GAME_MODE_PLAYING && !network_playing)
1785   {
1786     int centered_player_nr_next = -999;
1787
1788     if (key == setup.shortcut.focus_player_all)
1789       centered_player_nr_next = -1;
1790     else
1791       for (i = 0; i < MAX_PLAYERS; i++)
1792         if (key == setup.shortcut.focus_player[i])
1793           centered_player_nr_next = i;
1794
1795     if (centered_player_nr_next != -999)
1796     {
1797       game.centered_player_nr_next = centered_player_nr_next;
1798       game.set_centered_player = TRUE;
1799
1800       if (tape.recording)
1801       {
1802         tape.centered_player_nr_next = game.centered_player_nr_next;
1803         tape.set_centered_player = TRUE;
1804       }
1805     }
1806   }
1807
1808   HandleKeysSpecial(key);
1809
1810   if (HandleGadgetsKeyInput(key))
1811   {
1812     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
1813       key = KSYM_UNDEFINED;
1814   }
1815
1816   switch (game_status)
1817   {
1818     case GAME_MODE_PSEUDO_TYPENAME:
1819       HandleTypeName(0, key);
1820       break;
1821
1822     case GAME_MODE_TITLE:
1823     case GAME_MODE_MAIN:
1824     case GAME_MODE_LEVELS:
1825     case GAME_MODE_LEVELNR:
1826     case GAME_MODE_SETUP:
1827     case GAME_MODE_INFO:
1828     case GAME_MODE_SCORES:
1829       switch (key)
1830       {
1831         case KSYM_space:
1832         case KSYM_Return:
1833           if (game_status == GAME_MODE_TITLE)
1834             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1835           else if (game_status == GAME_MODE_MAIN)
1836             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
1837           else if (game_status == GAME_MODE_LEVELS)
1838             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
1839           else if (game_status == GAME_MODE_LEVELNR)
1840             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
1841           else if (game_status == GAME_MODE_SETUP)
1842             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1843           else if (game_status == GAME_MODE_INFO)
1844             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
1845           else if (game_status == GAME_MODE_SCORES)
1846             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
1847           break;
1848
1849         case KSYM_Escape:
1850           if (game_status != GAME_MODE_MAIN)
1851             FadeSkipNextFadeIn();
1852
1853           if (game_status == GAME_MODE_TITLE)
1854             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1855           else if (game_status == GAME_MODE_LEVELS)
1856             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
1857           else if (game_status == GAME_MODE_LEVELNR)
1858             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
1859           else if (game_status == GAME_MODE_SETUP)
1860             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1861           else if (game_status == GAME_MODE_INFO)
1862             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
1863           else if (game_status == GAME_MODE_SCORES)
1864             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
1865           break;
1866
1867         case KSYM_Page_Up:
1868           if (game_status == GAME_MODE_LEVELS)
1869             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1870           else if (game_status == GAME_MODE_LEVELNR)
1871             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1872           else if (game_status == GAME_MODE_SETUP)
1873             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1874           else if (game_status == GAME_MODE_INFO)
1875             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1876           else if (game_status == GAME_MODE_SCORES)
1877             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
1878           break;
1879
1880         case KSYM_Page_Down:
1881           if (game_status == GAME_MODE_LEVELS)
1882             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1883           else if (game_status == GAME_MODE_LEVELNR)
1884             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1885           else if (game_status == GAME_MODE_SETUP)
1886             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1887           else if (game_status == GAME_MODE_INFO)
1888             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1889           else if (game_status == GAME_MODE_SCORES)
1890             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
1891           break;
1892
1893         default:
1894           break;
1895       }
1896       break;
1897
1898     case GAME_MODE_EDITOR:
1899       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
1900         HandleLevelEditorKeyInput(key);
1901       break;
1902
1903     case GAME_MODE_PLAYING:
1904     {
1905       switch (key)
1906       {
1907         case KSYM_Escape:
1908           RequestQuitGame(setup.ask_on_escape);
1909           break;
1910
1911         default:
1912           break;
1913       }
1914       break;
1915     }
1916
1917     default:
1918       if (key == KSYM_Escape)
1919       {
1920         SetGameStatus(GAME_MODE_MAIN);
1921
1922         DrawMainMenu();
1923
1924         return;
1925       }
1926   }
1927
1928   HandleKeysDebug(key);
1929 }
1930
1931 void HandleNoEvent()
1932 {
1933   HandleMouseCursor();
1934
1935   switch (game_status)
1936   {
1937 #if defined(TARGET_SDL2)
1938     case GAME_MODE_PLAYING:
1939       HandleFollowFinger(-1, -1, -1);
1940       break;
1941 #endif
1942   }
1943 }
1944
1945 void HandleEventActions()
1946 {
1947   // if (button_status && game_status != GAME_MODE_PLAYING)
1948   if (button_status && (game_status != GAME_MODE_PLAYING ||
1949                         tape.pausing ||
1950                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
1951   {
1952     HandleButton(0, 0, button_status, -button_status);
1953   }
1954   else
1955   {
1956     HandleJoystick();
1957   }
1958
1959 #if defined(NETWORK_AVALIABLE)
1960   if (options.network)
1961     HandleNetworking();
1962 #endif
1963
1964   switch (game_status)
1965   {
1966     case GAME_MODE_MAIN:
1967       DrawPreviewLevelAnimation();
1968       break;
1969
1970     case GAME_MODE_EDITOR:
1971       HandleLevelEditorIdle();
1972       break;
1973
1974     default:
1975       break;
1976   }
1977 }
1978
1979 static int HandleJoystickForAllPlayers()
1980 {
1981   int i;
1982   int result = 0;
1983   boolean no_joysticks_configured = TRUE;
1984   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
1985   static byte joy_action_last[MAX_PLAYERS];
1986
1987   for (i = 0; i < MAX_PLAYERS; i++)
1988     if (setup.input[i].use_joystick)
1989       no_joysticks_configured = FALSE;
1990
1991   /* if no joysticks configured, map connected joysticks to players */
1992   if (no_joysticks_configured)
1993     use_as_joystick_nr = TRUE;
1994
1995   for (i = 0; i < MAX_PLAYERS; i++)
1996   {
1997     byte joy_action = 0;
1998
1999     joy_action = JoystickExt(i, use_as_joystick_nr);
2000     result |= joy_action;
2001
2002     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2003         joy_action != joy_action_last[i])
2004       stored_player[i].action = joy_action;
2005
2006     joy_action_last[i] = joy_action;
2007   }
2008
2009   return result;
2010 }
2011
2012 void HandleJoystick()
2013 {
2014   int joystick  = HandleJoystickForAllPlayers();
2015   int keyboard  = key_joystick_mapping;
2016   int joy       = (joystick | keyboard);
2017   int left      = joy & JOY_LEFT;
2018   int right     = joy & JOY_RIGHT;
2019   int up        = joy & JOY_UP;
2020   int down      = joy & JOY_DOWN;
2021   int button    = joy & JOY_BUTTON;
2022   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2023   int dx        = (left ? -1    : right ? 1     : 0);
2024   int dy        = (up   ? -1    : down  ? 1     : 0);
2025
2026   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2027   {
2028     /* do not handle this button event anymore */
2029     return;
2030   }
2031
2032   switch (game_status)
2033   {
2034     case GAME_MODE_TITLE:
2035     case GAME_MODE_MAIN:
2036     case GAME_MODE_LEVELS:
2037     case GAME_MODE_LEVELNR:
2038     case GAME_MODE_SETUP:
2039     case GAME_MODE_INFO:
2040     {
2041       static unsigned int joystickmove_delay = 0;
2042       static unsigned int joystickmove_delay_value = GADGET_FRAME_DELAY;
2043       static int joystick_last = 0;
2044
2045       if (joystick && !button &&
2046           !DelayReached(&joystickmove_delay, joystickmove_delay_value))
2047       {
2048         /* delay joystick actions if buttons/axes continually pressed */
2049         newbutton = dx = dy = 0;
2050       }
2051       else
2052       {
2053         /* start with longer delay, then continue with shorter delay */
2054         if (joystick != joystick_last)
2055           joystickmove_delay_value = GADGET_FRAME_DELAY_FIRST;
2056         else
2057           joystickmove_delay_value = GADGET_FRAME_DELAY;
2058       }
2059
2060       if (game_status == GAME_MODE_TITLE)
2061         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2062       else if (game_status == GAME_MODE_MAIN)
2063         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2064       else if (game_status == GAME_MODE_LEVELS)
2065         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2066       else if (game_status == GAME_MODE_LEVELNR)
2067         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2068       else if (game_status == GAME_MODE_SETUP)
2069         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2070       else if (game_status == GAME_MODE_INFO)
2071         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2072
2073       joystick_last = joystick;
2074
2075       break;
2076     }
2077
2078     case GAME_MODE_SCORES:
2079       HandleHallOfFame(0, 0, dx, dy, !newbutton);
2080       break;
2081
2082     case GAME_MODE_PLAYING:
2083       if (tape.playing || keyboard)
2084         newbutton = ((joy & JOY_BUTTON) != 0);
2085
2086       if (newbutton && AllPlayersGone)
2087       {
2088         GameEnd();
2089
2090         return;
2091       }
2092
2093       if (tape.recording && tape.pausing)
2094       {
2095         if (joystick & JOY_ACTION)
2096           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2097       }
2098
2099       break;
2100
2101     default:
2102       break;
2103   }
2104 }
2105
2106 void HandleSpecialGameControllerButtons(Event *event)
2107 {
2108 #if defined(TARGET_SDL2)
2109   switch (event->type)
2110   {
2111     case SDL_CONTROLLERBUTTONDOWN:
2112       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2113         HandleKey(KSYM_space, KEY_PRESSED);
2114       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2115         HandleKey(KSYM_Escape, KEY_PRESSED);
2116
2117       break;
2118
2119     case SDL_CONTROLLERBUTTONUP:
2120       if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2121         HandleKey(KSYM_space, KEY_RELEASED);
2122       else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2123         HandleKey(KSYM_Escape, KEY_RELEASED);
2124
2125       break;
2126   }
2127 #endif
2128 }
2129
2130 void HandleSpecialGameControllerKeys(Key key, int key_status)
2131 {
2132 #if defined(TARGET_SDL2)
2133 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2134   int button = SDL_CONTROLLER_BUTTON_INVALID;
2135
2136   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2137   if (key == KSYM_Rewind)
2138     button = SDL_CONTROLLER_BUTTON_A;
2139   else if (key == KSYM_FastForward || key == KSYM_Menu)
2140     button = SDL_CONTROLLER_BUTTON_B;
2141
2142   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2143   {
2144     Event event;
2145
2146     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2147                   SDL_CONTROLLERBUTTONUP);
2148
2149     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2150     event.cbutton.button = button;
2151     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2152                            SDL_RELEASED);
2153
2154     HandleJoystickEvent(&event);
2155   }
2156 #endif
2157 #endif
2158 }