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