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