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