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