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