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