replaced delay functions with new one that continues updating toons
[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   }
1374   else
1375   {
1376     if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377       HandleButtonOrFinger_FollowFinger(mx, my, button);
1378   }
1379 }
1380
1381 #if defined(TARGET_SDL2)
1382
1383 static boolean checkTextInputKeyModState()
1384 {
1385   // when playing, only handle raw key events and ignore text input
1386   if (game_status == GAME_MODE_PLAYING)
1387     return FALSE;
1388
1389   return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1390 }
1391
1392 void HandleTextEvent(TextEvent *event)
1393 {
1394   char *text = event->text;
1395   Key key = getKeyFromKeyName(text);
1396
1397 #if DEBUG_EVENTS_TEXT
1398   Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1399         text,
1400         strlen(text),
1401         text[0], (int)(text[0]),
1402         key,
1403         getKeyNameFromKey(key),
1404         GetKeyModState());
1405 #endif
1406
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408   // non-mobile devices: only handle key input with modifier keys pressed here
1409   // (every other key input is handled directly as physical key input event)
1410   if (!checkTextInputKeyModState())
1411     return;
1412 #endif
1413
1414   // process text input as "classic" (with uppercase etc.) key input event
1415   HandleKey(key, KEY_PRESSED);
1416   HandleKey(key, KEY_RELEASED);
1417 }
1418
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1420 {
1421   if (event->type == SDL_APP_WILLENTERBACKGROUND)
1422   {
1423     Mix_PauseMusic();
1424   }
1425   else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1426   {
1427     Mix_ResumeMusic();
1428   }
1429 }
1430
1431 #endif
1432
1433 void HandleKeyEvent(KeyEvent *event)
1434 {
1435   int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1436   boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1437   Key key = GetEventKey(event, with_modifiers);
1438   Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1439
1440 #if DEBUG_EVENTS_KEY
1441   Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1442         event->type == EVENT_KEYPRESS ? "pressed" : "released",
1443         event->keysym.scancode,
1444         event->keysym.sym,
1445         keymod,
1446         GetKeyModState(),
1447         key,
1448         getKeyNameFromKey(key));
1449 #endif
1450
1451 #if defined(PLATFORM_ANDROID)
1452   if (key == KSYM_Back)
1453   {
1454     // always map the "back" button to the "escape" key on Android devices
1455     key = KSYM_Escape;
1456   }
1457   else
1458   {
1459     // for any key event other than "back" button, disable overlay buttons
1460     SetOverlayEnabled(FALSE);
1461   }
1462 #endif
1463
1464   HandleKeyModState(keymod, key_status);
1465
1466 #if defined(TARGET_SDL2)
1467   // only handle raw key input without text modifier keys pressed
1468   if (!checkTextInputKeyModState())
1469     HandleKey(key, key_status);
1470 #else
1471   HandleKey(key, key_status);
1472 #endif
1473 }
1474
1475 void HandleFocusEvent(FocusChangeEvent *event)
1476 {
1477   static int old_joystick_status = -1;
1478
1479   if (event->type == EVENT_FOCUSOUT)
1480   {
1481     KeyboardAutoRepeatOn();
1482     old_joystick_status = joystick.status;
1483     joystick.status = JOYSTICK_NOT_AVAILABLE;
1484
1485     ClearPlayerAction();
1486   }
1487   else if (event->type == EVENT_FOCUSIN)
1488   {
1489     /* When there are two Rocks'n'Diamonds windows which overlap and
1490        the player moves the pointer from one game window to the other,
1491        a 'FocusOut' event is generated for the window the pointer is
1492        leaving and a 'FocusIn' event is generated for the window the
1493        pointer is entering. In some cases, it can happen that the
1494        'FocusIn' event is handled by the one game process before the
1495        'FocusOut' event by the other game process. In this case the
1496        X11 environment would end up with activated keyboard auto repeat,
1497        because unfortunately this is a global setting and not (which
1498        would be far better) set for each X11 window individually.
1499        The effect would be keyboard auto repeat while playing the game
1500        (game_status == GAME_MODE_PLAYING), which is not desired.
1501        To avoid this special case, we just wait 1/10 second before
1502        processing the 'FocusIn' event.
1503     */
1504
1505     if (game_status == GAME_MODE_PLAYING)
1506     {
1507       Delay(100);
1508       KeyboardAutoRepeatOffUnlessAutoplay();
1509     }
1510
1511     if (old_joystick_status != -1)
1512       joystick.status = old_joystick_status;
1513   }
1514 }
1515
1516 void HandleClientMessageEvent(ClientMessageEvent *event)
1517 {
1518   if (CheckCloseWindowEvent(event))
1519     CloseAllAndExit(0);
1520 }
1521
1522 void HandleWindowManagerEvent(Event *event)
1523 {
1524 #if defined(TARGET_SDL)
1525   SDLHandleWindowManagerEvent(event);
1526 #endif
1527 }
1528
1529 void HandleButton(int mx, int my, int button, int button_nr)
1530 {
1531   static int old_mx = 0, old_my = 0;
1532   boolean button_hold = FALSE;
1533   boolean handle_gadgets = TRUE;
1534
1535   if (button_nr < 0)
1536   {
1537     mx = old_mx;
1538     my = old_my;
1539     button_nr = -button_nr;
1540     button_hold = TRUE;
1541   }
1542   else
1543   {
1544     old_mx = mx;
1545     old_my = my;
1546   }
1547
1548 #if defined(PLATFORM_ANDROID)
1549   // when playing, only handle gadgets when using "follow finger" controls
1550   // or when using touch controls in combination with the MM game engine
1551   handle_gadgets =
1552     (game_status != GAME_MODE_PLAYING ||
1553      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1554      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1555 #endif
1556
1557   if (HandleGlobalAnimClicks(mx, my, button))
1558   {
1559     /* do not handle this button event anymore */
1560     return;             /* force mouse event not to be handled at all */
1561   }
1562
1563   if (handle_gadgets && HandleGadgets(mx, my, button))
1564   {
1565     /* do not handle this button event anymore */
1566     mx = my = -32;      /* force mouse event to be outside screen tiles */
1567   }
1568
1569   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1570     return;
1571
1572   /* do not use scroll wheel button events for anything other than gadgets */
1573   if (IS_WHEEL_BUTTON(button_nr))
1574     return;
1575
1576   switch (game_status)
1577   {
1578     case GAME_MODE_TITLE:
1579       HandleTitleScreen(mx, my, 0, 0, button);
1580       break;
1581
1582     case GAME_MODE_MAIN:
1583       HandleMainMenu(mx, my, 0, 0, button);
1584       break;
1585
1586     case GAME_MODE_PSEUDO_TYPENAME:
1587       HandleTypeName(0, KSYM_Return);
1588       break;
1589
1590     case GAME_MODE_LEVELS:
1591       HandleChooseLevelSet(mx, my, 0, 0, button);
1592       break;
1593
1594     case GAME_MODE_LEVELNR:
1595       HandleChooseLevelNr(mx, my, 0, 0, button);
1596       break;
1597
1598     case GAME_MODE_SCORES:
1599       HandleHallOfFame(0, 0, 0, 0, button);
1600       break;
1601
1602     case GAME_MODE_EDITOR:
1603       HandleLevelEditorIdle();
1604       break;
1605
1606     case GAME_MODE_INFO:
1607       HandleInfoScreen(mx, my, 0, 0, button);
1608       break;
1609
1610     case GAME_MODE_SETUP:
1611       HandleSetupScreen(mx, my, 0, 0, button);
1612       break;
1613
1614     case GAME_MODE_PLAYING:
1615       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1616         HandleButtonOrFinger(mx, my, button);
1617       else
1618         SetPlayerMouseAction(mx, my, button);
1619
1620 #ifdef DEBUG
1621       if (button == MB_PRESSED && !motion_status && !button_hold &&
1622           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1623         DumpTileFromScreen(mx, my);
1624 #endif
1625
1626       break;
1627
1628     default:
1629       break;
1630   }
1631 }
1632
1633 static boolean is_string_suffix(char *string, char *suffix)
1634 {
1635   int string_len = strlen(string);
1636   int suffix_len = strlen(suffix);
1637
1638   if (suffix_len > string_len)
1639     return FALSE;
1640
1641   return (strEqual(&string[string_len - suffix_len], suffix));
1642 }
1643
1644 #define MAX_CHEAT_INPUT_LEN     32
1645
1646 static void HandleKeysSpecial(Key key)
1647 {
1648   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1649   char letter = getCharFromKey(key);
1650   int cheat_input_len = strlen(cheat_input);
1651   int i;
1652
1653   if (letter == 0)
1654     return;
1655
1656   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1657   {
1658     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1659       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1660
1661     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1662   }
1663
1664   cheat_input[cheat_input_len++] = letter;
1665   cheat_input[cheat_input_len] = '\0';
1666
1667 #if DEBUG_EVENTS_KEY
1668   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1669 #endif
1670
1671   if (game_status == GAME_MODE_MAIN)
1672   {
1673     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1674         is_string_suffix(cheat_input, ":ist"))
1675     {
1676       InsertSolutionTape();
1677     }
1678     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1679              is_string_suffix(cheat_input, ":pst"))
1680     {
1681       PlaySolutionTape();
1682     }
1683     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1684              is_string_suffix(cheat_input, ":rg"))
1685     {
1686       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1687       DrawMainMenu();
1688     }
1689     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1690              is_string_suffix(cheat_input, ":rs"))
1691     {
1692       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1693       DrawMainMenu();
1694     }
1695     else if (is_string_suffix(cheat_input, ":reload-music") ||
1696              is_string_suffix(cheat_input, ":rm"))
1697     {
1698       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1699       DrawMainMenu();
1700     }
1701     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1702              is_string_suffix(cheat_input, ":ra"))
1703     {
1704       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1705                           1 << ARTWORK_TYPE_SOUNDS |
1706                           1 << ARTWORK_TYPE_MUSIC);
1707       DrawMainMenu();
1708     }
1709     else if (is_string_suffix(cheat_input, ":dump-level") ||
1710              is_string_suffix(cheat_input, ":dl"))
1711     {
1712       DumpLevel(&level);
1713     }
1714     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1715              is_string_suffix(cheat_input, ":dt"))
1716     {
1717       DumpTape(&tape);
1718     }
1719     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1720              is_string_suffix(cheat_input, ":ft"))
1721     {
1722       /* fix single-player tapes that contain player input for more than one
1723          player (due to a bug in 3.3.1.2 and earlier versions), which results
1724          in playing levels with more than one player in multi-player mode,
1725          even though the tape was originally recorded in single-player mode */
1726
1727       /* remove player input actions for all players but the first one */
1728       for (i = 1; i < MAX_PLAYERS; i++)
1729         tape.player_participates[i] = FALSE;
1730
1731       tape.changed = TRUE;
1732     }
1733     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1734              is_string_suffix(cheat_input, ":snl"))
1735     {
1736       SaveNativeLevel(&level);
1737     }
1738     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1739              is_string_suffix(cheat_input, ":fps"))
1740     {
1741       global.show_frames_per_second = !global.show_frames_per_second;
1742     }
1743   }
1744   else if (game_status == GAME_MODE_PLAYING)
1745   {
1746 #ifdef DEBUG
1747     if (is_string_suffix(cheat_input, ".q"))
1748       DEBUG_SetMaximumDynamite();
1749 #endif
1750   }
1751   else if (game_status == GAME_MODE_EDITOR)
1752   {
1753     if (is_string_suffix(cheat_input, ":dump-brush") ||
1754         is_string_suffix(cheat_input, ":DB"))
1755     {
1756       DumpBrush();
1757     }
1758     else if (is_string_suffix(cheat_input, ":DDB"))
1759     {
1760       DumpBrush_Small();
1761     }
1762   }
1763
1764   /* special key shortcuts for all game modes */
1765   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1766       is_string_suffix(cheat_input, ":dea") ||
1767       is_string_suffix(cheat_input, ":DEA"))
1768   {
1769     DumpGadgetIdentifiers();
1770     DumpScreenIdentifiers();
1771   }
1772 }
1773
1774 void HandleKeysDebug(Key key)
1775 {
1776 #ifdef DEBUG
1777   int i;
1778
1779   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1780   {
1781     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1782
1783     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1784     {
1785       if (key == setup.debug.frame_delay_key[i] &&
1786           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1787       {
1788         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1789                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1790
1791         if (!setup.debug.frame_delay_game_only)
1792           MenuFrameDelay = GameFrameDelay;
1793
1794         SetVideoFrameDelay(GameFrameDelay);
1795
1796         if (GameFrameDelay > ONE_SECOND_DELAY)
1797           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1798         else if (GameFrameDelay != 0)
1799           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1800                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1801                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1802         else
1803           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1804
1805         break;
1806       }
1807     }
1808   }
1809
1810   if (game_status == GAME_MODE_PLAYING)
1811   {
1812     if (key == KSYM_d)
1813     {
1814       options.debug = !options.debug;
1815
1816       Error(ERR_DEBUG, "debug mode %s",
1817             (options.debug ? "enabled" : "disabled"));
1818     }
1819     else if (key == KSYM_v)
1820     {
1821       Error(ERR_DEBUG, "currently using game engine version %d",
1822             game.engine_version);
1823     }
1824   }
1825 #endif
1826 }
1827
1828 void HandleKey(Key key, int key_status)
1829 {
1830   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1831   static boolean ignore_repeated_key = FALSE;
1832   static struct SetupKeyboardInfo ski;
1833   static struct SetupShortcutInfo ssi;
1834   static struct
1835   {
1836     Key *key_custom;
1837     Key *key_snap;
1838     Key key_default;
1839     byte action;
1840   } key_info[] =
1841   {
1842     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1843     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1844     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1845     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1846     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1847     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1848   };
1849   int joy = 0;
1850   int i;
1851
1852 #if defined(TARGET_SDL2)
1853   /* map special keys (media keys / remote control buttons) to default keys */
1854   if (key == KSYM_PlayPause)
1855     key = KSYM_space;
1856   else if (key == KSYM_Select)
1857     key = KSYM_Return;
1858 #endif
1859
1860   HandleSpecialGameControllerKeys(key, key_status);
1861
1862   if (game_status == GAME_MODE_PLAYING)
1863   {
1864     /* only needed for single-step tape recording mode */
1865     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1866     int pnr;
1867
1868     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1869     {
1870       byte key_action = 0;
1871
1872       if (setup.input[pnr].use_joystick)
1873         continue;
1874
1875       ski = setup.input[pnr].key;
1876
1877       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1878         if (key == *key_info[i].key_custom)
1879           key_action |= key_info[i].action;
1880
1881       /* use combined snap+direction keys for the first player only */
1882       if (pnr == 0)
1883       {
1884         ssi = setup.shortcut;
1885
1886         for (i = 0; i < NUM_DIRECTIONS; i++)
1887           if (key == *key_info[i].key_snap)
1888             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1889       }
1890
1891       if (key_status == KEY_PRESSED)
1892         stored_player[pnr].action |= key_action;
1893       else
1894         stored_player[pnr].action &= ~key_action;
1895
1896       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1897       {
1898         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1899         {
1900           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1901
1902           /* if snap key already pressed, keep pause mode when releasing */
1903           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1904             has_snapped[pnr] = TRUE;
1905         }
1906         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1907         {
1908           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1909
1910           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1911               getRedDiskReleaseFlag_SP() == 0)
1912           {
1913             /* add a single inactive frame before dropping starts */
1914             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1915             stored_player[pnr].force_dropping = TRUE;
1916           }
1917         }
1918         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1919         {
1920           /* if snap key was pressed without direction, leave pause mode */
1921           if (!has_snapped[pnr])
1922             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1923
1924           has_snapped[pnr] = FALSE;
1925         }
1926       }
1927       else if (tape.recording && tape.pausing && !tape.use_mouse)
1928       {
1929         /* prevent key release events from un-pausing a paused game */
1930         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1931           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1932       }
1933
1934       // for MM style levels, handle in-game keyboard input in HandleJoystick()
1935       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1936         joy |= key_action;
1937     }
1938   }
1939   else
1940   {
1941     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1942       if (key == key_info[i].key_default)
1943         joy |= key_info[i].action;
1944   }
1945
1946   if (joy)
1947   {
1948     if (key_status == KEY_PRESSED)
1949       key_joystick_mapping |= joy;
1950     else
1951       key_joystick_mapping &= ~joy;
1952
1953     HandleJoystick();
1954   }
1955
1956   if (game_status != GAME_MODE_PLAYING)
1957     key_joystick_mapping = 0;
1958
1959   if (key_status == KEY_RELEASED)
1960   {
1961     // reset flag to ignore repeated "key pressed" events after key release
1962     ignore_repeated_key = FALSE;
1963
1964     return;
1965   }
1966
1967   if ((key == KSYM_F11 ||
1968        ((key == KSYM_Return ||
1969          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1970       video.fullscreen_available &&
1971       !ignore_repeated_key)
1972   {
1973     setup.fullscreen = !setup.fullscreen;
1974
1975     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1976
1977     if (game_status == GAME_MODE_SETUP)
1978       RedrawSetupScreenAfterFullscreenToggle();
1979
1980     // set flag to ignore repeated "key pressed" events
1981     ignore_repeated_key = TRUE;
1982
1983     return;
1984   }
1985
1986   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1987        key == KSYM_minus || key == KSYM_KP_Subtract ||
1988        key == KSYM_plus  || key == KSYM_KP_Add ||
1989        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1990       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1991       video.window_scaling_available &&
1992       !video.fullscreen_enabled)
1993   {
1994     if (key == KSYM_0 || key == KSYM_KP_0)
1995       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1996     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1997       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1998     else
1999       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2000
2001     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2002       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2003     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2004       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2005
2006     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2007
2008     if (game_status == GAME_MODE_SETUP)
2009       RedrawSetupScreenAfterFullscreenToggle();
2010
2011     return;
2012   }
2013
2014   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2015                                       key == KSYM_Return ||
2016                                       key == KSYM_Escape)))
2017   {
2018     /* do not handle this key event anymore */
2019     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
2020       return;
2021   }
2022
2023   if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2024       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2025   {
2026     GameEnd();
2027
2028     return;
2029   }
2030
2031   if (game_status == GAME_MODE_MAIN &&
2032       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2033   {
2034     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2035
2036     return;
2037   }
2038
2039   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2040   {
2041     if (key == setup.shortcut.save_game)
2042       TapeQuickSave();
2043     else if (key == setup.shortcut.load_game)
2044       TapeQuickLoad();
2045     else if (key == setup.shortcut.toggle_pause)
2046       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2047
2048     HandleTapeButtonKeys(key);
2049     HandleSoundButtonKeys(key);
2050   }
2051
2052   if (game_status == GAME_MODE_PLAYING && !network_playing)
2053   {
2054     int centered_player_nr_next = -999;
2055
2056     if (key == setup.shortcut.focus_player_all)
2057       centered_player_nr_next = -1;
2058     else
2059       for (i = 0; i < MAX_PLAYERS; i++)
2060         if (key == setup.shortcut.focus_player[i])
2061           centered_player_nr_next = i;
2062
2063     if (centered_player_nr_next != -999)
2064     {
2065       game.centered_player_nr_next = centered_player_nr_next;
2066       game.set_centered_player = TRUE;
2067
2068       if (tape.recording)
2069       {
2070         tape.centered_player_nr_next = game.centered_player_nr_next;
2071         tape.set_centered_player = TRUE;
2072       }
2073     }
2074   }
2075
2076   HandleKeysSpecial(key);
2077
2078   if (HandleGadgetsKeyInput(key))
2079   {
2080     if (key != KSYM_Escape)     /* always allow ESC key to be handled */
2081       key = KSYM_UNDEFINED;
2082   }
2083
2084   switch (game_status)
2085   {
2086     case GAME_MODE_PSEUDO_TYPENAME:
2087       HandleTypeName(0, key);
2088       break;
2089
2090     case GAME_MODE_TITLE:
2091     case GAME_MODE_MAIN:
2092     case GAME_MODE_LEVELS:
2093     case GAME_MODE_LEVELNR:
2094     case GAME_MODE_SETUP:
2095     case GAME_MODE_INFO:
2096     case GAME_MODE_SCORES:
2097       switch (key)
2098       {
2099         case KSYM_space:
2100         case KSYM_Return:
2101           if (game_status == GAME_MODE_TITLE)
2102             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2103           else if (game_status == GAME_MODE_MAIN)
2104             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2105           else if (game_status == GAME_MODE_LEVELS)
2106             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2107           else if (game_status == GAME_MODE_LEVELNR)
2108             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2109           else if (game_status == GAME_MODE_SETUP)
2110             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2111           else if (game_status == GAME_MODE_INFO)
2112             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2113           else if (game_status == GAME_MODE_SCORES)
2114             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2115           break;
2116
2117         case KSYM_Escape:
2118           if (game_status != GAME_MODE_MAIN)
2119             FadeSkipNextFadeIn();
2120
2121           if (game_status == GAME_MODE_TITLE)
2122             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2123           else if (game_status == GAME_MODE_LEVELS)
2124             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2125           else if (game_status == GAME_MODE_LEVELNR)
2126             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2127           else if (game_status == GAME_MODE_SETUP)
2128             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2129           else if (game_status == GAME_MODE_INFO)
2130             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2131           else if (game_status == GAME_MODE_SCORES)
2132             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2133           break;
2134
2135         case KSYM_Page_Up:
2136           if (game_status == GAME_MODE_LEVELS)
2137             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2138           else if (game_status == GAME_MODE_LEVELNR)
2139             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2140           else if (game_status == GAME_MODE_SETUP)
2141             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2142           else if (game_status == GAME_MODE_INFO)
2143             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2144           else if (game_status == GAME_MODE_SCORES)
2145             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2146           break;
2147
2148         case KSYM_Page_Down:
2149           if (game_status == GAME_MODE_LEVELS)
2150             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2151           else if (game_status == GAME_MODE_LEVELNR)
2152             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2153           else if (game_status == GAME_MODE_SETUP)
2154             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2155           else if (game_status == GAME_MODE_INFO)
2156             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2157           else if (game_status == GAME_MODE_SCORES)
2158             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2159           break;
2160
2161         default:
2162           break;
2163       }
2164       break;
2165
2166     case GAME_MODE_EDITOR:
2167       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2168         HandleLevelEditorKeyInput(key);
2169       break;
2170
2171     case GAME_MODE_PLAYING:
2172     {
2173       switch (key)
2174       {
2175         case KSYM_Escape:
2176           RequestQuitGame(setup.ask_on_escape);
2177           break;
2178
2179         default:
2180           break;
2181       }
2182       break;
2183     }
2184
2185     default:
2186       if (key == KSYM_Escape)
2187       {
2188         SetGameStatus(GAME_MODE_MAIN);
2189
2190         DrawMainMenu();
2191
2192         return;
2193       }
2194   }
2195
2196   HandleKeysDebug(key);
2197 }
2198
2199 void HandleNoEvent()
2200 {
2201   HandleMouseCursor();
2202
2203   switch (game_status)
2204   {
2205     case GAME_MODE_PLAYING:
2206       HandleButtonOrFinger(-1, -1, -1);
2207       break;
2208   }
2209 }
2210
2211 void HandleEventActions()
2212 {
2213   // if (button_status && game_status != GAME_MODE_PLAYING)
2214   if (button_status && (game_status != GAME_MODE_PLAYING ||
2215                         tape.pausing ||
2216                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2217   {
2218     HandleButton(0, 0, button_status, -button_status);
2219   }
2220   else
2221   {
2222     HandleJoystick();
2223   }
2224
2225   if (network.enabled)
2226     HandleNetworking();
2227
2228   switch (game_status)
2229   {
2230     case GAME_MODE_MAIN:
2231       DrawPreviewLevelAnimation();
2232       break;
2233
2234     case GAME_MODE_EDITOR:
2235       HandleLevelEditorIdle();
2236       break;
2237
2238     default:
2239       break;
2240   }
2241 }
2242
2243 static void HandleTileCursor(int dx, int dy, int button)
2244 {
2245   if (!dx || !button)
2246     ClearPlayerMouseAction();
2247
2248   if (!dx && !dy)
2249     return;
2250
2251   if (button)
2252   {
2253     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2254                          (dx < 0 ? MB_LEFTBUTTON :
2255                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2256   }
2257   else if (!tile_cursor.moving)
2258   {
2259     int old_xpos = tile_cursor.xpos;
2260     int old_ypos = tile_cursor.ypos;
2261     int new_xpos = old_xpos;
2262     int new_ypos = old_ypos;
2263
2264     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2265       new_xpos = old_xpos + dx;
2266
2267     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2268       new_ypos = old_ypos + dy;
2269
2270     SetTileCursorTargetXY(new_xpos, new_ypos);
2271   }
2272 }
2273
2274 static int HandleJoystickForAllPlayers()
2275 {
2276   int i;
2277   int result = 0;
2278   boolean no_joysticks_configured = TRUE;
2279   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2280   static byte joy_action_last[MAX_PLAYERS];
2281
2282   for (i = 0; i < MAX_PLAYERS; i++)
2283     if (setup.input[i].use_joystick)
2284       no_joysticks_configured = FALSE;
2285
2286   /* if no joysticks configured, map connected joysticks to players */
2287   if (no_joysticks_configured)
2288     use_as_joystick_nr = TRUE;
2289
2290   for (i = 0; i < MAX_PLAYERS; i++)
2291   {
2292     byte joy_action = 0;
2293
2294     joy_action = JoystickExt(i, use_as_joystick_nr);
2295     result |= joy_action;
2296
2297     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2298         joy_action != joy_action_last[i])
2299       stored_player[i].action = joy_action;
2300
2301     joy_action_last[i] = joy_action;
2302   }
2303
2304   return result;
2305 }
2306
2307 void HandleJoystick()
2308 {
2309   static unsigned int joytest_delay = 0;
2310   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2311   static int joytest_last = 0;
2312   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2313   int delay_value       = GADGET_FRAME_DELAY;
2314   int joystick  = HandleJoystickForAllPlayers();
2315   int keyboard  = key_joystick_mapping;
2316   int joy       = (joystick | keyboard);
2317   int joytest   = joystick;
2318   int left      = joy & JOY_LEFT;
2319   int right     = joy & JOY_RIGHT;
2320   int up        = joy & JOY_UP;
2321   int down      = joy & JOY_DOWN;
2322   int button    = joy & JOY_BUTTON;
2323   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2324   int dx        = (left ? -1    : right ? 1     : 0);
2325   int dy        = (up   ? -1    : down  ? 1     : 0);
2326   boolean use_delay_value_first = (joytest != joytest_last);
2327
2328   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2329   {
2330     /* do not handle this button event anymore */
2331     return;
2332   }
2333
2334   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2335   {
2336     if (game_status == GAME_MODE_PLAYING)
2337     {
2338       // when playing MM style levels, also use delay for keyboard events
2339       joytest |= keyboard;
2340
2341       // only use first delay value for new events, but not for changed events
2342       use_delay_value_first = (!joytest != !joytest_last);
2343
2344       // only use delay after the initial keyboard event
2345       delay_value = 0;
2346     }
2347
2348     // for any joystick or keyboard event, enable playfield tile cursor
2349     if (dx || dy || button)
2350       SetTileCursorEnabled(TRUE);
2351   }
2352
2353   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2354   {
2355     /* delay joystick/keyboard actions if axes/keys continually pressed */
2356     newbutton = dx = dy = 0;
2357   }
2358   else
2359   {
2360     /* first start with longer delay, then continue with shorter delay */
2361     joytest_delay_value =
2362       (use_delay_value_first ? delay_value_first : delay_value);
2363   }
2364
2365   joytest_last = joytest;
2366
2367   switch (game_status)
2368   {
2369     case GAME_MODE_TITLE:
2370     case GAME_MODE_MAIN:
2371     case GAME_MODE_LEVELS:
2372     case GAME_MODE_LEVELNR:
2373     case GAME_MODE_SETUP:
2374     case GAME_MODE_INFO:
2375     case GAME_MODE_SCORES:
2376     {
2377       if (game_status == GAME_MODE_TITLE)
2378         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2379       else if (game_status == GAME_MODE_MAIN)
2380         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2381       else if (game_status == GAME_MODE_LEVELS)
2382         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2383       else if (game_status == GAME_MODE_LEVELNR)
2384         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2385       else if (game_status == GAME_MODE_SETUP)
2386         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2387       else if (game_status == GAME_MODE_INFO)
2388         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2389       else if (game_status == GAME_MODE_SCORES)
2390         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2391
2392       break;
2393     }
2394
2395     case GAME_MODE_PLAYING:
2396 #if 0
2397       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2398       if (tape.playing || keyboard)
2399         newbutton = ((joy & JOY_BUTTON) != 0);
2400 #endif
2401
2402       if (newbutton && AllPlayersGone)
2403       {
2404         GameEnd();
2405
2406         return;
2407       }
2408
2409       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2410       {
2411         if (joystick & JOY_ACTION)
2412           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2413       }
2414       else if (tape.recording && tape.pausing && !tape.use_mouse)
2415       {
2416         if (joystick & JOY_ACTION)
2417           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2418       }
2419
2420       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2421         HandleTileCursor(dx, dy, button);
2422
2423       break;
2424
2425     default:
2426       break;
2427   }
2428 }
2429
2430 void HandleSpecialGameControllerButtons(Event *event)
2431 {
2432 #if defined(TARGET_SDL2)
2433   int key_status;
2434   Key key;
2435
2436   switch (event->type)
2437   {
2438     case SDL_CONTROLLERBUTTONDOWN:
2439       key_status = KEY_PRESSED;
2440       break;
2441
2442     case SDL_CONTROLLERBUTTONUP:
2443       key_status = KEY_RELEASED;
2444       break;
2445
2446     default:
2447       return;
2448   }
2449
2450   switch (event->cbutton.button)
2451   {
2452     case SDL_CONTROLLER_BUTTON_START:
2453       key = KSYM_space;
2454       break;
2455
2456     case SDL_CONTROLLER_BUTTON_BACK:
2457       key = KSYM_Escape;
2458       break;
2459
2460     default:
2461       return;
2462   }
2463
2464   HandleKey(key, key_status);
2465 #endif
2466 }
2467
2468 void HandleSpecialGameControllerKeys(Key key, int key_status)
2469 {
2470 #if defined(TARGET_SDL2)
2471 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2472   int button = SDL_CONTROLLER_BUTTON_INVALID;
2473
2474   /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2475   if (key == KSYM_Rewind)
2476     button = SDL_CONTROLLER_BUTTON_A;
2477   else if (key == KSYM_FastForward || key == KSYM_Menu)
2478     button = SDL_CONTROLLER_BUTTON_B;
2479
2480   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2481   {
2482     Event event;
2483
2484     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2485                   SDL_CONTROLLERBUTTONUP);
2486
2487     event.cbutton.which = 0;    /* first joystick (Amazon Fire TV remote) */
2488     event.cbutton.button = button;
2489     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2490                            SDL_RELEASED);
2491
2492     HandleJoystickEvent(&event);
2493   }
2494 #endif
2495 #endif
2496 }
2497
2498 boolean DoKeysymAction(int keysym)
2499 {
2500   if (keysym < 0)
2501   {
2502     Key key = (Key)(-keysym);
2503
2504     HandleKey(key, KEY_PRESSED);
2505     HandleKey(key, KEY_RELEASED);
2506
2507     return TRUE;
2508   }
2509
2510   return FALSE;
2511 }