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