added toggling virtual buttons using 'menu' button on Android
[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 if (key == KSYM_Menu)
1456   {
1457     // the "menu" button can be used to toggle displaying virtual buttons
1458     if (key_status == KEY_PRESSED)
1459       SetOverlayEnabled(!GetOverlayEnabled());
1460   }
1461   else
1462   {
1463     // for any other "real" key event, disable virtual buttons
1464     SetOverlayEnabled(FALSE);
1465   }
1466 #endif
1467
1468   HandleKeyModState(keymod, key_status);
1469
1470   // only handle raw key input without text modifier keys pressed
1471   if (!checkTextInputKeyModState())
1472     HandleKey(key, key_status);
1473 }
1474
1475 void HandleFocusEvent(FocusChangeEvent *event)
1476 {
1477   static int old_joystick_status = -1;
1478
1479   if (event->type == EVENT_FOCUSOUT)
1480   {
1481     KeyboardAutoRepeatOn();
1482     old_joystick_status = joystick.status;
1483     joystick.status = JOYSTICK_NOT_AVAILABLE;
1484
1485     ClearPlayerAction();
1486   }
1487   else if (event->type == EVENT_FOCUSIN)
1488   {
1489     /* When there are two Rocks'n'Diamonds windows which overlap and
1490        the player moves the pointer from one game window to the other,
1491        a 'FocusOut' event is generated for the window the pointer is
1492        leaving and a 'FocusIn' event is generated for the window the
1493        pointer is entering. In some cases, it can happen that the
1494        'FocusIn' event is handled by the one game process before the
1495        'FocusOut' event by the other game process. In this case the
1496        X11 environment would end up with activated keyboard auto repeat,
1497        because unfortunately this is a global setting and not (which
1498        would be far better) set for each X11 window individually.
1499        The effect would be keyboard auto repeat while playing the game
1500        (game_status == GAME_MODE_PLAYING), which is not desired.
1501        To avoid this special case, we just wait 1/10 second before
1502        processing the 'FocusIn' event. */
1503
1504     if (game_status == GAME_MODE_PLAYING)
1505     {
1506       Delay(100);
1507       KeyboardAutoRepeatOffUnlessAutoplay();
1508     }
1509
1510     if (old_joystick_status != -1)
1511       joystick.status = old_joystick_status;
1512   }
1513 }
1514
1515 void HandleClientMessageEvent(ClientMessageEvent *event)
1516 {
1517   if (CheckCloseWindowEvent(event))
1518     CloseAllAndExit(0);
1519 }
1520
1521 void HandleWindowManagerEvent(Event *event)
1522 {
1523   SDLHandleWindowManagerEvent(event);
1524 }
1525
1526 void HandleButton(int mx, int my, int button, int button_nr)
1527 {
1528   static int old_mx = 0, old_my = 0;
1529   boolean button_hold = FALSE;
1530   boolean handle_gadgets = TRUE;
1531
1532   if (button_nr < 0)
1533   {
1534     mx = old_mx;
1535     my = old_my;
1536     button_nr = -button_nr;
1537     button_hold = TRUE;
1538   }
1539   else
1540   {
1541     old_mx = mx;
1542     old_my = my;
1543   }
1544
1545 #if defined(PLATFORM_ANDROID)
1546   // when playing, only handle gadgets when using "follow finger" controls
1547   // or when using touch controls in combination with the MM game engine
1548   handle_gadgets =
1549     (game_status != GAME_MODE_PLAYING ||
1550      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1551      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1552 #endif
1553
1554   if (HandleGlobalAnimClicks(mx, my, button))
1555   {
1556     // do not handle this button event anymore
1557     return;             // force mouse event not to be handled at all
1558   }
1559
1560   if (handle_gadgets && HandleGadgets(mx, my, button))
1561   {
1562     // do not handle this button event anymore
1563     mx = my = -32;      // force mouse event to be outside screen tiles
1564   }
1565
1566   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1567     return;
1568
1569   // do not use scroll wheel button events for anything other than gadgets
1570   if (IS_WHEEL_BUTTON(button_nr))
1571     return;
1572
1573   switch (game_status)
1574   {
1575     case GAME_MODE_TITLE:
1576       HandleTitleScreen(mx, my, 0, 0, button);
1577       break;
1578
1579     case GAME_MODE_MAIN:
1580       HandleMainMenu(mx, my, 0, 0, button);
1581       break;
1582
1583     case GAME_MODE_PSEUDO_TYPENAME:
1584       HandleTypeName(0, KSYM_Return);
1585       break;
1586
1587     case GAME_MODE_LEVELS:
1588       HandleChooseLevelSet(mx, my, 0, 0, button);
1589       break;
1590
1591     case GAME_MODE_LEVELNR:
1592       HandleChooseLevelNr(mx, my, 0, 0, button);
1593       break;
1594
1595     case GAME_MODE_SCORES:
1596       HandleHallOfFame(0, 0, 0, 0, button);
1597       break;
1598
1599     case GAME_MODE_EDITOR:
1600       HandleLevelEditorIdle();
1601       break;
1602
1603     case GAME_MODE_INFO:
1604       HandleInfoScreen(mx, my, 0, 0, button);
1605       break;
1606
1607     case GAME_MODE_SETUP:
1608       HandleSetupScreen(mx, my, 0, 0, button);
1609       break;
1610
1611     case GAME_MODE_PLAYING:
1612       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1613         HandleButtonOrFinger(mx, my, button);
1614       else
1615         SetPlayerMouseAction(mx, my, button);
1616
1617 #ifdef DEBUG
1618       if (button == MB_PRESSED && !motion_status && !button_hold &&
1619           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1620         DumpTileFromScreen(mx, my);
1621 #endif
1622
1623       break;
1624
1625     default:
1626       break;
1627   }
1628 }
1629
1630 static boolean is_string_suffix(char *string, char *suffix)
1631 {
1632   int string_len = strlen(string);
1633   int suffix_len = strlen(suffix);
1634
1635   if (suffix_len > string_len)
1636     return FALSE;
1637
1638   return (strEqual(&string[string_len - suffix_len], suffix));
1639 }
1640
1641 #define MAX_CHEAT_INPUT_LEN     32
1642
1643 static void HandleKeysSpecial(Key key)
1644 {
1645   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1646   char letter = getCharFromKey(key);
1647   int cheat_input_len = strlen(cheat_input);
1648   int i;
1649
1650   if (letter == 0)
1651     return;
1652
1653   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1654   {
1655     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1656       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1657
1658     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1659   }
1660
1661   cheat_input[cheat_input_len++] = letter;
1662   cheat_input[cheat_input_len] = '\0';
1663
1664 #if DEBUG_EVENTS_KEY
1665   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1666 #endif
1667
1668   if (game_status == GAME_MODE_MAIN)
1669   {
1670     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1671         is_string_suffix(cheat_input, ":ist"))
1672     {
1673       InsertSolutionTape();
1674     }
1675     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1676              is_string_suffix(cheat_input, ":pst"))
1677     {
1678       PlaySolutionTape();
1679     }
1680     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1681              is_string_suffix(cheat_input, ":rg"))
1682     {
1683       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1684       DrawMainMenu();
1685     }
1686     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1687              is_string_suffix(cheat_input, ":rs"))
1688     {
1689       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1690       DrawMainMenu();
1691     }
1692     else if (is_string_suffix(cheat_input, ":reload-music") ||
1693              is_string_suffix(cheat_input, ":rm"))
1694     {
1695       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1696       DrawMainMenu();
1697     }
1698     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1699              is_string_suffix(cheat_input, ":ra"))
1700     {
1701       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1702                           1 << ARTWORK_TYPE_SOUNDS |
1703                           1 << ARTWORK_TYPE_MUSIC);
1704       DrawMainMenu();
1705     }
1706     else if (is_string_suffix(cheat_input, ":dump-level") ||
1707              is_string_suffix(cheat_input, ":dl"))
1708     {
1709       DumpLevel(&level);
1710     }
1711     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1712              is_string_suffix(cheat_input, ":dt"))
1713     {
1714       DumpTape(&tape);
1715     }
1716     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1717              is_string_suffix(cheat_input, ":ft"))
1718     {
1719       /* fix single-player tapes that contain player input for more than one
1720          player (due to a bug in 3.3.1.2 and earlier versions), which results
1721          in playing levels with more than one player in multi-player mode,
1722          even though the tape was originally recorded in single-player mode */
1723
1724       // remove player input actions for all players but the first one
1725       for (i = 1; i < MAX_PLAYERS; i++)
1726         tape.player_participates[i] = FALSE;
1727
1728       tape.changed = TRUE;
1729     }
1730     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1731              is_string_suffix(cheat_input, ":snl"))
1732     {
1733       SaveNativeLevel(&level);
1734     }
1735     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1736              is_string_suffix(cheat_input, ":fps"))
1737     {
1738       global.show_frames_per_second = !global.show_frames_per_second;
1739     }
1740   }
1741   else if (game_status == GAME_MODE_PLAYING)
1742   {
1743 #ifdef DEBUG
1744     if (is_string_suffix(cheat_input, ".q"))
1745       DEBUG_SetMaximumDynamite();
1746 #endif
1747   }
1748   else if (game_status == GAME_MODE_EDITOR)
1749   {
1750     if (is_string_suffix(cheat_input, ":dump-brush") ||
1751         is_string_suffix(cheat_input, ":DB"))
1752     {
1753       DumpBrush();
1754     }
1755     else if (is_string_suffix(cheat_input, ":DDB"))
1756     {
1757       DumpBrush_Small();
1758     }
1759
1760     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1761     {
1762       if (letter == 'x')        // copy brush to clipboard (small size)
1763       {
1764         CopyBrushToClipboard_Small();
1765       }
1766       else if (letter == 'c')   // copy brush to clipboard (normal size)
1767       {
1768         CopyBrushToClipboard();
1769       }
1770       else if (letter == 'v')   // paste brush from Clipboard
1771       {
1772         CopyClipboardToBrush();
1773       }
1774     }
1775   }
1776
1777   // special key shortcuts for all game modes
1778   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1779       is_string_suffix(cheat_input, ":dea") ||
1780       is_string_suffix(cheat_input, ":DEA"))
1781   {
1782     DumpGadgetIdentifiers();
1783     DumpScreenIdentifiers();
1784   }
1785 }
1786
1787 boolean HandleKeysDebug(Key key, int key_status)
1788 {
1789 #ifdef DEBUG
1790   int i;
1791
1792   if (key_status != KEY_PRESSED)
1793     return FALSE;
1794
1795   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1796   {
1797     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1798
1799     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1800     {
1801       if (key == setup.debug.frame_delay_key[i] &&
1802           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1803       {
1804         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1805                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1806
1807         if (!setup.debug.frame_delay_game_only)
1808           MenuFrameDelay = GameFrameDelay;
1809
1810         SetVideoFrameDelay(GameFrameDelay);
1811
1812         if (GameFrameDelay > ONE_SECOND_DELAY)
1813           Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1814         else if (GameFrameDelay != 0)
1815           Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1816                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1817                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1818         else
1819           Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1820
1821         return TRUE;
1822       }
1823     }
1824   }
1825
1826   if (game_status == GAME_MODE_PLAYING)
1827   {
1828     if (key == KSYM_d)
1829     {
1830       options.debug = !options.debug;
1831
1832       Error(ERR_DEBUG, "debug mode %s",
1833             (options.debug ? "enabled" : "disabled"));
1834
1835       return TRUE;
1836     }
1837     else if (key == KSYM_v)
1838     {
1839       Error(ERR_DEBUG, "currently using game engine version %d",
1840             game.engine_version);
1841
1842       return TRUE;
1843     }
1844   }
1845
1846   return FALSE;
1847 #endif
1848 }
1849
1850 void HandleKey(Key key, int key_status)
1851 {
1852   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1853   static boolean ignore_repeated_key = FALSE;
1854   static struct SetupKeyboardInfo ski;
1855   static struct SetupShortcutInfo ssi;
1856   static struct
1857   {
1858     Key *key_custom;
1859     Key *key_snap;
1860     Key key_default;
1861     byte action;
1862   } key_info[] =
1863   {
1864     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
1865     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
1866     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
1867     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
1868     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
1869     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
1870   };
1871   int joy = 0;
1872   int i;
1873
1874   if (HandleKeysDebug(key, key_status))
1875     return;             // do not handle already processed keys again
1876
1877   // map special keys (media keys / remote control buttons) to default keys
1878   if (key == KSYM_PlayPause)
1879     key = KSYM_space;
1880   else if (key == KSYM_Select)
1881     key = KSYM_Return;
1882
1883   HandleSpecialGameControllerKeys(key, key_status);
1884
1885   if (game_status == GAME_MODE_PLAYING)
1886   {
1887     // only needed for single-step tape recording mode
1888     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1889     int pnr;
1890
1891     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1892     {
1893       byte key_action = 0;
1894
1895       if (setup.input[pnr].use_joystick)
1896         continue;
1897
1898       ski = setup.input[pnr].key;
1899
1900       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1901         if (key == *key_info[i].key_custom)
1902           key_action |= key_info[i].action;
1903
1904       // use combined snap+direction keys for the first player only
1905       if (pnr == 0)
1906       {
1907         ssi = setup.shortcut;
1908
1909         for (i = 0; i < NUM_DIRECTIONS; i++)
1910           if (key == *key_info[i].key_snap)
1911             key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1912       }
1913
1914       if (key_status == KEY_PRESSED)
1915         stored_player[pnr].action |= key_action;
1916       else
1917         stored_player[pnr].action &= ~key_action;
1918
1919       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1920       {
1921         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1922         {
1923           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1924
1925           // if snap key already pressed, keep pause mode when releasing
1926           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1927             has_snapped[pnr] = TRUE;
1928         }
1929         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1930         {
1931           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1932
1933           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1934               getRedDiskReleaseFlag_SP() == 0)
1935           {
1936             // add a single inactive frame before dropping starts
1937             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1938             stored_player[pnr].force_dropping = TRUE;
1939           }
1940         }
1941         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1942         {
1943           // if snap key was pressed without direction, leave pause mode
1944           if (!has_snapped[pnr])
1945             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1946
1947           has_snapped[pnr] = FALSE;
1948         }
1949       }
1950       else if (tape.recording && tape.pausing && !tape.use_mouse)
1951       {
1952         // prevent key release events from un-pausing a paused game
1953         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1954           TapeTogglePause(TAPE_TOGGLE_MANUAL);
1955       }
1956
1957       // for MM style levels, handle in-game keyboard input in HandleJoystick()
1958       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1959         joy |= key_action;
1960     }
1961   }
1962   else
1963   {
1964     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1965       if (key == key_info[i].key_default)
1966         joy |= key_info[i].action;
1967   }
1968
1969   if (joy)
1970   {
1971     if (key_status == KEY_PRESSED)
1972       key_joystick_mapping |= joy;
1973     else
1974       key_joystick_mapping &= ~joy;
1975
1976     HandleJoystick();
1977   }
1978
1979   if (game_status != GAME_MODE_PLAYING)
1980     key_joystick_mapping = 0;
1981
1982   if (key_status == KEY_RELEASED)
1983   {
1984     // reset flag to ignore repeated "key pressed" events after key release
1985     ignore_repeated_key = FALSE;
1986
1987     return;
1988   }
1989
1990   if ((key == KSYM_F11 ||
1991        ((key == KSYM_Return ||
1992          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1993       video.fullscreen_available &&
1994       !ignore_repeated_key)
1995   {
1996     setup.fullscreen = !setup.fullscreen;
1997
1998     ToggleFullscreenOrChangeWindowScalingIfNeeded();
1999
2000     if (game_status == GAME_MODE_SETUP)
2001       RedrawSetupScreenAfterFullscreenToggle();
2002
2003     // set flag to ignore repeated "key pressed" events
2004     ignore_repeated_key = TRUE;
2005
2006     return;
2007   }
2008
2009   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2010        key == KSYM_minus || key == KSYM_KP_Subtract ||
2011        key == KSYM_plus  || key == KSYM_KP_Add ||
2012        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2013       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2014       video.window_scaling_available &&
2015       !video.fullscreen_enabled)
2016   {
2017     if (key == KSYM_0 || key == KSYM_KP_0)
2018       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2019     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2020       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2021     else
2022       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2023
2024     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2025       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2026     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2027       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2028
2029     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2030
2031     if (game_status == GAME_MODE_SETUP)
2032       RedrawSetupScreenAfterFullscreenToggle();
2033
2034     return;
2035   }
2036
2037   if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2038                                       key == KSYM_Return ||
2039                                       key == KSYM_Escape)))
2040   {
2041     // do not handle this key event anymore
2042     if (key != KSYM_Escape)     // always allow ESC key to be handled
2043       return;
2044   }
2045
2046   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2047       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2048   {
2049     GameEnd();
2050
2051     return;
2052   }
2053
2054   if (game_status == GAME_MODE_MAIN &&
2055       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2056   {
2057     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2058
2059     return;
2060   }
2061
2062   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2063   {
2064     if (key == setup.shortcut.save_game)
2065       TapeQuickSave();
2066     else if (key == setup.shortcut.load_game)
2067       TapeQuickLoad();
2068     else if (key == setup.shortcut.toggle_pause)
2069       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2070
2071     HandleTapeButtonKeys(key);
2072     HandleSoundButtonKeys(key);
2073   }
2074
2075   if (game_status == GAME_MODE_PLAYING && !network_playing)
2076   {
2077     int centered_player_nr_next = -999;
2078
2079     if (key == setup.shortcut.focus_player_all)
2080       centered_player_nr_next = -1;
2081     else
2082       for (i = 0; i < MAX_PLAYERS; i++)
2083         if (key == setup.shortcut.focus_player[i])
2084           centered_player_nr_next = i;
2085
2086     if (centered_player_nr_next != -999)
2087     {
2088       game.centered_player_nr_next = centered_player_nr_next;
2089       game.set_centered_player = TRUE;
2090
2091       if (tape.recording)
2092       {
2093         tape.centered_player_nr_next = game.centered_player_nr_next;
2094         tape.set_centered_player = TRUE;
2095       }
2096     }
2097   }
2098
2099   HandleKeysSpecial(key);
2100
2101   if (HandleGadgetsKeyInput(key))
2102     return;             // do not handle already processed keys again
2103
2104   switch (game_status)
2105   {
2106     case GAME_MODE_PSEUDO_TYPENAME:
2107       HandleTypeName(0, key);
2108       break;
2109
2110     case GAME_MODE_TITLE:
2111     case GAME_MODE_MAIN:
2112     case GAME_MODE_LEVELS:
2113     case GAME_MODE_LEVELNR:
2114     case GAME_MODE_SETUP:
2115     case GAME_MODE_INFO:
2116     case GAME_MODE_SCORES:
2117
2118       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2119         break;
2120
2121       switch (key)
2122       {
2123         case KSYM_space:
2124         case KSYM_Return:
2125           if (game_status == GAME_MODE_TITLE)
2126             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2127           else if (game_status == GAME_MODE_MAIN)
2128             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2129           else if (game_status == GAME_MODE_LEVELS)
2130             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2131           else if (game_status == GAME_MODE_LEVELNR)
2132             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2133           else if (game_status == GAME_MODE_SETUP)
2134             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2135           else if (game_status == GAME_MODE_INFO)
2136             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2137           else if (game_status == GAME_MODE_SCORES)
2138             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2139           break;
2140
2141         case KSYM_Escape:
2142           if (game_status != GAME_MODE_MAIN)
2143             FadeSkipNextFadeIn();
2144
2145           if (game_status == GAME_MODE_TITLE)
2146             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2147           else if (game_status == GAME_MODE_LEVELS)
2148             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2149           else if (game_status == GAME_MODE_LEVELNR)
2150             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2151           else if (game_status == GAME_MODE_SETUP)
2152             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2153           else if (game_status == GAME_MODE_INFO)
2154             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2155           else if (game_status == GAME_MODE_SCORES)
2156             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2157           break;
2158
2159         case KSYM_Page_Up:
2160           if (game_status == GAME_MODE_LEVELS)
2161             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2162           else if (game_status == GAME_MODE_LEVELNR)
2163             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2164           else if (game_status == GAME_MODE_SETUP)
2165             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2166           else if (game_status == GAME_MODE_INFO)
2167             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2168           else if (game_status == GAME_MODE_SCORES)
2169             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2170           break;
2171
2172         case KSYM_Page_Down:
2173           if (game_status == GAME_MODE_LEVELS)
2174             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2175           else if (game_status == GAME_MODE_LEVELNR)
2176             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2177           else if (game_status == GAME_MODE_SETUP)
2178             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2179           else if (game_status == GAME_MODE_INFO)
2180             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2181           else if (game_status == GAME_MODE_SCORES)
2182             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2183           break;
2184
2185         default:
2186           break;
2187       }
2188       break;
2189
2190     case GAME_MODE_EDITOR:
2191       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2192         HandleLevelEditorKeyInput(key);
2193       break;
2194
2195     case GAME_MODE_PLAYING:
2196     {
2197       switch (key)
2198       {
2199         case KSYM_Escape:
2200           RequestQuitGame(setup.ask_on_escape);
2201           break;
2202
2203         default:
2204           break;
2205       }
2206       break;
2207     }
2208
2209     default:
2210       if (key == KSYM_Escape)
2211       {
2212         SetGameStatus(GAME_MODE_MAIN);
2213
2214         DrawMainMenu();
2215
2216         return;
2217       }
2218   }
2219 }
2220
2221 void HandleNoEvent(void)
2222 {
2223   HandleMouseCursor();
2224
2225   switch (game_status)
2226   {
2227     case GAME_MODE_PLAYING:
2228       HandleButtonOrFinger(-1, -1, -1);
2229       break;
2230   }
2231 }
2232
2233 void HandleEventActions(void)
2234 {
2235   // if (button_status && game_status != GAME_MODE_PLAYING)
2236   if (button_status && (game_status != GAME_MODE_PLAYING ||
2237                         tape.pausing ||
2238                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2239   {
2240     HandleButton(0, 0, button_status, -button_status);
2241   }
2242   else
2243   {
2244     HandleJoystick();
2245   }
2246
2247   if (network.enabled)
2248     HandleNetworking();
2249
2250   switch (game_status)
2251   {
2252     case GAME_MODE_MAIN:
2253       DrawPreviewLevelAnimation();
2254       break;
2255
2256     case GAME_MODE_EDITOR:
2257       HandleLevelEditorIdle();
2258       break;
2259
2260     default:
2261       break;
2262   }
2263 }
2264
2265 static void HandleTileCursor(int dx, int dy, int button)
2266 {
2267   if (!dx || !button)
2268     ClearPlayerMouseAction();
2269
2270   if (!dx && !dy)
2271     return;
2272
2273   if (button)
2274   {
2275     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2276                          (dx < 0 ? MB_LEFTBUTTON :
2277                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2278   }
2279   else if (!tile_cursor.moving)
2280   {
2281     int old_xpos = tile_cursor.xpos;
2282     int old_ypos = tile_cursor.ypos;
2283     int new_xpos = old_xpos;
2284     int new_ypos = old_ypos;
2285
2286     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2287       new_xpos = old_xpos + dx;
2288
2289     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2290       new_ypos = old_ypos + dy;
2291
2292     SetTileCursorTargetXY(new_xpos, new_ypos);
2293   }
2294 }
2295
2296 static int HandleJoystickForAllPlayers(void)
2297 {
2298   int i;
2299   int result = 0;
2300   boolean no_joysticks_configured = TRUE;
2301   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2302   static byte joy_action_last[MAX_PLAYERS];
2303
2304   for (i = 0; i < MAX_PLAYERS; i++)
2305     if (setup.input[i].use_joystick)
2306       no_joysticks_configured = FALSE;
2307
2308   // if no joysticks configured, map connected joysticks to players
2309   if (no_joysticks_configured)
2310     use_as_joystick_nr = TRUE;
2311
2312   for (i = 0; i < MAX_PLAYERS; i++)
2313   {
2314     byte joy_action = 0;
2315
2316     joy_action = JoystickExt(i, use_as_joystick_nr);
2317     result |= joy_action;
2318
2319     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2320         joy_action != joy_action_last[i])
2321       stored_player[i].action = joy_action;
2322
2323     joy_action_last[i] = joy_action;
2324   }
2325
2326   return result;
2327 }
2328
2329 void HandleJoystick(void)
2330 {
2331   static unsigned int joytest_delay = 0;
2332   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2333   static int joytest_last = 0;
2334   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2335   int delay_value       = GADGET_FRAME_DELAY;
2336   int joystick  = HandleJoystickForAllPlayers();
2337   int keyboard  = key_joystick_mapping;
2338   int joy       = (joystick | keyboard);
2339   int joytest   = joystick;
2340   int left      = joy & JOY_LEFT;
2341   int right     = joy & JOY_RIGHT;
2342   int up        = joy & JOY_UP;
2343   int down      = joy & JOY_DOWN;
2344   int button    = joy & JOY_BUTTON;
2345   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2346   int dx        = (left ? -1    : right ? 1     : 0);
2347   int dy        = (up   ? -1    : down  ? 1     : 0);
2348   boolean use_delay_value_first = (joytest != joytest_last);
2349
2350   if (HandleGlobalAnimClicks(-1, -1, newbutton))
2351   {
2352     // do not handle this button event anymore
2353     return;
2354   }
2355
2356   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2357   {
2358     if (game_status == GAME_MODE_PLAYING)
2359     {
2360       // when playing MM style levels, also use delay for keyboard events
2361       joytest |= keyboard;
2362
2363       // only use first delay value for new events, but not for changed events
2364       use_delay_value_first = (!joytest != !joytest_last);
2365
2366       // only use delay after the initial keyboard event
2367       delay_value = 0;
2368     }
2369
2370     // for any joystick or keyboard event, enable playfield tile cursor
2371     if (dx || dy || button)
2372       SetTileCursorEnabled(TRUE);
2373   }
2374
2375   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2376   {
2377     // delay joystick/keyboard actions if axes/keys continually pressed
2378     newbutton = dx = dy = 0;
2379   }
2380   else
2381   {
2382     // first start with longer delay, then continue with shorter delay
2383     joytest_delay_value =
2384       (use_delay_value_first ? delay_value_first : delay_value);
2385   }
2386
2387   joytest_last = joytest;
2388
2389   switch (game_status)
2390   {
2391     case GAME_MODE_TITLE:
2392     case GAME_MODE_MAIN:
2393     case GAME_MODE_LEVELS:
2394     case GAME_MODE_LEVELNR:
2395     case GAME_MODE_SETUP:
2396     case GAME_MODE_INFO:
2397     case GAME_MODE_SCORES:
2398     {
2399       if (anyTextGadgetActive())
2400         break;
2401
2402       if (game_status == GAME_MODE_TITLE)
2403         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2404       else if (game_status == GAME_MODE_MAIN)
2405         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2406       else if (game_status == GAME_MODE_LEVELS)
2407         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2408       else if (game_status == GAME_MODE_LEVELNR)
2409         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2410       else if (game_status == GAME_MODE_SETUP)
2411         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2412       else if (game_status == GAME_MODE_INFO)
2413         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2414       else if (game_status == GAME_MODE_SCORES)
2415         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2416
2417       break;
2418     }
2419
2420     case GAME_MODE_PLAYING:
2421 #if 0
2422       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2423       if (tape.playing || keyboard)
2424         newbutton = ((joy & JOY_BUTTON) != 0);
2425 #endif
2426
2427       if (newbutton && game.all_players_gone)
2428       {
2429         GameEnd();
2430
2431         return;
2432       }
2433
2434       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2435       {
2436         if (joystick & JOY_ACTION)
2437           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2438       }
2439       else if (tape.recording && tape.pausing && !tape.use_mouse)
2440       {
2441         if (joystick & JOY_ACTION)
2442           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2443       }
2444
2445       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2446         HandleTileCursor(dx, dy, button);
2447
2448       break;
2449
2450     default:
2451       break;
2452   }
2453 }
2454
2455 void HandleSpecialGameControllerButtons(Event *event)
2456 {
2457   int key_status;
2458   Key key;
2459
2460   switch (event->type)
2461   {
2462     case SDL_CONTROLLERBUTTONDOWN:
2463       key_status = KEY_PRESSED;
2464       break;
2465
2466     case SDL_CONTROLLERBUTTONUP:
2467       key_status = KEY_RELEASED;
2468       break;
2469
2470     default:
2471       return;
2472   }
2473
2474   switch (event->cbutton.button)
2475   {
2476     case SDL_CONTROLLER_BUTTON_START:
2477       key = KSYM_space;
2478       break;
2479
2480     case SDL_CONTROLLER_BUTTON_BACK:
2481       key = KSYM_Escape;
2482       break;
2483
2484     default:
2485       return;
2486   }
2487
2488   HandleKey(key, key_status);
2489 }
2490
2491 void HandleSpecialGameControllerKeys(Key key, int key_status)
2492 {
2493 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2494   int button = SDL_CONTROLLER_BUTTON_INVALID;
2495
2496   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2497   if (key == KSYM_Rewind)
2498     button = SDL_CONTROLLER_BUTTON_A;
2499   else if (key == KSYM_FastForward || key == KSYM_Menu)
2500     button = SDL_CONTROLLER_BUTTON_B;
2501
2502   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2503   {
2504     Event event;
2505
2506     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2507                   SDL_CONTROLLERBUTTONUP);
2508
2509     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2510     event.cbutton.button = button;
2511     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2512                            SDL_RELEASED);
2513
2514     HandleJoystickEvent(&event);
2515   }
2516 #endif
2517 }
2518
2519 boolean DoKeysymAction(int keysym)
2520 {
2521   if (keysym < 0)
2522   {
2523     Key key = (Key)(-keysym);
2524
2525     HandleKey(key, KEY_PRESSED);
2526     HandleKey(key, KEY_RELEASED);
2527
2528     return TRUE;
2529   }
2530
2531   return FALSE;
2532 }