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