removed support for SDL 1.2
[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 void HandleKeysDebug(Key key)
1782 {
1783 #ifdef DEBUG
1784   int i;
1785
1786   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1787   {
1788     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1789
1790     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1791     {
1792       if (key == setup.debug.frame_delay_key[i] &&
1793           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1794       {
1795         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1796                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1797
1798         if (!setup.debug.frame_delay_game_only)
1799           MenuFrameDelay = GameFrameDelay;
1800
1801         SetVideoFrameDelay(GameFrameDelay);
1802
1803         if (GameFrameDelay > ONE_SECOND_DELAY)
1804           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1805         else if (GameFrameDelay != 0)
1806           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1807                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1808                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1809         else
1810           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1811
1812         break;
1813       }
1814     }
1815   }
1816
1817   if (game_status == GAME_MODE_PLAYING)
1818   {
1819     if (key == KSYM_d)
1820     {
1821       options.debug = !options.debug;
1822
1823       Error(ERR_DEBUG, "debug mode %s",
1824             (options.debug ? "enabled" : "disabled"));
1825     }
1826     else if (key == KSYM_v)
1827     {
1828       Error(ERR_DEBUG, "currently using game engine version %d",
1829             game.engine_version);
1830     }
1831   }
1832 #endif
1833 }
1834
1835 void HandleKey(Key key, int key_status)
1836 {
1837   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1838   static boolean ignore_repeated_key = FALSE;
1839   static struct SetupKeyboardInfo ski;
1840   static struct SetupShortcutInfo ssi;
1841   static struct
1842   {
1843     Key *key_custom;
1844     Key *key_snap;
1845     Key key_default;
1846     byte action;
1847   } key_info[] =
1848   {
1849     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1850     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1851     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1852     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1853     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1854     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1855   };
1856   int joy = 0;
1857   int i;
1858
1859   // map special keys (media keys / remote control buttons) to default keys
1860   if (key == KSYM_PlayPause)
1861     key = KSYM_space;
1862   else if (key == KSYM_Select)
1863     key = KSYM_Return;
1864
1865   HandleSpecialGameControllerKeys(key, key_status);
1866
1867   if (game_status == GAME_MODE_PLAYING)
1868   {
1869     // only needed for single-step tape recording mode
1870     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1871     int pnr;
1872
1873     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1874     {
1875       byte key_action = 0;
1876
1877       if (setup.input[pnr].use_joystick)
1878         continue;
1879
1880       ski = setup.input[pnr].key;
1881
1882       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1883         if (key == *key_info[i].key_custom)
1884           key_action |= key_info[i].action;
1885
1886       // use combined snap+direction keys for the first player only
1887       if (pnr == 0)
1888       {
1889         ssi = setup.shortcut;
1890
1891         for (i = 0; i < NUM_DIRECTIONS; i++)
1892           if (key == *key_info[i].key_snap)
1893             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1894       }
1895
1896       if (key_status == KEY_PRESSED)
1897         stored_player[pnr].action |= key_action;
1898       else
1899         stored_player[pnr].action &= ~key_action;
1900
1901       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1902       {
1903         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1904         {
1905           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1906
1907           // if snap key already pressed, keep pause mode when releasing
1908           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1909             has_snapped[pnr] = TRUE;
1910         }
1911         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1912         {
1913           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1914
1915           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1916               getRedDiskReleaseFlag_SP() == 0)
1917           {
1918             // add a single inactive frame before dropping starts
1919             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1920             stored_player[pnr].force_dropping = TRUE;
1921           }
1922         }
1923         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1924         {
1925           // if snap key was pressed without direction, leave pause mode
1926           if (!has_snapped[pnr])
1927             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1928
1929           has_snapped[pnr] = FALSE;
1930         }
1931       }
1932       else if (tape.recording && tape.pausing && !tape.use_mouse)
1933       {
1934         // prevent key release events from un-pausing a paused game
1935         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1936           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1937       }
1938
1939       // for MM style levels, handle in-game keyboard input in HandleJoystick()
1940       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1941         joy |= key_action;
1942     }
1943   }
1944   else
1945   {
1946     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1947       if (key == key_info[i].key_default)
1948         joy |= key_info[i].action;
1949   }
1950
1951   if (joy)
1952   {
1953     if (key_status == KEY_PRESSED)
1954       key_joystick_mapping |= joy;
1955     else
1956       key_joystick_mapping &= ~joy;
1957
1958     HandleJoystick();
1959   }
1960
1961   if (game_status != GAME_MODE_PLAYING)
1962     key_joystick_mapping = 0;
1963
1964   if (key_status == KEY_RELEASED)
1965   {
1966     // reset flag to ignore repeated "key pressed" events after key release
1967     ignore_repeated_key = FALSE;
1968
1969     return;
1970   }
1971
1972   if ((key == KSYM_F11 ||
1973        ((key == KSYM_Return ||
1974          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1975       video.fullscreen_available &&
1976       !ignore_repeated_key)
1977   {
1978     setup.fullscreen = !setup.fullscreen;
1979
1980     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1981
1982     if (game_status == GAME_MODE_SETUP)
1983       RedrawSetupScreenAfterFullscreenToggle();
1984
1985     // set flag to ignore repeated "key pressed" events
1986     ignore_repeated_key = TRUE;
1987
1988     return;
1989   }
1990
1991   if ((key == KSYM_0     || key == KSYM_KP_0 ||
1992        key == KSYM_minus || key == KSYM_KP_Subtract ||
1993        key == KSYM_plus  || key == KSYM_KP_Add ||
1994        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
1995       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1996       video.window_scaling_available &&
1997       !video.fullscreen_enabled)
1998   {
1999     if (key == KSYM_0 || key == KSYM_KP_0)
2000       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2001     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2002       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2003     else
2004       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2005
2006     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2007       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2008     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2009       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2010
2011     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2012
2013     if (game_status == GAME_MODE_SETUP)
2014       RedrawSetupScreenAfterFullscreenToggle();
2015
2016     return;
2017   }
2018
2019   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2020                                       key == KSYM_Return ||
2021                                       key == KSYM_Escape)))
2022   {
2023     // do not handle this key event anymore
2024     if (key != KSYM_Escape)     // always allow ESC key to be handled
2025       return;
2026   }
2027
2028   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2029       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2030   {
2031     GameEnd();
2032
2033     return;
2034   }
2035
2036   if (game_status == GAME_MODE_MAIN &&
2037       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2038   {
2039     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2040
2041     return;
2042   }
2043
2044   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2045   {
2046     if (key == setup.shortcut.save_game)
2047       TapeQuickSave();
2048     else if (key == setup.shortcut.load_game)
2049       TapeQuickLoad();
2050     else if (key == setup.shortcut.toggle_pause)
2051       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2052
2053     HandleTapeButtonKeys(key);
2054     HandleSoundButtonKeys(key);
2055   }
2056
2057   if (game_status == GAME_MODE_PLAYING && !network_playing)
2058   {
2059     int centered_player_nr_next = -999;
2060
2061     if (key == setup.shortcut.focus_player_all)
2062       centered_player_nr_next = -1;
2063     else
2064       for (i = 0; i < MAX_PLAYERS; i++)
2065         if (key == setup.shortcut.focus_player[i])
2066           centered_player_nr_next = i;
2067
2068     if (centered_player_nr_next != -999)
2069     {
2070       game.centered_player_nr_next = centered_player_nr_next;
2071       game.set_centered_player = TRUE;
2072
2073       if (tape.recording)
2074       {
2075         tape.centered_player_nr_next = game.centered_player_nr_next;
2076         tape.set_centered_player = TRUE;
2077       }
2078     }
2079   }
2080
2081   HandleKeysSpecial(key);
2082
2083   if (HandleGadgetsKeyInput(key))
2084   {
2085     if (key != KSYM_Escape)     // always allow ESC key to be handled
2086       key = KSYM_UNDEFINED;
2087   }
2088
2089   switch (game_status)
2090   {
2091     case GAME_MODE_PSEUDO_TYPENAME:
2092       HandleTypeName(0, key);
2093       break;
2094
2095     case GAME_MODE_TITLE:
2096     case GAME_MODE_MAIN:
2097     case GAME_MODE_LEVELS:
2098     case GAME_MODE_LEVELNR:
2099     case GAME_MODE_SETUP:
2100     case GAME_MODE_INFO:
2101     case GAME_MODE_SCORES:
2102
2103       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2104         break;
2105
2106       switch (key)
2107       {
2108         case KSYM_space:
2109         case KSYM_Return:
2110           if (game_status == GAME_MODE_TITLE)
2111             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2112           else if (game_status == GAME_MODE_MAIN)
2113             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2114           else if (game_status == GAME_MODE_LEVELS)
2115             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2116           else if (game_status == GAME_MODE_LEVELNR)
2117             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2118           else if (game_status == GAME_MODE_SETUP)
2119             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2120           else if (game_status == GAME_MODE_INFO)
2121             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2122           else if (game_status == GAME_MODE_SCORES)
2123             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2124           break;
2125
2126         case KSYM_Escape:
2127           if (game_status != GAME_MODE_MAIN)
2128             FadeSkipNextFadeIn();
2129
2130           if (game_status == GAME_MODE_TITLE)
2131             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2132           else if (game_status == GAME_MODE_LEVELS)
2133             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2134           else if (game_status == GAME_MODE_LEVELNR)
2135             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2136           else if (game_status == GAME_MODE_SETUP)
2137             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2138           else if (game_status == GAME_MODE_INFO)
2139             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2140           else if (game_status == GAME_MODE_SCORES)
2141             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2142           break;
2143
2144         case KSYM_Page_Up:
2145           if (game_status == GAME_MODE_LEVELS)
2146             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2147           else if (game_status == GAME_MODE_LEVELNR)
2148             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2149           else if (game_status == GAME_MODE_SETUP)
2150             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2151           else if (game_status == GAME_MODE_INFO)
2152             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2153           else if (game_status == GAME_MODE_SCORES)
2154             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2155           break;
2156
2157         case KSYM_Page_Down:
2158           if (game_status == GAME_MODE_LEVELS)
2159             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2160           else if (game_status == GAME_MODE_LEVELNR)
2161             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2162           else if (game_status == GAME_MODE_SETUP)
2163             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2164           else if (game_status == GAME_MODE_INFO)
2165             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2166           else if (game_status == GAME_MODE_SCORES)
2167             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2168           break;
2169
2170         default:
2171           break;
2172       }
2173       break;
2174
2175     case GAME_MODE_EDITOR:
2176       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2177         HandleLevelEditorKeyInput(key);
2178       break;
2179
2180     case GAME_MODE_PLAYING:
2181     {
2182       switch (key)
2183       {
2184         case KSYM_Escape:
2185           RequestQuitGame(setup.ask_on_escape);
2186           break;
2187
2188         default:
2189           break;
2190       }
2191       break;
2192     }
2193
2194     default:
2195       if (key == KSYM_Escape)
2196       {
2197         SetGameStatus(GAME_MODE_MAIN);
2198
2199         DrawMainMenu();
2200
2201         return;
2202       }
2203   }
2204
2205   HandleKeysDebug(key);
2206 }
2207
2208 void HandleNoEvent(void)
2209 {
2210   HandleMouseCursor();
2211
2212   switch (game_status)
2213   {
2214     case GAME_MODE_PLAYING:
2215       HandleButtonOrFinger(-1, -1, -1);
2216       break;
2217   }
2218 }
2219
2220 void HandleEventActions(void)
2221 {
2222   // if (button_status && game_status != GAME_MODE_PLAYING)
2223   if (button_status && (game_status != GAME_MODE_PLAYING ||
2224                         tape.pausing ||
2225                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2226   {
2227     HandleButton(0, 0, button_status, -button_status);
2228   }
2229   else
2230   {
2231     HandleJoystick();
2232   }
2233
2234   if (network.enabled)
2235     HandleNetworking();
2236
2237   switch (game_status)
2238   {
2239     case GAME_MODE_MAIN:
2240       DrawPreviewLevelAnimation();
2241       break;
2242
2243     case GAME_MODE_EDITOR:
2244       HandleLevelEditorIdle();
2245       break;
2246
2247     default:
2248       break;
2249   }
2250 }
2251
2252 static void HandleTileCursor(int dx, int dy, int button)
2253 {
2254   if (!dx || !button)
2255     ClearPlayerMouseAction();
2256
2257   if (!dx && !dy)
2258     return;
2259
2260   if (button)
2261   {
2262     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2263                          (dx < 0 ? MB_LEFTBUTTON :
2264                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2265   }
2266   else if (!tile_cursor.moving)
2267   {
2268     int old_xpos = tile_cursor.xpos;
2269     int old_ypos = tile_cursor.ypos;
2270     int new_xpos = old_xpos;
2271     int new_ypos = old_ypos;
2272
2273     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2274       new_xpos = old_xpos + dx;
2275
2276     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2277       new_ypos = old_ypos + dy;
2278
2279     SetTileCursorTargetXY(new_xpos, new_ypos);
2280   }
2281 }
2282
2283 static int HandleJoystickForAllPlayers(void)
2284 {
2285   int i;
2286   int result = 0;
2287   boolean no_joysticks_configured = TRUE;
2288   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2289   static byte joy_action_last[MAX_PLAYERS];
2290
2291   for (i = 0; i < MAX_PLAYERS; i++)
2292     if (setup.input[i].use_joystick)
2293       no_joysticks_configured = FALSE;
2294
2295   // if no joysticks configured, map connected joysticks to players
2296   if (no_joysticks_configured)
2297     use_as_joystick_nr = TRUE;
2298
2299   for (i = 0; i < MAX_PLAYERS; i++)
2300   {
2301     byte joy_action = 0;
2302
2303     joy_action = JoystickExt(i, use_as_joystick_nr);
2304     result |= joy_action;
2305
2306     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2307         joy_action != joy_action_last[i])
2308       stored_player[i].action = joy_action;
2309
2310     joy_action_last[i] = joy_action;
2311   }
2312
2313   return result;
2314 }
2315
2316 void HandleJoystick(void)
2317 {
2318   static unsigned int joytest_delay = 0;
2319   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2320   static int joytest_last = 0;
2321   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2322   int delay_value       = GADGET_FRAME_DELAY;
2323   int joystick  = HandleJoystickForAllPlayers();
2324   int keyboard  = key_joystick_mapping;
2325   int joy       = (joystick | keyboard);
2326   int joytest   = joystick;
2327   int left      = joy & JOY_LEFT;
2328   int right     = joy & JOY_RIGHT;
2329   int up        = joy & JOY_UP;
2330   int down      = joy & JOY_DOWN;
2331   int button    = joy & JOY_BUTTON;
2332   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2333   int dx        = (left ? -1    : right ? 1     : 0);
2334   int dy        = (up   ? -1    : down  ? 1     : 0);
2335   boolean use_delay_value_first = (joytest != joytest_last);
2336
2337   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2338   {
2339     // do not handle this button event anymore
2340     return;
2341   }
2342
2343   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2344   {
2345     if (game_status == GAME_MODE_PLAYING)
2346     {
2347       // when playing MM style levels, also use delay for keyboard events
2348       joytest |= keyboard;
2349
2350       // only use first delay value for new events, but not for changed events
2351       use_delay_value_first = (!joytest != !joytest_last);
2352
2353       // only use delay after the initial keyboard event
2354       delay_value = 0;
2355     }
2356
2357     // for any joystick or keyboard event, enable playfield tile cursor
2358     if (dx || dy || button)
2359       SetTileCursorEnabled(TRUE);
2360   }
2361
2362   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2363   {
2364     // delay joystick/keyboard actions if axes/keys continually pressed
2365     newbutton = dx = dy = 0;
2366   }
2367   else
2368   {
2369     // first start with longer delay, then continue with shorter delay
2370     joytest_delay_value =
2371       (use_delay_value_first ? delay_value_first : delay_value);
2372   }
2373
2374   joytest_last = joytest;
2375
2376   switch (game_status)
2377   {
2378     case GAME_MODE_TITLE:
2379     case GAME_MODE_MAIN:
2380     case GAME_MODE_LEVELS:
2381     case GAME_MODE_LEVELNR:
2382     case GAME_MODE_SETUP:
2383     case GAME_MODE_INFO:
2384     case GAME_MODE_SCORES:
2385     {
2386       if (anyTextGadgetActive())
2387         break;
2388
2389       if (game_status == GAME_MODE_TITLE)
2390         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2391       else if (game_status == GAME_MODE_MAIN)
2392         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2393       else if (game_status == GAME_MODE_LEVELS)
2394         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2395       else if (game_status == GAME_MODE_LEVELNR)
2396         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2397       else if (game_status == GAME_MODE_SETUP)
2398         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2399       else if (game_status == GAME_MODE_INFO)
2400         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2401       else if (game_status == GAME_MODE_SCORES)
2402         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2403
2404       break;
2405     }
2406
2407     case GAME_MODE_PLAYING:
2408 #if 0
2409       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2410       if (tape.playing || keyboard)
2411         newbutton = ((joy & JOY_BUTTON) != 0);
2412 #endif
2413
2414       if (newbutton && game.all_players_gone)
2415       {
2416         GameEnd();
2417
2418         return;
2419       }
2420
2421       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2422       {
2423         if (joystick & JOY_ACTION)
2424           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2425       }
2426       else if (tape.recording && tape.pausing && !tape.use_mouse)
2427       {
2428         if (joystick & JOY_ACTION)
2429           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2430       }
2431
2432       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2433         HandleTileCursor(dx, dy, button);
2434
2435       break;
2436
2437     default:
2438       break;
2439   }
2440 }
2441
2442 void HandleSpecialGameControllerButtons(Event *event)
2443 {
2444   int key_status;
2445   Key key;
2446
2447   switch (event->type)
2448   {
2449     case SDL_CONTROLLERBUTTONDOWN:
2450       key_status = KEY_PRESSED;
2451       break;
2452
2453     case SDL_CONTROLLERBUTTONUP:
2454       key_status = KEY_RELEASED;
2455       break;
2456
2457     default:
2458       return;
2459   }
2460
2461   switch (event->cbutton.button)
2462   {
2463     case SDL_CONTROLLER_BUTTON_START:
2464       key = KSYM_space;
2465       break;
2466
2467     case SDL_CONTROLLER_BUTTON_BACK:
2468       key = KSYM_Escape;
2469       break;
2470
2471     default:
2472       return;
2473   }
2474
2475   HandleKey(key, key_status);
2476 }
2477
2478 void HandleSpecialGameControllerKeys(Key key, int key_status)
2479 {
2480 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2481   int button = SDL_CONTROLLER_BUTTON_INVALID;
2482
2483   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2484   if (key == KSYM_Rewind)
2485     button = SDL_CONTROLLER_BUTTON_A;
2486   else if (key == KSYM_FastForward || key == KSYM_Menu)
2487     button = SDL_CONTROLLER_BUTTON_B;
2488
2489   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2490   {
2491     Event event;
2492
2493     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2494                   SDL_CONTROLLERBUTTONUP);
2495
2496     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2497     event.cbutton.button = button;
2498     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2499                            SDL_RELEASED);
2500
2501     HandleJoystickEvent(&event);
2502   }
2503 #endif
2504 }
2505
2506 boolean DoKeysymAction(int keysym)
2507 {
2508   if (keysym < 0)
2509   {
2510     Key key = (Key)(-keysym);
2511
2512     HandleKey(key, KEY_PRESSED);
2513     HandleKey(key, KEY_RELEASED);
2514
2515     return TRUE;
2516   }
2517
2518   return FALSE;
2519 }