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