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