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