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