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