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