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