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