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