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