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