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