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