fixed creating additional click events from global animation events
[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 boolean HandleKeysDebug(Key key, int key_status)
1990 {
1991 #ifdef DEBUG
1992   int i;
1993
1994   if (key_status != KEY_PRESSED)
1995     return FALSE;
1996
1997   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1998   {
1999     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
2000
2001     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
2002     {
2003       if (key == setup.debug.frame_delay_key[i] &&
2004           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
2005       {
2006         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
2007                           setup.debug.frame_delay[i] : setup.game_frame_delay);
2008
2009         if (!setup.debug.frame_delay_game_only)
2010           MenuFrameDelay = GameFrameDelay;
2011
2012         SetVideoFrameDelay(GameFrameDelay);
2013
2014         if (GameFrameDelay > ONE_SECOND_DELAY)
2015           Debug("event:key:debug", "frame delay == %d ms", GameFrameDelay);
2016         else if (GameFrameDelay != 0)
2017           Debug("event:key:debug", "frame delay == %d ms (max. %d fps / %d %%)",
2018                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
2019                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
2020         else
2021           Debug("event:key:debug", "frame delay == 0 ms (maximum speed)");
2022
2023         return TRUE;
2024       }
2025     }
2026   }
2027
2028   if (game_status == GAME_MODE_PLAYING)
2029   {
2030     if (key == KSYM_d)
2031     {
2032       options.debug = !options.debug;
2033
2034       Debug("event:key:debug", "debug mode %s",
2035             (options.debug ? "enabled" : "disabled"));
2036
2037       return TRUE;
2038     }
2039     else if (key == KSYM_v)
2040     {
2041       Debug("event:key:debug", "currently using game engine version %d",
2042             game.engine_version);
2043
2044       return TRUE;
2045     }
2046   }
2047 #endif
2048
2049   return FALSE;
2050 }
2051
2052 void HandleKey(Key key, int key_status)
2053 {
2054   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2055   static boolean ignore_repeated_key = FALSE;
2056   static struct SetupKeyboardInfo ski;
2057   static struct SetupShortcutInfo ssi;
2058   static struct
2059   {
2060     Key *key_custom;
2061     Key *key_snap;
2062     Key key_default;
2063     byte action;
2064   } key_info[] =
2065   {
2066     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2067     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2068     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2069     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2070     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2071     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2072   };
2073   int joy = 0;
2074   int i;
2075
2076   if (HandleKeysDebug(key, key_status))
2077     return;             // do not handle already processed keys again
2078
2079   // map special keys (media keys / remote control buttons) to default keys
2080   if (key == KSYM_PlayPause)
2081     key = KSYM_space;
2082   else if (key == KSYM_Select)
2083     key = KSYM_Return;
2084
2085   HandleSpecialGameControllerKeys(key, key_status);
2086
2087   if (game_status == GAME_MODE_PLAYING)
2088   {
2089     // only needed for single-step tape recording mode
2090     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2091     int pnr;
2092
2093     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2094     {
2095       byte key_action = 0;
2096       byte key_snap_action = 0;
2097
2098       if (setup.input[pnr].use_joystick)
2099         continue;
2100
2101       ski = setup.input[pnr].key;
2102
2103       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2104         if (key == *key_info[i].key_custom)
2105           key_action |= key_info[i].action;
2106
2107       // use combined snap+direction keys for the first player only
2108       if (pnr == 0)
2109       {
2110         ssi = setup.shortcut;
2111
2112         // also remember normal snap key when handling snap+direction keys
2113         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2114
2115         for (i = 0; i < NUM_DIRECTIONS; i++)
2116         {
2117           if (key == *key_info[i].key_snap)
2118           {
2119             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2120             key_snap_action |= key_info[i].action;
2121
2122             tape.property_bits |= TAPE_PROPERTY_TAS_KEYS;
2123           }
2124         }
2125       }
2126
2127       if (key_status == KEY_PRESSED)
2128       {
2129         stored_player[pnr].action      |= key_action;
2130         stored_player[pnr].snap_action |= key_snap_action;
2131       }
2132       else
2133       {
2134         stored_player[pnr].action      &= ~key_action;
2135         stored_player[pnr].snap_action &= ~key_snap_action;
2136       }
2137
2138       // restore snap action if one of several pressed snap keys was released
2139       if (stored_player[pnr].snap_action)
2140         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2141
2142       if (tape.recording && tape.pausing && tape.use_key_actions)
2143       {
2144         if (tape.single_step)
2145         {
2146           if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2147           {
2148             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2149
2150             // if snap key already pressed, keep pause mode when releasing
2151             if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2152               has_snapped[pnr] = TRUE;
2153           }
2154           else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2155           {
2156             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2157
2158             if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2159                 getRedDiskReleaseFlag_SP() == 0)
2160             {
2161               // add a single inactive frame before dropping starts
2162               stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2163               stored_player[pnr].force_dropping = TRUE;
2164             }
2165           }
2166           else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2167           {
2168             // if snap key was pressed without direction, leave pause mode
2169             if (!has_snapped[pnr])
2170               TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2171
2172             has_snapped[pnr] = FALSE;
2173           }
2174         }
2175         else
2176         {
2177           // prevent key release events from un-pausing a paused game
2178           if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2179             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2180         }
2181       }
2182
2183       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2184       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2185         joy |= key_action;
2186
2187       // for any keyboard event, enable playfield mouse cursor
2188       if (key_action && key_status == KEY_PRESSED)
2189         SetPlayfieldMouseCursorEnabled(TRUE);
2190     }
2191   }
2192   else
2193   {
2194     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2195       if (key == key_info[i].key_default)
2196         joy |= key_info[i].action;
2197   }
2198
2199   if (joy)
2200   {
2201     if (key_status == KEY_PRESSED)
2202       key_joystick_mapping |= joy;
2203     else
2204       key_joystick_mapping &= ~joy;
2205
2206     HandleJoystick();
2207   }
2208
2209   if (game_status != GAME_MODE_PLAYING)
2210     key_joystick_mapping = 0;
2211
2212   if (key_status == KEY_RELEASED)
2213   {
2214     // reset flag to ignore repeated "key pressed" events after key release
2215     ignore_repeated_key = FALSE;
2216
2217     // send key release event to global animation event handling
2218     if (!is_global_anim_event)
2219       HandleGlobalAnimClicks(-1, -1, KEY_RELEASED, FALSE);
2220
2221     return;
2222   }
2223
2224   if ((key == KSYM_F11 ||
2225        ((key == KSYM_Return ||
2226          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2227       video.fullscreen_available &&
2228       !ignore_repeated_key)
2229   {
2230     setup.fullscreen = !setup.fullscreen;
2231
2232     ToggleFullscreenIfNeeded();
2233
2234     if (game_status == GAME_MODE_SETUP)
2235       RedrawSetupScreenAfterFullscreenToggle();
2236
2237     UpdateMousePosition();
2238
2239     // set flag to ignore repeated "key pressed" events
2240     ignore_repeated_key = TRUE;
2241
2242     return;
2243   }
2244
2245   if ((key == KSYM_0     || key == KSYM_KP_0 ||
2246        key == KSYM_minus || key == KSYM_KP_Subtract ||
2247        key == KSYM_plus  || key == KSYM_KP_Add ||
2248        key == KSYM_equal) &&    // ("Shift-=" is "+" on US keyboards)
2249       (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2250       video.window_scaling_available &&
2251       !video.fullscreen_enabled)
2252   {
2253     if (key == KSYM_0 || key == KSYM_KP_0)
2254       setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2255     else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2256       setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2257     else
2258       setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2259
2260     if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2261       setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2262     else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2263       setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2264
2265     ChangeWindowScalingIfNeeded();
2266
2267     if (game_status == GAME_MODE_SETUP)
2268       RedrawSetupScreenAfterFullscreenToggle();
2269
2270     UpdateMousePosition();
2271
2272     return;
2273   }
2274
2275   // some key events are handled like clicks for global animations
2276   boolean click = (!is_global_anim_event && (key == KSYM_space ||
2277                                              key == KSYM_Return ||
2278                                              key == KSYM_Escape));
2279
2280   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2281   {
2282     // do not handle this key event anymore
2283     if (key != KSYM_Escape)     // always allow ESC key to be handled
2284       return;
2285   }
2286
2287   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2288       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2289   {
2290     GameEnd();
2291
2292     return;
2293   }
2294
2295   if (game_status == GAME_MODE_MAIN &&
2296       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2297   {
2298     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2299
2300     return;
2301   }
2302
2303   if (game_status == GAME_MODE_MAIN &&
2304       (setup.internal.info_screens_from_main ||
2305        leveldir_current->info_screens_from_main) &&
2306       (key >= KSYM_KP_1 && key <= KSYM_KP_9))
2307   {
2308     DrawInfoScreen_FromMainMenu(key - KSYM_KP_1 + 1);
2309
2310     return;
2311   }
2312
2313   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2314   {
2315     if (key == setup.shortcut.save_game)
2316       TapeQuickSave();
2317     else if (key == setup.shortcut.load_game)
2318       TapeQuickLoad();
2319     else if (key == setup.shortcut.restart_game)
2320       TapeRestartGame();
2321     else if (key == setup.shortcut.pause_before_end)
2322       TapeReplayAndPauseBeforeEnd();
2323     else if (key == setup.shortcut.toggle_pause)
2324       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2325
2326     HandleTapeButtonKeys(key);
2327     HandleSoundButtonKeys(key);
2328   }
2329
2330   if (game_status == GAME_MODE_SCOREINFO)
2331   {
2332     HandleScreenGadgetKeys(key);
2333   }
2334
2335   if (game_status == GAME_MODE_PLAYING && !network_playing)
2336   {
2337     int centered_player_nr_next = -999;
2338
2339     if (key == setup.shortcut.focus_player_all)
2340       centered_player_nr_next = -1;
2341     else
2342       for (i = 0; i < MAX_PLAYERS; i++)
2343         if (key == setup.shortcut.focus_player[i])
2344           centered_player_nr_next = i;
2345
2346     if (centered_player_nr_next != -999)
2347     {
2348       game.centered_player_nr_next = centered_player_nr_next;
2349       game.set_centered_player = TRUE;
2350
2351       if (tape.recording)
2352       {
2353         tape.centered_player_nr_next = game.centered_player_nr_next;
2354         tape.set_centered_player = TRUE;
2355       }
2356     }
2357   }
2358
2359   HandleKeysSpecial(key);
2360
2361   if (HandleGadgetsKeyInput(key))
2362     return;             // do not handle already processed keys again
2363
2364   // special case: on "space" key, either continue playing or go to main menu
2365   if (game_status == GAME_MODE_SCORES && key == KSYM_space)
2366   {
2367     HandleHallOfFame(0, 0, 0, 0, MB_MENU_CONTINUE);
2368
2369     return;
2370   }
2371
2372   switch (game_status)
2373   {
2374     case GAME_MODE_PSEUDO_TYPENAME:
2375     case GAME_MODE_PSEUDO_TYPENAMES:
2376       HandleTypeName(key);
2377       break;
2378
2379     case GAME_MODE_TITLE:
2380     case GAME_MODE_MAIN:
2381     case GAME_MODE_NAMES:
2382     case GAME_MODE_LEVELS:
2383     case GAME_MODE_LEVELNR:
2384     case GAME_MODE_SETUP:
2385     case GAME_MODE_INFO:
2386     case GAME_MODE_SCORES:
2387     case GAME_MODE_SCOREINFO:
2388
2389       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2390         break;
2391
2392       switch (key)
2393       {
2394         case KSYM_space:
2395         case KSYM_Return:
2396           if (game_status == GAME_MODE_TITLE)
2397             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2398           else if (game_status == GAME_MODE_MAIN)
2399             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2400           else if (game_status == GAME_MODE_NAMES)
2401             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_CHOICE);
2402           else if (game_status == GAME_MODE_LEVELS)
2403             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2404           else if (game_status == GAME_MODE_LEVELNR)
2405             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2406           else if (game_status == GAME_MODE_SETUP)
2407             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2408           else if (game_status == GAME_MODE_INFO)
2409             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2410           else if (game_status == GAME_MODE_SCORES)
2411             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2412           else if (game_status == GAME_MODE_SCOREINFO)
2413             HandleScoreInfo(0, 0, 0, 0, MB_MENU_CHOICE);
2414           break;
2415
2416         case KSYM_Escape:
2417           if (game_status != GAME_MODE_MAIN)
2418             FadeSkipNextFadeIn();
2419
2420           if (game_status == GAME_MODE_TITLE)
2421             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2422           else if (game_status == GAME_MODE_NAMES)
2423             HandleChoosePlayerName(0, 0, 0, 0, MB_MENU_LEAVE);
2424           else if (game_status == GAME_MODE_LEVELS)
2425             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2426           else if (game_status == GAME_MODE_LEVELNR)
2427             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2428           else if (game_status == GAME_MODE_SETUP)
2429             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2430           else if (game_status == GAME_MODE_INFO)
2431             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2432           else if (game_status == GAME_MODE_SCORES)
2433             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2434           else if (game_status == GAME_MODE_SCOREINFO)
2435             HandleScoreInfo(0, 0, 0, 0, MB_MENU_LEAVE);
2436           break;
2437
2438         case KSYM_Page_Up:
2439           if (game_status == GAME_MODE_NAMES)
2440             HandleChoosePlayerName(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2441           else if (game_status == GAME_MODE_LEVELS)
2442             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2443           else if (game_status == GAME_MODE_LEVELNR)
2444             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2445           else if (game_status == GAME_MODE_SETUP)
2446             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2447           else if (game_status == GAME_MODE_INFO)
2448             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2449           else if (game_status == GAME_MODE_SCORES)
2450             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2451           else if (game_status == GAME_MODE_SCOREINFO)
2452             HandleScoreInfo(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2453           break;
2454
2455         case KSYM_Page_Down:
2456           if (game_status == GAME_MODE_NAMES)
2457             HandleChoosePlayerName(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2458           else if (game_status == GAME_MODE_LEVELS)
2459             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2460           else if (game_status == GAME_MODE_LEVELNR)
2461             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2462           else if (game_status == GAME_MODE_SETUP)
2463             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2464           else if (game_status == GAME_MODE_INFO)
2465             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2466           else if (game_status == GAME_MODE_SCORES)
2467             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2468           else if (game_status == GAME_MODE_SCOREINFO)
2469             HandleScoreInfo(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2470           break;
2471
2472         default:
2473           break;
2474       }
2475       break;
2476
2477     case GAME_MODE_EDITOR:
2478       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2479         HandleLevelEditorKeyInput(key);
2480       break;
2481
2482     case GAME_MODE_PLAYING:
2483     {
2484       switch (key)
2485       {
2486         case KSYM_Escape:
2487           RequestQuitGame(TRUE);
2488           break;
2489
2490         default:
2491           break;
2492       }
2493       break;
2494     }
2495
2496     default:
2497       if (key == KSYM_Escape)
2498       {
2499         SetGameStatus(GAME_MODE_MAIN);
2500
2501         DrawMainMenu();
2502
2503         return;
2504       }
2505   }
2506 }
2507
2508 void HandleNoEvent(void)
2509 {
2510   HandleMouseCursor();
2511
2512   switch (game_status)
2513   {
2514     case GAME_MODE_PLAYING:
2515       HandleButtonOrFinger(-1, -1, -1);
2516       break;
2517   }
2518 }
2519
2520 void HandleEventActions(void)
2521 {
2522   // if (button_status && game_status != GAME_MODE_PLAYING)
2523   if (button_status && (game_status != GAME_MODE_PLAYING ||
2524                         tape.pausing ||
2525                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2526   {
2527     HandleButton(0, 0, button_status, -button_status);
2528   }
2529   else
2530   {
2531     HandleJoystick();
2532   }
2533
2534   if (network.enabled)
2535     HandleNetworking();
2536
2537   switch (game_status)
2538   {
2539     case GAME_MODE_MAIN:
2540       DrawPreviewLevelAnimation();
2541       break;
2542
2543     case GAME_MODE_EDITOR:
2544       HandleLevelEditorIdle();
2545       break;
2546
2547     default:
2548       break;
2549   }
2550 }
2551
2552 static void HandleTileCursor(int dx, int dy, int button)
2553 {
2554   if (!dx || !button)
2555     ClearPlayerMouseAction();
2556
2557   if (!dx && !dy)
2558     return;
2559
2560   if (button)
2561   {
2562     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2563                          (dx < 0 ? MB_LEFTBUTTON :
2564                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2565   }
2566   else if (!tile_cursor.moving)
2567   {
2568     int old_xpos = tile_cursor.xpos;
2569     int old_ypos = tile_cursor.ypos;
2570     int new_xpos = tile_cursor.xpos + dx;
2571     int new_ypos = tile_cursor.ypos + dy;
2572
2573     if (!IN_LEV_FIELD(new_xpos, old_ypos) || !IN_SCR_FIELD(new_xpos, old_ypos))
2574       new_xpos = old_xpos;
2575
2576     if (!IN_LEV_FIELD(old_xpos, new_ypos) || !IN_SCR_FIELD(old_xpos, new_ypos))
2577       new_ypos = old_ypos;
2578
2579     SetTileCursorTargetXY(new_xpos, new_ypos);
2580   }
2581 }
2582
2583 static int HandleJoystickForAllPlayers(void)
2584 {
2585   int i;
2586   int result = 0;
2587   boolean no_joysticks_configured = TRUE;
2588   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2589   static byte joy_action_last[MAX_PLAYERS];
2590
2591   for (i = 0; i < MAX_PLAYERS; i++)
2592     if (setup.input[i].use_joystick)
2593       no_joysticks_configured = FALSE;
2594
2595   // if no joysticks configured, map connected joysticks to players
2596   if (no_joysticks_configured)
2597     use_as_joystick_nr = TRUE;
2598
2599   for (i = 0; i < MAX_PLAYERS; i++)
2600   {
2601     byte joy_action = 0;
2602
2603     joy_action = JoystickExt(i, use_as_joystick_nr);
2604     result |= joy_action;
2605
2606     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2607         joy_action != joy_action_last[i])
2608       stored_player[i].action = joy_action;
2609
2610     joy_action_last[i] = joy_action;
2611   }
2612
2613   return result;
2614 }
2615
2616 void HandleJoystick(void)
2617 {
2618   static DelayCounter joytest_delay = { GADGET_FRAME_DELAY };
2619   static int joytest_last = 0;
2620   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2621   int delay_value       = GADGET_FRAME_DELAY;
2622   int joystick  = HandleJoystickForAllPlayers();
2623   int keyboard  = key_joystick_mapping;
2624   int joy       = (joystick | keyboard);
2625   int joytest   = joystick;
2626   int left      = joy & JOY_LEFT;
2627   int right     = joy & JOY_RIGHT;
2628   int up        = joy & JOY_UP;
2629   int down      = joy & JOY_DOWN;
2630   int button    = joy & JOY_BUTTON;
2631   int anybutton = AnyJoystickButton();
2632   int newbutton = (anybutton == JOY_BUTTON_NEW_PRESSED);
2633   int dx        = (left ? -1    : right ? 1     : 0);
2634   int dy        = (up   ? -1    : down  ? 1     : 0);
2635   boolean use_delay_value_first = (joytest != joytest_last);
2636   boolean new_button_event = (anybutton == JOY_BUTTON_NEW_PRESSED ||
2637                               anybutton == JOY_BUTTON_NEW_RELEASED);
2638
2639   if (new_button_event && HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2640   {
2641     // do not handle this button event anymore
2642     return;
2643   }
2644
2645   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2646                     game_status == GAME_MODE_PSEUDO_TYPENAMES ||
2647                     anyTextGadgetActive()))
2648   {
2649     // leave name input in main menu or text input gadget
2650     HandleKey(KSYM_Escape, KEY_PRESSED);
2651     HandleKey(KSYM_Escape, KEY_RELEASED);
2652
2653     return;
2654   }
2655
2656   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2657   {
2658     if (game_status == GAME_MODE_PLAYING)
2659     {
2660       // when playing MM style levels, also use delay for keyboard events
2661       joytest |= keyboard;
2662
2663       // only use first delay value for new events, but not for changed events
2664       use_delay_value_first = (!joytest != !joytest_last);
2665
2666       // only use delay after the initial keyboard event
2667       delay_value = 0;
2668     }
2669
2670     // for any joystick or keyboard event, enable playfield tile cursor
2671     if (dx || dy || button)
2672       SetTileCursorEnabled(TRUE);
2673   }
2674
2675   // for any joystick event, enable playfield mouse cursor
2676   if (dx || dy || button)
2677     SetPlayfieldMouseCursorEnabled(TRUE);
2678
2679   if (joytest && !button && !DelayReached(&joytest_delay))
2680   {
2681     // delay joystick/keyboard actions if axes/keys continually pressed
2682     newbutton = dx = dy = 0;
2683   }
2684   else
2685   {
2686     // first start with longer delay, then continue with shorter delay
2687     joytest_delay.value =
2688       (use_delay_value_first ? delay_value_first : delay_value);
2689   }
2690
2691   joytest_last = joytest;
2692
2693   switch (game_status)
2694   {
2695     case GAME_MODE_TITLE:
2696     case GAME_MODE_MAIN:
2697     case GAME_MODE_NAMES:
2698     case GAME_MODE_LEVELS:
2699     case GAME_MODE_LEVELNR:
2700     case GAME_MODE_SETUP:
2701     case GAME_MODE_INFO:
2702     case GAME_MODE_SCORES:
2703     case GAME_MODE_SCOREINFO:
2704     {
2705       if (anyTextGadgetActive())
2706         break;
2707
2708       if (game_status == GAME_MODE_TITLE)
2709         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2710       else if (game_status == GAME_MODE_MAIN)
2711         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2712       else if (game_status == GAME_MODE_NAMES)
2713         HandleChoosePlayerName(0,0,dx,dy,newbutton?MB_MENU_CHOICE:MB_MENU_MARK);
2714       else if (game_status == GAME_MODE_LEVELS)
2715         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2716       else if (game_status == GAME_MODE_LEVELNR)
2717         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2718       else if (game_status == GAME_MODE_SETUP)
2719         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2720       else if (game_status == GAME_MODE_INFO)
2721         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2722       else if (game_status == GAME_MODE_SCORES)
2723         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2724       else if (game_status == GAME_MODE_SCOREINFO)
2725         HandleScoreInfo(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2726
2727       break;
2728     }
2729
2730     case GAME_MODE_PLAYING:
2731 #if 0
2732       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2733       if (tape.playing || keyboard)
2734         newbutton = ((joy & JOY_BUTTON) != 0);
2735 #endif
2736
2737       if (newbutton && game.all_players_gone)
2738       {
2739         GameEnd();
2740
2741         return;
2742       }
2743
2744       if (tape.recording && tape.pausing && tape.use_key_actions)
2745       {
2746         if (tape.single_step)
2747         {
2748           if (joystick & JOY_ACTION)
2749             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2750         }
2751         else
2752         {
2753           if (joystick & JOY_ACTION)
2754             TapeTogglePause(TAPE_TOGGLE_MANUAL);
2755         }
2756       }
2757
2758       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2759         HandleTileCursor(dx, dy, button);
2760
2761       break;
2762
2763     default:
2764       break;
2765   }
2766 }
2767
2768 void HandleSpecialGameControllerButtons(Event *event)
2769 {
2770   int key_status;
2771   Key key;
2772
2773   switch (event->type)
2774   {
2775     case SDL_CONTROLLERBUTTONDOWN:
2776       key_status = KEY_PRESSED;
2777       break;
2778
2779     case SDL_CONTROLLERBUTTONUP:
2780       key_status = KEY_RELEASED;
2781       break;
2782
2783     default:
2784       return;
2785   }
2786
2787   switch (event->cbutton.button)
2788   {
2789     case SDL_CONTROLLER_BUTTON_START:
2790       key = KSYM_space;
2791       break;
2792
2793     case SDL_CONTROLLER_BUTTON_BACK:
2794       key = KSYM_Escape;
2795       break;
2796
2797     default:
2798       return;
2799   }
2800
2801   HandleKey(key, key_status);
2802 }
2803
2804 void HandleSpecialGameControllerKeys(Key key, int key_status)
2805 {
2806 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2807   int button = SDL_CONTROLLER_BUTTON_INVALID;
2808
2809   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2810   if (key == KSYM_Rewind)
2811     button = SDL_CONTROLLER_BUTTON_A;
2812   else if (key == KSYM_FastForward || key == KSYM_Menu)
2813     button = SDL_CONTROLLER_BUTTON_B;
2814
2815   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2816   {
2817     Event event;
2818
2819     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2820                   SDL_CONTROLLERBUTTONUP);
2821
2822     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2823     event.cbutton.button = button;
2824     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2825                            SDL_RELEASED);
2826
2827     HandleJoystickEvent(&event);
2828   }
2829 #endif
2830 }
2831
2832 boolean DoKeysymAction(int keysym)
2833 {
2834   if (keysym < 0)
2835   {
2836     Key key = (Key)(-keysym);
2837
2838     is_global_anim_event = TRUE;
2839
2840     HandleKey(key, KEY_PRESSED);
2841     HandleKey(key, KEY_RELEASED);
2842
2843     is_global_anim_event = FALSE;
2844
2845     return TRUE;
2846   }
2847
2848   return FALSE;
2849 }