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