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