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