0c5b2bc1354f5ad40477daa9ed5ccde37b3ff689
[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     return;             // do not handle already processed keys again
2085
2086   switch (game_status)
2087   {
2088     case GAME_MODE_PSEUDO_TYPENAME:
2089       HandleTypeName(0, key);
2090       break;
2091
2092     case GAME_MODE_TITLE:
2093     case GAME_MODE_MAIN:
2094     case GAME_MODE_LEVELS:
2095     case GAME_MODE_LEVELNR:
2096     case GAME_MODE_SETUP:
2097     case GAME_MODE_INFO:
2098     case GAME_MODE_SCORES:
2099
2100       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2101         break;
2102
2103       switch (key)
2104       {
2105         case KSYM_space:
2106         case KSYM_Return:
2107           if (game_status == GAME_MODE_TITLE)
2108             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2109           else if (game_status == GAME_MODE_MAIN)
2110             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2111           else if (game_status == GAME_MODE_LEVELS)
2112             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2113           else if (game_status == GAME_MODE_LEVELNR)
2114             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2115           else if (game_status == GAME_MODE_SETUP)
2116             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2117           else if (game_status == GAME_MODE_INFO)
2118             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2119           else if (game_status == GAME_MODE_SCORES)
2120             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2121           break;
2122
2123         case KSYM_Escape:
2124           if (game_status != GAME_MODE_MAIN)
2125             FadeSkipNextFadeIn();
2126
2127           if (game_status == GAME_MODE_TITLE)
2128             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2129           else if (game_status == GAME_MODE_LEVELS)
2130             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2131           else if (game_status == GAME_MODE_LEVELNR)
2132             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2133           else if (game_status == GAME_MODE_SETUP)
2134             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2135           else if (game_status == GAME_MODE_INFO)
2136             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2137           else if (game_status == GAME_MODE_SCORES)
2138             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2139           break;
2140
2141         case KSYM_Page_Up:
2142           if (game_status == GAME_MODE_LEVELS)
2143             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2144           else if (game_status == GAME_MODE_LEVELNR)
2145             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2146           else if (game_status == GAME_MODE_SETUP)
2147             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2148           else if (game_status == GAME_MODE_INFO)
2149             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2150           else if (game_status == GAME_MODE_SCORES)
2151             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2152           break;
2153
2154         case KSYM_Page_Down:
2155           if (game_status == GAME_MODE_LEVELS)
2156             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2157           else if (game_status == GAME_MODE_LEVELNR)
2158             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2159           else if (game_status == GAME_MODE_SETUP)
2160             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2161           else if (game_status == GAME_MODE_INFO)
2162             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2163           else if (game_status == GAME_MODE_SCORES)
2164             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2165           break;
2166
2167         default:
2168           break;
2169       }
2170       break;
2171
2172     case GAME_MODE_EDITOR:
2173       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2174         HandleLevelEditorKeyInput(key);
2175       break;
2176
2177     case GAME_MODE_PLAYING:
2178     {
2179       switch (key)
2180       {
2181         case KSYM_Escape:
2182           RequestQuitGame(setup.ask_on_escape);
2183           break;
2184
2185         default:
2186           break;
2187       }
2188       break;
2189     }
2190
2191     default:
2192       if (key == KSYM_Escape)
2193       {
2194         SetGameStatus(GAME_MODE_MAIN);
2195
2196         DrawMainMenu();
2197
2198         return;
2199       }
2200   }
2201
2202   HandleKeysDebug(key);
2203 }
2204
2205 void HandleNoEvent(void)
2206 {
2207   HandleMouseCursor();
2208
2209   switch (game_status)
2210   {
2211     case GAME_MODE_PLAYING:
2212       HandleButtonOrFinger(-1, -1, -1);
2213       break;
2214   }
2215 }
2216
2217 void HandleEventActions(void)
2218 {
2219   // if (button_status && game_status != GAME_MODE_PLAYING)
2220   if (button_status && (game_status != GAME_MODE_PLAYING ||
2221                         tape.pausing ||
2222                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2223   {
2224     HandleButton(0, 0, button_status, -button_status);
2225   }
2226   else
2227   {
2228     HandleJoystick();
2229   }
2230
2231   if (network.enabled)
2232     HandleNetworking();
2233
2234   switch (game_status)
2235   {
2236     case GAME_MODE_MAIN:
2237       DrawPreviewLevelAnimation();
2238       break;
2239
2240     case GAME_MODE_EDITOR:
2241       HandleLevelEditorIdle();
2242       break;
2243
2244     default:
2245       break;
2246   }
2247 }
2248
2249 static void HandleTileCursor(int dx, int dy, int button)
2250 {
2251   if (!dx || !button)
2252     ClearPlayerMouseAction();
2253
2254   if (!dx && !dy)
2255     return;
2256
2257   if (button)
2258   {
2259     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2260                          (dx < 0 ? MB_LEFTBUTTON :
2261                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2262   }
2263   else if (!tile_cursor.moving)
2264   {
2265     int old_xpos = tile_cursor.xpos;
2266     int old_ypos = tile_cursor.ypos;
2267     int new_xpos = old_xpos;
2268     int new_ypos = old_ypos;
2269
2270     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2271       new_xpos = old_xpos + dx;
2272
2273     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2274       new_ypos = old_ypos + dy;
2275
2276     SetTileCursorTargetXY(new_xpos, new_ypos);
2277   }
2278 }
2279
2280 static int HandleJoystickForAllPlayers(void)
2281 {
2282   int i;
2283   int result = 0;
2284   boolean no_joysticks_configured = TRUE;
2285   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2286   static byte joy_action_last[MAX_PLAYERS];
2287
2288   for (i = 0; i < MAX_PLAYERS; i++)
2289     if (setup.input[i].use_joystick)
2290       no_joysticks_configured = FALSE;
2291
2292   // if no joysticks configured, map connected joysticks to players
2293   if (no_joysticks_configured)
2294     use_as_joystick_nr = TRUE;
2295
2296   for (i = 0; i < MAX_PLAYERS; i++)
2297   {
2298     byte joy_action = 0;
2299
2300     joy_action = JoystickExt(i, use_as_joystick_nr);
2301     result |= joy_action;
2302
2303     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2304         joy_action != joy_action_last[i])
2305       stored_player[i].action = joy_action;
2306
2307     joy_action_last[i] = joy_action;
2308   }
2309
2310   return result;
2311 }
2312
2313 void HandleJoystick(void)
2314 {
2315   static unsigned int joytest_delay = 0;
2316   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2317   static int joytest_last = 0;
2318   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2319   int delay_value       = GADGET_FRAME_DELAY;
2320   int joystick  = HandleJoystickForAllPlayers();
2321   int keyboard  = key_joystick_mapping;
2322   int joy       = (joystick | keyboard);
2323   int joytest   = joystick;
2324   int left      = joy & JOY_LEFT;
2325   int right     = joy & JOY_RIGHT;
2326   int up        = joy & JOY_UP;
2327   int down      = joy & JOY_DOWN;
2328   int button    = joy & JOY_BUTTON;
2329   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2330   int dx        = (left ? -1    : right ? 1     : 0);
2331   int dy        = (up   ? -1    : down  ? 1     : 0);
2332   boolean use_delay_value_first = (joytest != joytest_last);
2333
2334   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2335   {
2336     // do not handle this button event anymore
2337     return;
2338   }
2339
2340   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2341   {
2342     if (game_status == GAME_MODE_PLAYING)
2343     {
2344       // when playing MM style levels, also use delay for keyboard events
2345       joytest |= keyboard;
2346
2347       // only use first delay value for new events, but not for changed events
2348       use_delay_value_first = (!joytest != !joytest_last);
2349
2350       // only use delay after the initial keyboard event
2351       delay_value = 0;
2352     }
2353
2354     // for any joystick or keyboard event, enable playfield tile cursor
2355     if (dx || dy || button)
2356       SetTileCursorEnabled(TRUE);
2357   }
2358
2359   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2360   {
2361     // delay joystick/keyboard actions if axes/keys continually pressed
2362     newbutton = dx = dy = 0;
2363   }
2364   else
2365   {
2366     // first start with longer delay, then continue with shorter delay
2367     joytest_delay_value =
2368       (use_delay_value_first ? delay_value_first : delay_value);
2369   }
2370
2371   joytest_last = joytest;
2372
2373   switch (game_status)
2374   {
2375     case GAME_MODE_TITLE:
2376     case GAME_MODE_MAIN:
2377     case GAME_MODE_LEVELS:
2378     case GAME_MODE_LEVELNR:
2379     case GAME_MODE_SETUP:
2380     case GAME_MODE_INFO:
2381     case GAME_MODE_SCORES:
2382     {
2383       if (anyTextGadgetActive())
2384         break;
2385
2386       if (game_status == GAME_MODE_TITLE)
2387         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2388       else if (game_status == GAME_MODE_MAIN)
2389         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2390       else if (game_status == GAME_MODE_LEVELS)
2391         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2392       else if (game_status == GAME_MODE_LEVELNR)
2393         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2394       else if (game_status == GAME_MODE_SETUP)
2395         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2396       else if (game_status == GAME_MODE_INFO)
2397         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2398       else if (game_status == GAME_MODE_SCORES)
2399         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2400
2401       break;
2402     }
2403
2404     case GAME_MODE_PLAYING:
2405 #if 0
2406       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2407       if (tape.playing || keyboard)
2408         newbutton = ((joy & JOY_BUTTON) != 0);
2409 #endif
2410
2411       if (newbutton && game.all_players_gone)
2412       {
2413         GameEnd();
2414
2415         return;
2416       }
2417
2418       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2419       {
2420         if (joystick & JOY_ACTION)
2421           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2422       }
2423       else if (tape.recording && tape.pausing && !tape.use_mouse)
2424       {
2425         if (joystick & JOY_ACTION)
2426           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2427       }
2428
2429       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2430         HandleTileCursor(dx, dy, button);
2431
2432       break;
2433
2434     default:
2435       break;
2436   }
2437 }
2438
2439 void HandleSpecialGameControllerButtons(Event *event)
2440 {
2441   int key_status;
2442   Key key;
2443
2444   switch (event->type)
2445   {
2446     case SDL_CONTROLLERBUTTONDOWN:
2447       key_status = KEY_PRESSED;
2448       break;
2449
2450     case SDL_CONTROLLERBUTTONUP:
2451       key_status = KEY_RELEASED;
2452       break;
2453
2454     default:
2455       return;
2456   }
2457
2458   switch (event->cbutton.button)
2459   {
2460     case SDL_CONTROLLER_BUTTON_START:
2461       key = KSYM_space;
2462       break;
2463
2464     case SDL_CONTROLLER_BUTTON_BACK:
2465       key = KSYM_Escape;
2466       break;
2467
2468     default:
2469       return;
2470   }
2471
2472   HandleKey(key, key_status);
2473 }
2474
2475 void HandleSpecialGameControllerKeys(Key key, int key_status)
2476 {
2477 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2478   int button = SDL_CONTROLLER_BUTTON_INVALID;
2479
2480   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2481   if (key == KSYM_Rewind)
2482     button = SDL_CONTROLLER_BUTTON_A;
2483   else if (key == KSYM_FastForward || key == KSYM_Menu)
2484     button = SDL_CONTROLLER_BUTTON_B;
2485
2486   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2487   {
2488     Event event;
2489
2490     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2491                   SDL_CONTROLLERBUTTONUP);
2492
2493     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2494     event.cbutton.button = button;
2495     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2496                            SDL_RELEASED);
2497
2498     HandleJoystickEvent(&event);
2499   }
2500 #endif
2501 }
2502
2503 boolean DoKeysymAction(int keysym)
2504 {
2505   if (keysym < 0)
2506   {
2507     Key key = (Key)(-keysym);
2508
2509     HandleKey(key, KEY_PRESSED);
2510     HandleKey(key, KEY_RELEASED);
2511
2512     return TRUE;
2513   }
2514
2515   return FALSE;
2516 }