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