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