added support for BD game engine to Makefile for Android
[rocksndiamonds.git] / src / events.c
1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  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   {
1510     Warn("file '%s' not supported", filename);
1511
1512     return TREE_TYPE_UNDEFINED;
1513   }
1514
1515   TreeInfo *tree_node = NULL;
1516   int tree_type = GetZipFileTreeType(filename);
1517   char *directory = TREE_USERDIR(tree_type);
1518
1519   if (directory == NULL)
1520   {
1521     Warn("zip file '%s' has invalid content!", filename);
1522
1523     return TREE_TYPE_UNDEFINED;
1524   }
1525
1526   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1527       game_status == GAME_MODE_LEVELS &&
1528       leveldir_current->node_parent != NULL)
1529   {
1530     // extract new level set next to currently selected level set
1531     tree_node = leveldir_current;
1532
1533     // get parent directory of currently selected level set directory
1534     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1535
1536     // use private level directory instead of top-level package level directory
1537     if (strPrefix(directory, options.level_directory) &&
1538         strEqual(leveldir_current->node_parent->fullpath, "."))
1539       directory = getUserLevelDir(NULL);
1540   }
1541
1542   // extract level or artwork set from zip file to target directory
1543   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1544
1545   if (top_dir == NULL)
1546   {
1547     // error message already issued by "ExtractZipFileIntoDirectory()"
1548
1549     return TREE_TYPE_UNDEFINED;
1550   }
1551
1552   // add extracted level or artwork set to tree info structure
1553   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1554
1555   // force restart after adding level collection
1556   if (getTreeInfoFromIdentifier(TREE_FIRST_NODE(tree_type), top_dir) == NULL)
1557   {
1558     Request("Program must be restarted after adding a new level collection!",
1559             REQ_CONFIRM);
1560
1561     CloseAllAndExit(0);
1562   }
1563
1564   // update menu screen (and possibly change current level set)
1565   DrawScreenAfterAddingSet(top_dir, tree_type);
1566
1567   return tree_type;
1568 }
1569
1570 static void HandleDropTextEvent(char *text)
1571 {
1572   Debug("event:droptext", "text == '%s'", text);
1573 }
1574
1575 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1576                                     int num_artwork_sets_succeeded,
1577                                     int num_files_failed)
1578 {
1579   // only show request dialog if no other request dialog already active
1580   if (game.request_active)
1581     return;
1582
1583   // this case can happen with drag-and-drop with older SDL versions
1584   if (num_level_sets_succeeded == 0 &&
1585       num_artwork_sets_succeeded == 0 &&
1586       num_files_failed == 0)
1587     return;
1588
1589   char message[100];
1590
1591   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1592   {
1593     char message_part1[50];
1594
1595     sprintf(message_part1, "New %s set%s added",
1596             (num_artwork_sets_succeeded == 0 ? "level" :
1597              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1598             (num_level_sets_succeeded +
1599              num_artwork_sets_succeeded > 1 ? "s" : ""));
1600
1601     if (num_files_failed > 0)
1602       sprintf(message, "%s, but %d dropped file%s failed!",
1603               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1604     else
1605       sprintf(message, "%s!", message_part1);
1606   }
1607   else if (num_files_failed > 0)
1608   {
1609     sprintf(message, "Failed to process dropped file%s!",
1610             num_files_failed > 1 ? "s" : "");
1611   }
1612
1613   Request(message, REQ_CONFIRM);
1614 }
1615
1616 void HandleDropEvent(Event *event)
1617 {
1618   static boolean confirm_on_drop_complete = FALSE;
1619   static int num_level_sets_succeeded = 0;
1620   static int num_artwork_sets_succeeded = 0;
1621   static int num_files_failed = 0;
1622
1623   switch (event->type)
1624   {
1625     case SDL_DROPBEGIN:
1626     {
1627       confirm_on_drop_complete = TRUE;
1628       num_level_sets_succeeded = 0;
1629       num_artwork_sets_succeeded = 0;
1630       num_files_failed = 0;
1631
1632       break;
1633     }
1634
1635     case SDL_DROPFILE:
1636     {
1637       int tree_type = HandleDropFileEvent(event->drop.file);
1638
1639       if (tree_type == TREE_TYPE_LEVEL_DIR)
1640         num_level_sets_succeeded++;
1641       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1642                tree_type == TREE_TYPE_SOUNDS_DIR ||
1643                tree_type == TREE_TYPE_MUSIC_DIR)
1644         num_artwork_sets_succeeded++;
1645       else
1646         num_files_failed++;
1647
1648       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1649       if (!confirm_on_drop_complete)
1650       {
1651         // process all remaining events, including further SDL_DROPFILE events
1652         ClearEventQueue();
1653
1654         HandleDropCompleteEvent(num_level_sets_succeeded,
1655                                 num_artwork_sets_succeeded,
1656                                 num_files_failed);
1657
1658         num_level_sets_succeeded = 0;
1659         num_artwork_sets_succeeded = 0;
1660         num_files_failed = 0;
1661       }
1662
1663       break;
1664     }
1665
1666     case SDL_DROPTEXT:
1667     {
1668       HandleDropTextEvent(event->drop.file);
1669
1670       break;
1671     }
1672
1673     case SDL_DROPCOMPLETE:
1674     {
1675       HandleDropCompleteEvent(num_level_sets_succeeded,
1676                               num_artwork_sets_succeeded,
1677                               num_files_failed);
1678
1679       break;
1680     }
1681   }
1682
1683   if (event->drop.file != NULL)
1684     SDL_free(event->drop.file);
1685 }
1686
1687 void HandleUserEvent(UserEvent *event)
1688 {
1689   switch (event->code)
1690   {
1691     case USEREVENT_ANIM_DELAY_ACTION:
1692     case USEREVENT_ANIM_EVENT_ACTION:
1693       // execute action functions until matching action was found
1694       if (DoKeysymAction(event->value1) ||
1695           DoGadgetAction(event->value1) ||
1696           DoScreenAction(event->value1))
1697         return;
1698       break;
1699
1700     default:
1701       break;
1702   }
1703 }
1704
1705 void HandleButton(int mx, int my, int button, int button_nr)
1706 {
1707   static int old_mx = 0, old_my = 0;
1708   boolean button_hold = FALSE;
1709   boolean handle_gadgets = TRUE;
1710   int game_status_last = game_status;
1711
1712   if (button_nr < 0)
1713   {
1714     mx = old_mx;
1715     my = old_my;
1716     button_nr = -button_nr;
1717     button_hold = TRUE;
1718   }
1719   else
1720   {
1721     old_mx = mx;
1722     old_my = my;
1723   }
1724
1725 #if defined(PLATFORM_ANDROID)
1726   // when playing, only handle gadgets when using "follow finger" controls
1727   // or when using touch controls in combination with the MM game engine
1728   // or when using gadgets that do not overlap with virtual buttons
1729   // or when touch controls are disabled (e.g., with mouse-only levels)
1730   handle_gadgets =
1731     (game_status != GAME_MODE_PLAYING ||
1732      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1733      strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF) ||
1734      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1735      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1736       !CheckVirtualButtonPressed(mx, my, button)));
1737
1738   // always recognize potentially releasing already pressed gadgets
1739   if (button == MB_RELEASED)
1740     handle_gadgets = TRUE;
1741
1742   // always recognize pressing or releasing overlay touch buttons
1743   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1744     handle_gadgets = TRUE;
1745 #endif
1746
1747   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1748   {
1749     // do not handle this button event anymore
1750     return;             // force mouse event not to be handled at all
1751   }
1752
1753   if (handle_gadgets && HandleGadgets(mx, my, button))
1754   {
1755     // do not handle this button event anymore with position on screen
1756     mx = my = -32;      // force mouse event to be outside screen tiles
1757
1758     // do not handle this button event anymore if game status has changed
1759     if (game_status != game_status_last)
1760       return;
1761   }
1762
1763   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1764     return;
1765
1766   // do not use scroll wheel button events for anything other than gadgets
1767   if (IS_WHEEL_BUTTON(button_nr))
1768     return;
1769
1770   switch (game_status)
1771   {
1772     case GAME_MODE_TITLE:
1773       HandleTitleScreen(mx, my, 0, 0, button);
1774       break;
1775
1776     case GAME_MODE_MAIN:
1777       HandleMainMenu(mx, my, 0, 0, button);
1778       break;
1779
1780     case GAME_MODE_PSEUDO_TYPENAME:
1781     case GAME_MODE_PSEUDO_TYPENAMES:
1782       HandleTypeName(KSYM_Return);
1783       break;
1784
1785     case GAME_MODE_NAMES:
1786       HandleChoosePlayerName(mx, my, 0, 0, button);
1787       break;
1788
1789     case GAME_MODE_LEVELS:
1790       HandleChooseLevelSet(mx, my, 0, 0, button);
1791       break;
1792
1793     case GAME_MODE_LEVELNR:
1794       HandleChooseLevelNr(mx, my, 0, 0, button);
1795       break;
1796
1797     case GAME_MODE_SCORES:
1798       HandleHallOfFame(mx, my, 0, 0, button);
1799       break;
1800
1801     case GAME_MODE_SCOREINFO:
1802       HandleScoreInfo(mx, my, 0, 0, button);
1803       break;
1804
1805     case GAME_MODE_EDITOR:
1806       HandleLevelEditorIdle();
1807       break;
1808
1809     case GAME_MODE_INFO:
1810       HandleInfoScreen(mx, my, 0, 0, button);
1811       break;
1812
1813     case GAME_MODE_SETUP:
1814       HandleSetupScreen(mx, my, 0, 0, button);
1815       break;
1816
1817     case GAME_MODE_PLAYING:
1818       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1819         HandleButtonOrFinger(mx, my, button);
1820       else
1821         SetPlayerMouseAction(mx, my, button);
1822
1823 #ifdef DEBUG
1824       if (button == MB_PRESSED && !motion_status && !button_hold &&
1825           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1826         DumpTileFromScreen(mx, my);
1827 #endif
1828
1829       break;
1830
1831     default:
1832       break;
1833   }
1834 }
1835
1836 #define MAX_CHEAT_INPUT_LEN     32
1837
1838 static void HandleKeysSpecial(Key key)
1839 {
1840   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1841   char letter = getCharFromKey(key);
1842   int cheat_input_len = strlen(cheat_input);
1843   int i;
1844
1845   if (letter == 0)
1846     return;
1847
1848   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1849   {
1850     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1851       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1852
1853     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1854   }
1855
1856   cheat_input[cheat_input_len++] = letter;
1857   cheat_input[cheat_input_len] = '\0';
1858
1859 #if DEBUG_EVENTS_KEY
1860   Debug("event:key:special", "'%s' [%d]", cheat_input, cheat_input_len);
1861 #endif
1862
1863   if (game_status == GAME_MODE_MAIN)
1864   {
1865     if (strSuffix(cheat_input, ":insert-solution-tape") ||
1866         strSuffix(cheat_input, ":ist"))
1867     {
1868       InsertSolutionTape();
1869     }
1870     else if (strSuffix(cheat_input, ":play-solution-tape") ||
1871              strSuffix(cheat_input, ":pst"))
1872     {
1873       PlaySolutionTape();
1874     }
1875     else if (strSuffix(cheat_input, ":reload-graphics") ||
1876              strSuffix(cheat_input, ":rg"))
1877     {
1878       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1879       DrawMainMenu();
1880     }
1881     else if (strSuffix(cheat_input, ":reload-sounds") ||
1882              strSuffix(cheat_input, ":rs"))
1883     {
1884       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1885       DrawMainMenu();
1886     }
1887     else if (strSuffix(cheat_input, ":reload-music") ||
1888              strSuffix(cheat_input, ":rm"))
1889     {
1890       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1891       DrawMainMenu();
1892     }
1893     else if (strSuffix(cheat_input, ":reload-artwork") ||
1894              strSuffix(cheat_input, ":ra"))
1895     {
1896       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1897                           1 << ARTWORK_TYPE_SOUNDS |
1898                           1 << ARTWORK_TYPE_MUSIC);
1899       DrawMainMenu();
1900     }
1901     else if (strSuffix(cheat_input, ":dump-level") ||
1902              strSuffix(cheat_input, ":dl"))
1903     {
1904       DumpLevel(&level);
1905     }
1906     else if (strSuffix(cheat_input, ":dump-tape") ||
1907              strSuffix(cheat_input, ":dt"))
1908     {
1909       DumpTape(&tape);
1910     }
1911     else if (strSuffix(cheat_input, ":undo-tape") ||
1912              strSuffix(cheat_input, ":ut"))
1913     {
1914       UndoTape();
1915     }
1916     else if (strSuffix(cheat_input, ":fix-tape") ||
1917              strSuffix(cheat_input, ":ft"))
1918     {
1919       FixTape_ForceSinglePlayer();
1920     }
1921     else if (strSuffix(cheat_input, ":save-native-level") ||
1922              strSuffix(cheat_input, ":snl"))
1923     {
1924       SaveNativeLevel(&level);
1925     }
1926     else if (strSuffix(cheat_input, ":frames-per-second") ||
1927              strSuffix(cheat_input, ":fps"))
1928     {
1929       global.show_frames_per_second = !global.show_frames_per_second;
1930     }
1931     else if (strSuffix(cheat_input, ":xsn"))
1932     {
1933       tile_cursor.xsn_debug = TRUE;
1934     }
1935   }
1936   else if (game_status == GAME_MODE_PLAYING)
1937   {
1938 #ifdef DEBUG
1939     if (strSuffix(cheat_input, ".q"))
1940       DEBUG_SetMaximumDynamite();
1941 #endif
1942   }
1943   else if (game_status == GAME_MODE_EDITOR)
1944   {
1945     if (strSuffix(cheat_input, ":dump-brush") ||
1946         strSuffix(cheat_input, ":DB"))
1947     {
1948       DumpBrush();
1949     }
1950     else if (strSuffix(cheat_input, ":DDB"))
1951     {
1952       DumpBrush_Small();
1953     }
1954
1955     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1956     {
1957       if (letter == 'x')        // copy brush to clipboard (small size)
1958       {
1959         CopyBrushToClipboard_Small();
1960       }
1961       else if (letter == 'c')   // copy brush to clipboard (normal size)
1962       {
1963         CopyBrushToClipboard();
1964       }
1965       else if (letter == 'v')   // paste brush from Clipboard
1966       {
1967         CopyClipboardToBrush();
1968       }
1969       else if (letter == 'z')   // undo or redo last operation
1970       {
1971         if (GetKeyModState() & KMOD_Shift)
1972           RedoLevelEditorOperation();
1973         else
1974           UndoLevelEditorOperation();
1975       }
1976     }
1977   }
1978
1979   // special key shortcuts for all game modes
1980   if (strSuffix(cheat_input, ":dump-event-actions") ||
1981       strSuffix(cheat_input, ":dea") ||
1982       strSuffix(cheat_input, ":DEA"))
1983   {
1984     DumpGadgetIdentifiers();
1985     DumpScreenIdentifiers();
1986   }
1987 }
1988
1989 static boolean HandleKeysSpeed(Key key, int key_status)
1990 {
1991   if (game_status == GAME_MODE_PLAYING)
1992   {
1993     if (key == setup.shortcut.speed_fast ||
1994         key == setup.shortcut.speed_slow)
1995     {
1996       int speed_factor = 4;
1997
1998       GameFrameDelay = (key_status != KEY_PRESSED ? setup.game_frame_delay :
1999                         key == setup.shortcut.speed_fast ? setup.game_frame_delay / speed_factor :
2000                         key == setup.shortcut.speed_slow ? setup.game_frame_delay * speed_factor :
2001                         setup.game_frame_delay);
2002
2003       GameFrameDelay = MIN(MAX(1, GameFrameDelay), 1000);
2004
2005       SetVideoFrameDelay(GameFrameDelay);
2006
2007       return TRUE;
2008     }
2009   }
2010
2011   return FALSE;
2012 }
2013
2014 boolean HandleKeysDebug(Key key, int key_status)
2015 {
2016 #ifdef DEBUG
2017   int i;
2018
2019   if (key_status != KEY_PRESSED)
2020     return FALSE;
2021
2022   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
2023   {
2024     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2025
2026     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2027     {
2028       if (key == setup.debug.frame_delay_key[i] &&
2029           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2030       {
2031         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2032                           setup.debug.frame_delay[i] : setup.game_frame_delay);
2033
2034         if (!setup.debug.frame_delay_game_only)
2035           MenuFrameDelay = GameFrameDelay;
2036
2037         SetVideoFrameDelay(GameFrameDelay);
2038
2039         if (GameFrameDelay > ONE_SECOND_DELAY)
2040           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2041         else if (GameFrameDelay != 0)
2042           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2043                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2044                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2045         else
2046           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2047
2048         return TRUE;
2049       }
2050     }
2051   }
2052
2053   if (game_status == GAME_MODE_PLAYING)
2054   {
2055     if (key == KSYM_d)
2056     {
2057       options.debug = !options.debug;
2058
2059       Debug("event:key:debug", "debug mode %s",
2060             (options.debug ? "enabled" : "disabled"));
2061
2062       return TRUE;
2063     }
2064     else if (key == KSYM_v)
2065     {
2066       Debug("event:key:debug", "currently using game engine version %d",
2067             game.engine_version);
2068
2069       return TRUE;
2070     }
2071   }
2072 #endif
2073
2074   return FALSE;
2075 }
2076
2077 void HandleKey(Key key, int key_status)
2078 {
2079   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2080   static boolean ignore_repeated_key = FALSE;
2081   static struct SetupKeyboardInfo ski;
2082   static struct SetupShortcutInfo ssi;
2083   static struct
2084   {
2085     Key *key_custom;
2086     Key *key_snap;
2087     Key key_default;
2088     byte action;
2089   } key_info[] =
2090   {
2091     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2092     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2093     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2094     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2095     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2096     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2097   };
2098   int joy = 0;
2099   int i;
2100
2101   if (HandleKeysSpeed(key, key_status))
2102     return;             // do not handle already processed keys again
2103
2104   if (HandleKeysDebug(key, key_status))
2105     return;             // do not handle already processed keys again
2106
2107   // map special keys (media keys / remote control buttons) to default keys
2108   if (key == KSYM_PlayPause)
2109     key = KSYM_space;
2110   else if (key == KSYM_Select)
2111     key = KSYM_Return;
2112
2113   HandleSpecialGameControllerKeys(key, key_status);
2114
2115   if (game_status == GAME_MODE_PLAYING)
2116   {
2117     // only needed for single-step tape recording mode
2118     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2119     int pnr;
2120
2121     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2122     {
2123       byte key_action = 0;
2124       byte key_snap_action = 0;
2125
2126       if (setup.input[pnr].use_joystick)
2127         continue;
2128
2129       ski = setup.input[pnr].key;
2130
2131       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2132         if (key == *key_info[i].key_custom)
2133           key_action |= key_info[i].action;
2134
2135       // use combined snap+direction keys for the first player only
2136       if (pnr == 0)
2137       {
2138         ssi = setup.shortcut;
2139
2140         // also remember normal snap key when handling snap+direction keys
2141         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2142
2143         for (i = 0; i < NUM_DIRECTIONS; i++)
2144         {
2145           if (key == *key_info[i].key_snap)
2146           {
2147             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2148             key_snap_action |= key_info[i].action;
2149
2150             tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2151           }
2152         }
2153       }
2154
2155       if (key_status == KEY_PRESSED)
2156       {
2157         stored_player[pnr].action      |= key_action;
2158         stored_player[pnr].snap_action |= key_snap_action;
2159       }
2160       else
2161       {
2162         stored_player[pnr].action      &= ~key_action;
2163         stored_player[pnr].snap_action &= ~key_snap_action;
2164       }
2165
2166       // restore snap action if one of several pressed snap keys was released
2167       if (stored_player[pnr].snap_action)
2168         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2169
2170       if (tape.recording && tape.pausing && tape.use_key_actions)
2171       {
2172         if (tape.single_step)
2173         {
2174           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2175           {
2176             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2177
2178             // if snap key already pressed, keep pause mode when releasing
2179             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2180               has_snapped[pnr] = TRUE;
2181           }
2182           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2183           {
2184             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2185
2186             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2187                 getRedDiskReleaseFlag_SP() == 0)
2188             {
2189               // add a single inactive frame before dropping starts
2190               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2191               stored_player[pnr].force_dropping = TRUE;
2192             }
2193           }
2194           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2195           {
2196             // if snap key was pressed without direction, leave pause mode
2197             if (!has_snapped[pnr])
2198               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2199
2200             has_snapped[pnr] = FALSE;
2201           }
2202         }
2203         else
2204         {
2205           // prevent key release events from un-pausing a paused game
2206           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2207             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2208         }
2209       }
2210
2211       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2212       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2213         joy |= key_action;
2214
2215       // for any keyboard event, enable playfield mouse cursor
2216       if (key_action && key_status == KEY_PRESSED)
2217         SetPlayfieldMouseCursorEnabled(TRUE);
2218     }
2219   }
2220   else
2221   {
2222     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2223       if (key == key_info[i].key_default)
2224         joy |= key_info[i].action;
2225   }
2226
2227   if (joy)
2228   {
2229     if (key_status == KEY_PRESSED)
2230       key_joystick_mapping |= joy;
2231     else
2232       key_joystick_mapping &= ~joy;
2233
2234     HandleJoystick();
2235   }
2236
2237   if (game_status != GAME_MODE_PLAYING)
2238     key_joystick_mapping = 0;
2239
2240   if (key_status == KEY_RELEASED)
2241   {
2242     // reset flag to ignore repeated "key pressed" events after key release
2243     ignore_repeated_key = FALSE;
2244
2245     // send key release event to global animation event handling
2246     if (!is_global_anim_event)
2247       HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2248
2249     return;
2250   }
2251
2252   if ((key == KSYM_F11 ||
2253        ((key == KSYM_Return ||
2254          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2255       video.fullscreen_available &&
2256       !ignore_repeated_key)
2257   {
2258     setup.fullscreen = !setup.fullscreen;
2259
2260     ToggleFullscreenIfNeeded();
2261
2262     if (game_status == GAME_MODE_SETUP)
2263       RedrawSetupScreenAfterFullscreenToggle();
2264
2265     UpdateMousePosition();
2266
2267     // set flag to ignore repeated "key pressed" events
2268     ignore_repeated_key = TRUE;
2269
2270     return;
2271   }
2272
2273   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2274        key == KSYM_minus || key == KSYM_KP_Subtract ||
2275        key == KSYM_plus  || key == KSYM_KP_Add ||
2276        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2277       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2278       video.window_scaling_available &&
2279       !video.fullscreen_enabled)
2280   {
2281     if (key == KSYM_0 || key == KSYM_KP_0)
2282       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2283     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2284       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2285     else
2286       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2287
2288     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2289       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2290     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2291       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2292
2293     ChangeWindowScalingIfNeeded();
2294
2295     if (game_status == GAME_MODE_SETUP)
2296       RedrawSetupScreenAfterFullscreenToggle();
2297
2298     UpdateMousePosition();
2299
2300     return;
2301   }
2302
2303   // some key events are handled like clicks for global animations
2304   boolean click = (!is_global_anim_event && (key == KSYM_space ||
2305                                              key == KSYM_Return ||
2306                                              key == KSYM_Escape));
2307
2308   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2309   {
2310     // do not handle this key event anymore
2311     if (key != KSYM_Escape)     // always allow ESC key to be handled
2312       return;
2313   }
2314
2315   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2316       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2317   {
2318     GameEnd();
2319
2320     return;
2321   }
2322
2323   if (game_status == GAME_MODE_MAIN &&
2324       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2325   {
2326     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2327
2328     return;
2329   }
2330
2331   if (game_status == GAME_MODE_MAIN &&
2332       (setup.internal.info_screens_from_main ||
2333        leveldir_current->info_screens_from_main) &&
2334       (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2335   {
2336     DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2337
2338     return;
2339   }
2340
2341   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2342   {
2343     if (key == setup.shortcut.save_game)
2344       TapeQuickSave();
2345     else if (key == setup.shortcut.load_game)
2346       TapeQuickLoad();
2347     else if (key == setup.shortcut.restart_game)
2348       TapeRestartGame();
2349     else if (key == setup.shortcut.pause_before_end)
2350       TapeReplayAndPauseBeforeEnd();
2351     else if (key == setup.shortcut.toggle_pause)
2352       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2353
2354     HandleTapeButtonKeys(key);
2355     HandleSoundButtonKeys(key);
2356   }
2357
2358   if (game_status == GAME_MODE_SCOREINFO)
2359   {
2360     HandleScreenGadgetKeys(key);
2361   }
2362
2363   if (game_status == GAME_MODE_PLAYING && !network_playing)
2364   {
2365     int centered_player_nr_next = -999;
2366
2367     if (key == setup.shortcut.focus_player_all)
2368       centered_player_nr_next = -1;
2369     else
2370       for (i = 0; i < MAX_PLAYERS; i++)
2371         if (key == setup.shortcut.focus_player[i])
2372           centered_player_nr_next = i;
2373
2374     if (centered_player_nr_next != -999)
2375     {
2376       game.centered_player_nr_next = centered_player_nr_next;
2377       game.set_centered_player = TRUE;
2378
2379       if (tape.recording)
2380       {
2381         tape.centered_player_nr_next = game.centered_player_nr_next;
2382         tape.set_centered_player = TRUE;
2383       }
2384     }
2385   }
2386
2387   HandleKeysSpecial(key);
2388
2389   if (HandleGadgetsKeyInput(key))
2390     return;             // do not handle already processed keys again
2391
2392   // special case: on "space" key, either continue playing or go to main menu
2393   if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2394   {
2395     HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2396
2397     return;
2398   }
2399
2400   switch (game_status)
2401   {
2402     case GAME_MODE_PSEUDO_TYPENAME:
2403     case GAME_MODE_PSEUDO_TYPENAMES:
2404       HandleTypeName(key);
2405       break;
2406
2407     case GAME_MODE_TITLE:
2408     case GAME_MODE_MAIN:
2409     case GAME_MODE_NAMES:
2410     case GAME_MODE_LEVELS:
2411     case GAME_MODE_LEVELNR:
2412     case GAME_MODE_SETUP:
2413     case GAME_MODE_INFO:
2414     case GAME_MODE_SCORES:
2415     case GAME_MODE_SCOREINFO:
2416
2417       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2418         break;
2419
2420       switch (key)
2421       {
2422         case KSYM_space:
2423         case KSYM_Return:
2424           if (game_status == GAME_MODE_TITLE)
2425             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2426           else if (game_status == GAME_MODE_MAIN)
2427             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2428           else if (game_status == GAME_MODE_NAMES)
2429             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2430           else if (game_status == GAME_MODE_LEVELS)
2431             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2432           else if (game_status == GAME_MODE_LEVELNR)
2433             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2434           else if (game_status == GAME_MODE_SETUP)
2435             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2436           else if (game_status == GAME_MODE_INFO)
2437             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2438           else if (game_status == GAME_MODE_SCORES)
2439             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2440           else if (game_status == GAME_MODE_SCOREINFO)
2441             HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2442           break;
2443
2444         case KSYM_Escape:
2445           if (game_status != GAME_MODE_MAIN)
2446             FadeSkipNextFadeIn();
2447
2448           if (game_status == GAME_MODE_TITLE)
2449             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2450           else if (game_status == GAME_MODE_NAMES)
2451             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2452           else if (game_status == GAME_MODE_LEVELS)
2453             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2454           else if (game_status == GAME_MODE_LEVELNR)
2455             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2456           else if (game_status == GAME_MODE_SETUP)
2457             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2458           else if (game_status == GAME_MODE_INFO)
2459             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2460           else if (game_status == GAME_MODE_SCORES)
2461             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2462           else if (game_status == GAME_MODE_SCOREINFO)
2463             HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2464           break;
2465
2466         case KSYM_Page_Up:
2467           if (game_status == GAME_MODE_NAMES)
2468             HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2469           else if (game_status == GAME_MODE_LEVELS)
2470             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2471           else if (game_status == GAME_MODE_LEVELNR)
2472             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2473           else if (game_status == GAME_MODE_SETUP)
2474             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2475           else if (game_status == GAME_MODE_INFO)
2476             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2477           else if (game_status == GAME_MODE_SCORES)
2478             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2479           else if (game_status == GAME_MODE_SCOREINFO)
2480             HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2481           break;
2482
2483         case KSYM_Page_Down:
2484           if (game_status == GAME_MODE_NAMES)
2485             HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2486           else if (game_status == GAME_MODE_LEVELS)
2487             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2488           else if (game_status == GAME_MODE_LEVELNR)
2489             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2490           else if (game_status == GAME_MODE_SETUP)
2491             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2492           else if (game_status == GAME_MODE_INFO)
2493             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2494           else if (game_status == GAME_MODE_SCORES)
2495             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2496           else if (game_status == GAME_MODE_SCOREINFO)
2497             HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2498           break;
2499
2500         default:
2501           break;
2502       }
2503       break;
2504
2505     case GAME_MODE_EDITOR:
2506       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2507         HandleLevelEditorKeyInput(key);
2508       break;
2509
2510     case GAME_MODE_PLAYING:
2511     {
2512       switch (key)
2513       {
2514         case KSYM_Escape:
2515           RequestQuitGame(TRUE);
2516           break;
2517
2518         default:
2519           break;
2520       }
2521       break;
2522     }
2523
2524     default:
2525       if (key == KSYM_Escape)
2526       {
2527         SetGameStatus(GAME_MODE_MAIN);
2528
2529         DrawMainMenu();
2530
2531         return;
2532       }
2533   }
2534 }
2535
2536 void HandleNoEvent(void)
2537 {
2538   HandleMouseCursor();
2539
2540   switch (game_status)
2541   {
2542     case GAME_MODE_PLAYING:
2543       HandleButtonOrFinger(-1, -1, -1);
2544       break;
2545   }
2546 }
2547
2548 void HandleEventActions(void)
2549 {
2550   // if (button_status && game_status != GAME_MODE_PLAYING)
2551   if (button_status && (game_status != GAME_MODE_PLAYING ||
2552                         tape.pausing ||
2553                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2554   {
2555     HandleButton(0, 0, button_status, -button_status);
2556   }
2557   else
2558   {
2559     HandleJoystick();
2560   }
2561
2562   if (network.enabled)
2563     HandleNetworking();
2564
2565   switch (game_status)
2566   {
2567     case GAME_MODE_MAIN:
2568       DrawPreviewLevelAnimation();
2569       break;
2570
2571     case GAME_MODE_EDITOR:
2572       HandleLevelEditorIdle();
2573       break;
2574
2575     default:
2576       break;
2577   }
2578 }
2579
2580 static void HandleTileCursor(int dx, int dy, int button)
2581 {
2582   if (!dx || !button)
2583     ClearPlayerMouseAction();
2584
2585   if (!dx && !dy)
2586     return;
2587
2588   if (button)
2589   {
2590     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2591                          (dx < 0 ? MB_LEFTBUTTON :
2592                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2593   }
2594   else if (!tile_cursor.moving)
2595   {
2596     int old_xpos = tile_cursor.xpos;
2597     int old_ypos = tile_cursor.ypos;
2598     int new_xpos = tile_cursor.xpos + dx;
2599     int new_ypos = tile_cursor.ypos + dy;
2600
2601     if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2602       new_xpos = old_xpos;
2603
2604     if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2605       new_ypos = old_ypos;
2606
2607     SetTileCursorTargetXY(new_xpos, new_ypos);
2608   }
2609 }
2610
2611 static int HandleJoystickForAllPlayers(void)
2612 {
2613   int i;
2614   int result = 0;
2615   boolean no_joysticks_configured = TRUE;
2616   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2617   static byte joy_action_last[MAX_PLAYERS];
2618
2619   for (i = 0; i < MAX_PLAYERS; i++)
2620     if (setup.input[i].use_joystick)
2621       no_joysticks_configured = FALSE;
2622
2623   // if no joysticks configured, map connected joysticks to players
2624   if (no_joysticks_configured)
2625     use_as_joystick_nr = TRUE;
2626
2627   for (i = 0; i < MAX_PLAYERS; i++)
2628   {
2629     byte joy_action = 0;
2630
2631     joy_action = JoystickExt(i, use_as_joystick_nr);
2632     result |= joy_action;
2633
2634     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2635         joy_action != joy_action_last[i])
2636       stored_player[i].action = joy_action;
2637
2638     joy_action_last[i] = joy_action;
2639   }
2640
2641   return result;
2642 }
2643
2644 void HandleJoystick(void)
2645 {
2646   static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2647   static int joytest_last = 0;
2648   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2649   int delay_value       = GADGET_FRAME_DELAY;
2650   int joystick  = HandleJoystickForAllPlayers();
2651   int keyboard  = key_joystick_mapping;
2652   int joy       = (joystick | keyboard);
2653   int joytest   = joystick;
2654   int left      = joy & JOY_LEFT;
2655   int right     = joy & JOY_RIGHT;
2656   int up        = joy & JOY_UP;
2657   int down      = joy & JOY_DOWN;
2658   int button    = joy & JOY_BUTTON;
2659   int anybutton = AnyJoystickButton();
2660   int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2661   int dx        = (left ? -1    : right ? 1     : 0);
2662   int dy        = (up   ? -1    : down  ? 1     : 0);
2663   boolean use_delay_value_first = (joytest != joytest_last);
2664   boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2665                               anybutton == JOY_BUTTON_NEW_RELEASED);
2666
2667   if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2668   {
2669     // do not handle this button event anymore
2670     return;
2671   }
2672
2673   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2674                     game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2675                     anyTextGadgetActive()))
2676   {
2677     // leave name input in main menu or text input gadget
2678     HandleKey(KSYM_Escape, KEY_PRESSED);
2679     HandleKey(KSYM_Escape, KEY_RELEASED);
2680
2681     return;
2682   }
2683
2684   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2685   {
2686     if (game_status == GAME_MODE_PLAYING)
2687     {
2688       // when playing MM style levels, also use delay for keyboard events
2689       joytest |= keyboard;
2690
2691       // only use first delay value for new events, but not for changed events
2692       use_delay_value_first = (!joytest != !joytest_last);
2693
2694       // only use delay after the initial keyboard event
2695       delay_value = 0;
2696     }
2697
2698     // for any joystick or keyboard event, enable playfield tile cursor
2699     if (dx || dy || button)
2700       SetTileCursorEnabled(TRUE);
2701   }
2702
2703   // for any joystick event, enable playfield mouse cursor
2704   if (dx || dy || button)
2705     SetPlayfieldMouseCursorEnabled(TRUE);
2706
2707   if (joytest && !button && !DelayReached(&joytest_delay))
2708   {
2709     // delay joystick/keyboard actions if axes/keys continually pressed
2710     newbutton = dx = dy = 0;
2711   }
2712   else
2713   {
2714     // first start with longer delay, then continue with shorter delay
2715     joytest_delay.value =
2716       (use_delay_value_first ? delay_value_first : delay_value);
2717   }
2718
2719   joytest_last = joytest;
2720
2721   switch (game_status)
2722   {
2723     case GAME_MODE_TITLE:
2724     case GAME_MODE_MAIN:
2725     case GAME_MODE_NAMES:
2726     case GAME_MODE_LEVELS:
2727     case GAME_MODE_LEVELNR:
2728     case GAME_MODE_SETUP:
2729     case GAME_MODE_INFO:
2730     case GAME_MODE_SCORES:
2731     case GAME_MODE_SCOREINFO:
2732     {
2733       if (anyTextGadgetActive())
2734         break;
2735
2736       if (game_status == GAME_MODE_TITLE)
2737         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2738       else if (game_status == GAME_MODE_MAIN)
2739         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2740       else if (game_status == GAME_MODE_NAMES)
2741         HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2742       else if (game_status == GAME_MODE_LEVELS)
2743         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2744       else if (game_status == GAME_MODE_LEVELNR)
2745         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2746       else if (game_status == GAME_MODE_SETUP)
2747         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2748       else if (game_status == GAME_MODE_INFO)
2749         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2750       else if (game_status == GAME_MODE_SCORES)
2751         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2752       else if (game_status == GAME_MODE_SCOREINFO)
2753         HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2754
2755       break;
2756     }
2757
2758     case GAME_MODE_PLAYING:
2759 #if 0
2760       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2761       if (tape.playing || keyboard)
2762         newbutton = ((joy & JOY_BUTTON) != 0);
2763 #endif
2764
2765       if (newbutton && game.all_players_gone)
2766       {
2767         GameEnd();
2768
2769         return;
2770       }
2771
2772       if (tape.recording && tape.pausing && tape.use_key_actions)
2773       {
2774         if (tape.single_step)
2775         {
2776           if (joystick & JOY_ACTION)
2777             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2778         }
2779         else
2780         {
2781           if (joystick & JOY_ACTION)
2782             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2783         }
2784       }
2785
2786       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2787         HandleTileCursor(dx, dy, button);
2788
2789       break;
2790
2791     default:
2792       break;
2793   }
2794 }
2795
2796 void HandleSpecialGameControllerButtons(Event *event)
2797 {
2798   int key_status;
2799   Key key;
2800
2801   switch (event->type)
2802   {
2803     case SDL_CONTROLLERBUTTONDOWN:
2804       key_status = KEY_PRESSED;
2805       break;
2806
2807     case SDL_CONTROLLERBUTTONUP:
2808       key_status = KEY_RELEASED;
2809       break;
2810
2811     default:
2812       return;
2813   }
2814
2815   switch (event->cbutton.button)
2816   {
2817     case SDL_CONTROLLER_BUTTON_START:
2818       key = KSYM_space;
2819       break;
2820
2821     case SDL_CONTROLLER_BUTTON_BACK:
2822       key = KSYM_Escape;
2823       break;
2824
2825     default:
2826       return;
2827   }
2828
2829   HandleKey(key, key_status);
2830 }
2831
2832 void HandleSpecialGameControllerKeys(Key key, int key_status)
2833 {
2834 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2835   int button = SDL_CONTROLLER_BUTTON_INVALID;
2836
2837   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2838   if (key == KSYM_Rewind)
2839     button = SDL_CONTROLLER_BUTTON_A;
2840   else if (key == KSYM_FastForward || key == KSYM_Menu)
2841     button = SDL_CONTROLLER_BUTTON_B;
2842
2843   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2844   {
2845     Event event;
2846
2847     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2848                   SDL_CONTROLLERBUTTONUP);
2849
2850     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2851     event.cbutton.button = button;
2852     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2853                            SDL_RELEASED);
2854
2855     HandleJoystickEvent(&event);
2856   }
2857 #endif
2858 }
2859
2860 boolean DoKeysymAction(int keysym)
2861 {
2862   if (keysym < 0)
2863   {
2864     Key key = (Key)(-keysym);
2865
2866     is_global_anim_event = TRUE;
2867
2868     HandleKey(key, KEY_PRESSED);
2869     HandleKey(key, KEY_RELEASED);
2870
2871     is_global_anim_event = FALSE;
2872
2873     return TRUE;
2874   }
2875
2876   return FALSE;
2877 }