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