added Android project files
[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     // for any other "real" key event, disable overlay touch buttons
1454     runtime.uses_touch_device = FALSE;
1455   }
1456 #endif
1457
1458   HandleKeyModState(keymod, key_status);
1459
1460   // only handle raw key input without text modifier keys pressed
1461   if (!checkTextInputKeyModState())
1462     HandleKey(key, key_status);
1463 }
1464
1465 static int HandleDropFileEvent(char *filename)
1466 {
1467   Error(ERR_DEBUG, "DROP FILE EVENT: '%s'", filename);
1468
1469   // check and extract dropped zip files into correct user data directory
1470   if (!strSuffixLower(filename, ".zip"))
1471   {
1472     Error(ERR_WARN, "file '%s' not supported", filename);
1473
1474     return TREE_TYPE_UNDEFINED;
1475   }
1476
1477   TreeInfo *tree_node = NULL;
1478   int tree_type = GetZipFileTreeType(filename);
1479   char *directory = TREE_USERDIR(tree_type);
1480
1481   if (directory == NULL)
1482   {
1483     Error(ERR_WARN, "zip file '%s' has invalid content!", filename);
1484
1485     return TREE_TYPE_UNDEFINED;
1486   }
1487
1488   if (tree_type == TREE_TYPE_LEVEL_DIR &&
1489       game_status == GAME_MODE_LEVELS &&
1490       leveldir_current->node_parent != NULL)
1491   {
1492     // extract new level set next to currently selected level set
1493     tree_node = leveldir_current;
1494
1495     // get parent directory of currently selected level set directory
1496     directory = getLevelDirFromTreeInfo(leveldir_current->node_parent);
1497
1498     // use private level directory instead of top-level package level directory
1499     if (strPrefix(directory, options.level_directory) &&
1500         strEqual(leveldir_current->node_parent->fullpath, "."))
1501       directory = getUserLevelDir(NULL);
1502   }
1503
1504   // extract level or artwork set from zip file to target directory
1505   char *top_dir = ExtractZipFileIntoDirectory(filename, directory, tree_type);
1506
1507   if (top_dir == NULL)
1508   {
1509     // error message already issued by "ExtractZipFileIntoDirectory()"
1510
1511     return TREE_TYPE_UNDEFINED;
1512   }
1513
1514   // add extracted level or artwork set to tree info structure
1515   AddTreeSetToTreeInfo(tree_node, directory, top_dir, tree_type);
1516
1517   // update menu screen (and possibly change current level set)
1518   DrawScreenAfterAddingSet(top_dir, tree_type);
1519
1520   return tree_type;
1521 }
1522
1523 static void HandleDropTextEvent(char *text)
1524 {
1525   Error(ERR_DEBUG, "DROP TEXT EVENT: '%s'", text);
1526 }
1527
1528 static void HandleDropCompleteEvent(int num_level_sets_succeeded,
1529                                     int num_artwork_sets_succeeded,
1530                                     int num_files_failed)
1531 {
1532   // only show request dialog if no other request dialog already active
1533   if (game.request_active)
1534     return;
1535
1536   // this case can happen with drag-and-drop with older SDL versions
1537   if (num_level_sets_succeeded == 0 &&
1538       num_artwork_sets_succeeded == 0 &&
1539       num_files_failed == 0)
1540     return;
1541
1542   char message[100];
1543
1544   if (num_level_sets_succeeded > 0 || num_artwork_sets_succeeded > 0)
1545   {
1546     char message_part1[50];
1547
1548     sprintf(message_part1, "New %s set%s added",
1549             (num_artwork_sets_succeeded == 0 ? "level" :
1550              num_level_sets_succeeded == 0 ? "artwork" : "level and artwork"),
1551             (num_level_sets_succeeded +
1552              num_artwork_sets_succeeded > 1 ? "s" : ""));
1553
1554     if (num_files_failed > 0)
1555       sprintf(message, "%s, but %d dropped file%s failed!",
1556               message_part1, num_files_failed, num_files_failed > 1 ? "s" : "");
1557     else
1558       sprintf(message, "%s!", message_part1);
1559   }
1560   else if (num_files_failed > 0)
1561   {
1562     sprintf(message, "Failed to process dropped file%s!",
1563             num_files_failed > 1 ? "s" : "");
1564   }
1565
1566   Request(message, REQ_CONFIRM);
1567 }
1568
1569 void HandleDropEvent(Event *event)
1570 {
1571   static boolean confirm_on_drop_complete = FALSE;
1572   static int num_level_sets_succeeded = 0;
1573   static int num_artwork_sets_succeeded = 0;
1574   static int num_files_failed = 0;
1575
1576   switch (event->type)
1577   {
1578     case SDL_DROPBEGIN:
1579     {
1580       confirm_on_drop_complete = TRUE;
1581       num_level_sets_succeeded = 0;
1582       num_artwork_sets_succeeded = 0;
1583       num_files_failed = 0;
1584
1585       break;
1586     }
1587
1588     case SDL_DROPFILE:
1589     {
1590       int tree_type = HandleDropFileEvent(event->drop.file);
1591
1592       if (tree_type == TREE_TYPE_LEVEL_DIR)
1593         num_level_sets_succeeded++;
1594       else if (tree_type == TREE_TYPE_GRAPHICS_DIR ||
1595                tree_type == TREE_TYPE_SOUNDS_DIR ||
1596                tree_type == TREE_TYPE_MUSIC_DIR)
1597         num_artwork_sets_succeeded++;
1598       else
1599         num_files_failed++;
1600
1601       // SDL_DROPBEGIN / SDL_DROPCOMPLETE did not exist in older SDL versions
1602       if (!confirm_on_drop_complete)
1603       {
1604         // process all remaining events, including further SDL_DROPFILE events
1605         ClearEventQueue();
1606
1607         HandleDropCompleteEvent(num_level_sets_succeeded,
1608                                 num_artwork_sets_succeeded,
1609                                 num_files_failed);
1610
1611         num_level_sets_succeeded = 0;
1612         num_artwork_sets_succeeded = 0;
1613         num_files_failed = 0;
1614       }
1615
1616       break;
1617     }
1618
1619     case SDL_DROPTEXT:
1620     {
1621       HandleDropTextEvent(event->drop.file);
1622
1623       break;
1624     }
1625
1626     case SDL_DROPCOMPLETE:
1627     {
1628       HandleDropCompleteEvent(num_level_sets_succeeded,
1629                               num_artwork_sets_succeeded,
1630                               num_files_failed);
1631
1632       break;
1633     }
1634   }
1635
1636   if (event->drop.file != NULL)
1637     SDL_free(event->drop.file);
1638 }
1639
1640 void HandleUserEvent(UserEvent *event)
1641 {
1642   switch (event->code)
1643   {
1644     case USEREVENT_ANIM_DELAY_ACTION:
1645     case USEREVENT_ANIM_EVENT_ACTION:
1646       // execute action functions until matching action was found
1647       if (DoKeysymAction(event->value1) ||
1648           DoGadgetAction(event->value1) ||
1649           DoScreenAction(event->value1))
1650         return;
1651       break;
1652
1653     default:
1654       break;
1655   }
1656 }
1657
1658 void HandleButton(int mx, int my, int button, int button_nr)
1659 {
1660   static int old_mx = 0, old_my = 0;
1661   boolean button_hold = FALSE;
1662   boolean handle_gadgets = TRUE;
1663
1664   if (button_nr < 0)
1665   {
1666     mx = old_mx;
1667     my = old_my;
1668     button_nr = -button_nr;
1669     button_hold = TRUE;
1670   }
1671   else
1672   {
1673     old_mx = mx;
1674     old_my = my;
1675   }
1676
1677 #if defined(PLATFORM_ANDROID)
1678   // when playing, only handle gadgets when using "follow finger" controls
1679   // or when using touch controls in combination with the MM game engine
1680   // or when using gadgets that do not overlap with virtual buttons
1681   handle_gadgets =
1682     (game_status != GAME_MODE_PLAYING ||
1683      level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1684      strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER) ||
1685      (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS) &&
1686       !CheckVirtualButtonPressed(mx, my, button)));
1687
1688   // always recognize potentially releasing already pressed gadgets
1689   if (button == MB_RELEASED)
1690     handle_gadgets = TRUE;
1691
1692   // always recognize pressing or releasing overlay touch buttons
1693   if (CheckPosition_OverlayTouchButtons(mx, my, button) && !motion_status)
1694     handle_gadgets = TRUE;
1695 #endif
1696
1697   if (HandleGlobalAnimClicks(mx, my, button, FALSE))
1698   {
1699     // do not handle this button event anymore
1700     return;             // force mouse event not to be handled at all
1701   }
1702
1703   if (handle_gadgets && HandleGadgets(mx, my, button))
1704   {
1705     // do not handle this button event anymore
1706     mx = my = -32;      // force mouse event to be outside screen tiles
1707   }
1708
1709   if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1710     return;
1711
1712   // do not use scroll wheel button events for anything other than gadgets
1713   if (IS_WHEEL_BUTTON(button_nr))
1714     return;
1715
1716   switch (game_status)
1717   {
1718     case GAME_MODE_TITLE:
1719       HandleTitleScreen(mx, my, 0, 0, button);
1720       break;
1721
1722     case GAME_MODE_MAIN:
1723       HandleMainMenu(mx, my, 0, 0, button);
1724       break;
1725
1726     case GAME_MODE_PSEUDO_TYPENAME:
1727       HandleTypeName(0, KSYM_Return);
1728       break;
1729
1730     case GAME_MODE_LEVELS:
1731       HandleChooseLevelSet(mx, my, 0, 0, button);
1732       break;
1733
1734     case GAME_MODE_LEVELNR:
1735       HandleChooseLevelNr(mx, my, 0, 0, button);
1736       break;
1737
1738     case GAME_MODE_SCORES:
1739       HandleHallOfFame(0, 0, 0, 0, button);
1740       break;
1741
1742     case GAME_MODE_EDITOR:
1743       HandleLevelEditorIdle();
1744       break;
1745
1746     case GAME_MODE_INFO:
1747       HandleInfoScreen(mx, my, 0, 0, button);
1748       break;
1749
1750     case GAME_MODE_SETUP:
1751       HandleSetupScreen(mx, my, 0, 0, button);
1752       break;
1753
1754     case GAME_MODE_PLAYING:
1755       if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1756         HandleButtonOrFinger(mx, my, button);
1757       else
1758         SetPlayerMouseAction(mx, my, button);
1759
1760 #ifdef DEBUG
1761       if (button == MB_PRESSED && !motion_status && !button_hold &&
1762           IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1763         DumpTileFromScreen(mx, my);
1764 #endif
1765
1766       break;
1767
1768     default:
1769       break;
1770   }
1771 }
1772
1773 static boolean is_string_suffix(char *string, char *suffix)
1774 {
1775   int string_len = strlen(string);
1776   int suffix_len = strlen(suffix);
1777
1778   if (suffix_len > string_len)
1779     return FALSE;
1780
1781   return (strEqual(&string[string_len - suffix_len], suffix));
1782 }
1783
1784 #define MAX_CHEAT_INPUT_LEN     32
1785
1786 static void HandleKeysSpecial(Key key)
1787 {
1788   static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1789   char letter = getCharFromKey(key);
1790   int cheat_input_len = strlen(cheat_input);
1791   int i;
1792
1793   if (letter == 0)
1794     return;
1795
1796   if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1797   {
1798     for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1799       cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1800
1801     cheat_input_len = MAX_CHEAT_INPUT_LEN;
1802   }
1803
1804   cheat_input[cheat_input_len++] = letter;
1805   cheat_input[cheat_input_len] = '\0';
1806
1807 #if DEBUG_EVENTS_KEY
1808   Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1809 #endif
1810
1811   if (game_status == GAME_MODE_MAIN)
1812   {
1813     if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1814         is_string_suffix(cheat_input, ":ist"))
1815     {
1816       InsertSolutionTape();
1817     }
1818     else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1819              is_string_suffix(cheat_input, ":pst"))
1820     {
1821       PlaySolutionTape();
1822     }
1823     else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1824              is_string_suffix(cheat_input, ":rg"))
1825     {
1826       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1827       DrawMainMenu();
1828     }
1829     else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1830              is_string_suffix(cheat_input, ":rs"))
1831     {
1832       ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1833       DrawMainMenu();
1834     }
1835     else if (is_string_suffix(cheat_input, ":reload-music") ||
1836              is_string_suffix(cheat_input, ":rm"))
1837     {
1838       ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1839       DrawMainMenu();
1840     }
1841     else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1842              is_string_suffix(cheat_input, ":ra"))
1843     {
1844       ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1845                           1 << ARTWORK_TYPE_SOUNDS |
1846                           1 << ARTWORK_TYPE_MUSIC);
1847       DrawMainMenu();
1848     }
1849     else if (is_string_suffix(cheat_input, ":dump-level") ||
1850              is_string_suffix(cheat_input, ":dl"))
1851     {
1852       DumpLevel(&level);
1853     }
1854     else if (is_string_suffix(cheat_input, ":dump-tape") ||
1855              is_string_suffix(cheat_input, ":dt"))
1856     {
1857       DumpTape(&tape);
1858     }
1859     else if (is_string_suffix(cheat_input, ":fix-tape") ||
1860              is_string_suffix(cheat_input, ":ft"))
1861     {
1862       /* fix single-player tapes that contain player input for more than one
1863          player (due to a bug in 3.3.1.2 and earlier versions), which results
1864          in playing levels with more than one player in multi-player mode,
1865          even though the tape was originally recorded in single-player mode */
1866
1867       // remove player input actions for all players but the first one
1868       for (i = 1; i < MAX_PLAYERS; i++)
1869         tape.player_participates[i] = FALSE;
1870
1871       tape.changed = TRUE;
1872     }
1873     else if (is_string_suffix(cheat_input, ":save-native-level") ||
1874              is_string_suffix(cheat_input, ":snl"))
1875     {
1876       SaveNativeLevel(&level);
1877     }
1878     else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1879              is_string_suffix(cheat_input, ":fps"))
1880     {
1881       global.show_frames_per_second = !global.show_frames_per_second;
1882     }
1883   }
1884   else if (game_status == GAME_MODE_PLAYING)
1885   {
1886 #ifdef DEBUG
1887     if (is_string_suffix(cheat_input, ".q"))
1888       DEBUG_SetMaximumDynamite();
1889 #endif
1890   }
1891   else if (game_status == GAME_MODE_EDITOR)
1892   {
1893     if (is_string_suffix(cheat_input, ":dump-brush") ||
1894         is_string_suffix(cheat_input, ":DB"))
1895     {
1896       DumpBrush();
1897     }
1898     else if (is_string_suffix(cheat_input, ":DDB"))
1899     {
1900       DumpBrush_Small();
1901     }
1902
1903     if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1904     {
1905       if (letter == 'x')        // copy brush to clipboard (small size)
1906       {
1907         CopyBrushToClipboard_Small();
1908       }
1909       else if (letter == 'c')   // copy brush to clipboard (normal size)
1910       {
1911         CopyBrushToClipboard();
1912       }
1913       else if (letter == 'v')   // paste brush from Clipboard
1914       {
1915         CopyClipboardToBrush();
1916       }
1917       else if (letter == 'z')   // undo or redo last operation
1918       {
1919         if (GetKeyModState() & KMOD_Shift)
1920           RedoLevelEditorOperation();
1921         else
1922           UndoLevelEditorOperation();
1923       }
1924     }
1925   }
1926
1927   // special key shortcuts for all game modes
1928   if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1929       is_string_suffix(cheat_input, ":dea") ||
1930       is_string_suffix(cheat_input, ":DEA"))
1931   {
1932     DumpGadgetIdentifiers();
1933     DumpScreenIdentifiers();
1934   }
1935 }
1936
1937 boolean HandleKeysDebug(Key key, int key_status)
1938 {
1939 #ifdef DEBUG
1940   int i;
1941
1942   if (key_status != KEY_PRESSED)
1943     return FALSE;
1944
1945   if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1946   {
1947     boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1948
1949     for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1950     {
1951       if (key == setup.debug.frame_delay_key[i] &&
1952           (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1953       {
1954         GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1955                           setup.debug.frame_delay[i] : setup.game_frame_delay);
1956
1957         if (!setup.debug.frame_delay_game_only)
1958           MenuFrameDelay = GameFrameDelay;
1959
1960         SetVideoFrameDelay(GameFrameDelay);
1961
1962         if (GameFrameDelay > ONE_SECOND_DELAY)
1963           Error(ERR_INFO, "frame delay == %d ms", GameFrameDelay);
1964         else if (GameFrameDelay != 0)
1965           Error(ERR_INFO, "frame delay == %d ms (max. %d fps / %d %%)",
1966                 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1967                 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1968         else
1969           Error(ERR_INFO, "frame delay == 0 ms (maximum speed)");
1970
1971         return TRUE;
1972       }
1973     }
1974   }
1975
1976   if (game_status == GAME_MODE_PLAYING)
1977   {
1978     if (key == KSYM_d)
1979     {
1980       options.debug = !options.debug;
1981
1982       Error(ERR_INFO, "debug mode %s",
1983             (options.debug ? "enabled" : "disabled"));
1984
1985       return TRUE;
1986     }
1987     else if (key == KSYM_v)
1988     {
1989       Error(ERR_INFO, "currently using game engine version %d",
1990             game.engine_version);
1991
1992       return TRUE;
1993     }
1994   }
1995 #endif
1996
1997   return FALSE;
1998 }
1999
2000 void HandleKey(Key key, int key_status)
2001 {
2002   boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
2003   static boolean ignore_repeated_key = FALSE;
2004   static struct SetupKeyboardInfo ski;
2005   static struct SetupShortcutInfo ssi;
2006   static struct
2007   {
2008     Key *key_custom;
2009     Key *key_snap;
2010     Key key_default;
2011     byte action;
2012   } key_info[] =
2013   {
2014     { &ski.left,  &ssi.snap_left,  DEFAULT_KEY_LEFT,  JOY_LEFT        },
2015     { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT       },
2016     { &ski.up,    &ssi.snap_up,    DEFAULT_KEY_UP,    JOY_UP          },
2017     { &ski.down,  &ssi.snap_down,  DEFAULT_KEY_DOWN,  JOY_DOWN        },
2018     { &ski.snap,  NULL,            DEFAULT_KEY_SNAP,  JOY_BUTTON_SNAP },
2019     { &ski.drop,  NULL,            DEFAULT_KEY_DROP,  JOY_BUTTON_DROP }
2020   };
2021   int joy = 0;
2022   int i;
2023
2024   if (HandleKeysDebug(key, key_status))
2025     return;             // do not handle already processed keys again
2026
2027   // map special keys (media keys / remote control buttons) to default keys
2028   if (key == KSYM_PlayPause)
2029     key = KSYM_space;
2030   else if (key == KSYM_Select)
2031     key = KSYM_Return;
2032
2033   HandleSpecialGameControllerKeys(key, key_status);
2034
2035   if (game_status == GAME_MODE_PLAYING)
2036   {
2037     // only needed for single-step tape recording mode
2038     static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
2039     int pnr;
2040
2041     for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
2042     {
2043       byte key_action = 0;
2044       byte key_snap_action = 0;
2045
2046       if (setup.input[pnr].use_joystick)
2047         continue;
2048
2049       ski = setup.input[pnr].key;
2050
2051       for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2052         if (key == *key_info[i].key_custom)
2053           key_action |= key_info[i].action;
2054
2055       // use combined snap+direction keys for the first player only
2056       if (pnr == 0)
2057       {
2058         ssi = setup.shortcut;
2059
2060         // also remember normal snap key when handling snap+direction keys
2061         key_snap_action |= key_action & JOY_BUTTON_SNAP;
2062
2063         for (i = 0; i < NUM_DIRECTIONS; i++)
2064         {
2065           if (key == *key_info[i].key_snap)
2066           {
2067             key_action      |= key_info[i].action | JOY_BUTTON_SNAP;
2068             key_snap_action |= key_info[i].action;
2069           }
2070         }
2071       }
2072
2073       if (key_status == KEY_PRESSED)
2074       {
2075         stored_player[pnr].action      |= key_action;
2076         stored_player[pnr].snap_action |= key_snap_action;
2077       }
2078       else
2079       {
2080         stored_player[pnr].action      &= ~key_action;
2081         stored_player[pnr].snap_action &= ~key_snap_action;
2082       }
2083
2084       // restore snap action if one of several pressed snap keys was released
2085       if (stored_player[pnr].snap_action)
2086         stored_player[pnr].action |= JOY_BUTTON_SNAP;
2087
2088       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2089       {
2090         if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
2091         {
2092           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2093
2094           // if snap key already pressed, keep pause mode when releasing
2095           if (stored_player[pnr].action & KEY_BUTTON_SNAP)
2096             has_snapped[pnr] = TRUE;
2097         }
2098         else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
2099         {
2100           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2101
2102           if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
2103               getRedDiskReleaseFlag_SP() == 0)
2104           {
2105             // add a single inactive frame before dropping starts
2106             stored_player[pnr].action &= ~KEY_BUTTON_DROP;
2107             stored_player[pnr].force_dropping = TRUE;
2108           }
2109         }
2110         else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
2111         {
2112           // if snap key was pressed without direction, leave pause mode
2113           if (!has_snapped[pnr])
2114             TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2115
2116           has_snapped[pnr] = FALSE;
2117         }
2118       }
2119       else if (tape.recording && tape.pausing && !tape.use_mouse)
2120       {
2121         // prevent key release events from un-pausing a paused game
2122         if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
2123           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2124       }
2125
2126       // for MM style levels, handle in-game keyboard input in HandleJoystick()
2127       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2128         joy |= key_action;
2129     }
2130   }
2131   else
2132   {
2133     for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
2134       if (key == key_info[i].key_default)
2135         joy |= key_info[i].action;
2136   }
2137
2138   if (joy)
2139   {
2140     if (key_status == KEY_PRESSED)
2141       key_joystick_mapping |= joy;
2142     else
2143       key_joystick_mapping &= ~joy;
2144
2145     HandleJoystick();
2146   }
2147
2148   if (game_status != GAME_MODE_PLAYING)
2149     key_joystick_mapping = 0;
2150
2151   if (key_status == KEY_RELEASED)
2152   {
2153     // reset flag to ignore repeated "key pressed" events after key release
2154     ignore_repeated_key = FALSE;
2155
2156     return;
2157   }
2158
2159   if ((key == KSYM_F11 ||
2160        ((key == KSYM_Return ||
2161          key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2162       video.fullscreen_available &&
2163       !ignore_repeated_key)
2164   {
2165     setup.fullscreen = !setup.fullscreen;
2166
2167     ToggleFullscreenOrChangeWindowScalingIfNeeded();
2168
2169     if (game_status == GAME_MODE_SETUP)
2170       RedrawSetupScreenAfterFullscreenToggle();
2171
2172     UpdateMousePosition();
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     UpdateMousePosition();
2206
2207     return;
2208   }
2209
2210   // some key events are handled like clicks for global animations
2211   boolean click = (key == KSYM_space ||
2212                    key == KSYM_Return ||
2213                    key == KSYM_Escape);
2214
2215   if (click && HandleGlobalAnimClicks(-1, -1, MB_LEFTBUTTON, TRUE))
2216   {
2217     // do not handle this key event anymore
2218     if (key != KSYM_Escape)     // always allow ESC key to be handled
2219       return;
2220   }
2221
2222   if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2223       (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2224   {
2225     GameEnd();
2226
2227     return;
2228   }
2229
2230   if (game_status == GAME_MODE_MAIN &&
2231       (key == setup.shortcut.toggle_pause || key == KSYM_space))
2232   {
2233     StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2234
2235     return;
2236   }
2237
2238   if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2239   {
2240     if (key == setup.shortcut.save_game)
2241       TapeQuickSave();
2242     else if (key == setup.shortcut.load_game)
2243       TapeQuickLoad();
2244     else if (key == setup.shortcut.toggle_pause)
2245       TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2246
2247     HandleTapeButtonKeys(key);
2248     HandleSoundButtonKeys(key);
2249   }
2250
2251   if (game_status == GAME_MODE_PLAYING && !network_playing)
2252   {
2253     int centered_player_nr_next = -999;
2254
2255     if (key == setup.shortcut.focus_player_all)
2256       centered_player_nr_next = -1;
2257     else
2258       for (i = 0; i < MAX_PLAYERS; i++)
2259         if (key == setup.shortcut.focus_player[i])
2260           centered_player_nr_next = i;
2261
2262     if (centered_player_nr_next != -999)
2263     {
2264       game.centered_player_nr_next = centered_player_nr_next;
2265       game.set_centered_player = TRUE;
2266
2267       if (tape.recording)
2268       {
2269         tape.centered_player_nr_next = game.centered_player_nr_next;
2270         tape.set_centered_player = TRUE;
2271       }
2272     }
2273   }
2274
2275   HandleKeysSpecial(key);
2276
2277   if (HandleGadgetsKeyInput(key))
2278     return;             // do not handle already processed keys again
2279
2280   switch (game_status)
2281   {
2282     case GAME_MODE_PSEUDO_TYPENAME:
2283       HandleTypeName(0, key);
2284       break;
2285
2286     case GAME_MODE_TITLE:
2287     case GAME_MODE_MAIN:
2288     case GAME_MODE_LEVELS:
2289     case GAME_MODE_LEVELNR:
2290     case GAME_MODE_SETUP:
2291     case GAME_MODE_INFO:
2292     case GAME_MODE_SCORES:
2293
2294       if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2295         break;
2296
2297       switch (key)
2298       {
2299         case KSYM_space:
2300         case KSYM_Return:
2301           if (game_status == GAME_MODE_TITLE)
2302             HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2303           else if (game_status == GAME_MODE_MAIN)
2304             HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2305           else if (game_status == GAME_MODE_LEVELS)
2306             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2307           else if (game_status == GAME_MODE_LEVELNR)
2308             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2309           else if (game_status == GAME_MODE_SETUP)
2310             HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2311           else if (game_status == GAME_MODE_INFO)
2312             HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2313           else if (game_status == GAME_MODE_SCORES)
2314             HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2315           break;
2316
2317         case KSYM_Escape:
2318           if (game_status != GAME_MODE_MAIN)
2319             FadeSkipNextFadeIn();
2320
2321           if (game_status == GAME_MODE_TITLE)
2322             HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2323           else if (game_status == GAME_MODE_LEVELS)
2324             HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2325           else if (game_status == GAME_MODE_LEVELNR)
2326             HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2327           else if (game_status == GAME_MODE_SETUP)
2328             HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2329           else if (game_status == GAME_MODE_INFO)
2330             HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2331           else if (game_status == GAME_MODE_SCORES)
2332             HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2333           break;
2334
2335         case KSYM_Page_Up:
2336           if (game_status == GAME_MODE_LEVELS)
2337             HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2338           else if (game_status == GAME_MODE_LEVELNR)
2339             HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2340           else if (game_status == GAME_MODE_SETUP)
2341             HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2342           else if (game_status == GAME_MODE_INFO)
2343             HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2344           else if (game_status == GAME_MODE_SCORES)
2345             HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2346           break;
2347
2348         case KSYM_Page_Down:
2349           if (game_status == GAME_MODE_LEVELS)
2350             HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2351           else if (game_status == GAME_MODE_LEVELNR)
2352             HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2353           else if (game_status == GAME_MODE_SETUP)
2354             HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2355           else if (game_status == GAME_MODE_INFO)
2356             HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2357           else if (game_status == GAME_MODE_SCORES)
2358             HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2359           break;
2360
2361         default:
2362           break;
2363       }
2364       break;
2365
2366     case GAME_MODE_EDITOR:
2367       if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2368         HandleLevelEditorKeyInput(key);
2369       break;
2370
2371     case GAME_MODE_PLAYING:
2372     {
2373       switch (key)
2374       {
2375         case KSYM_Escape:
2376           RequestQuitGame(setup.ask_on_escape);
2377           break;
2378
2379         default:
2380           break;
2381       }
2382       break;
2383     }
2384
2385     default:
2386       if (key == KSYM_Escape)
2387       {
2388         SetGameStatus(GAME_MODE_MAIN);
2389
2390         DrawMainMenu();
2391
2392         return;
2393       }
2394   }
2395 }
2396
2397 void HandleNoEvent(void)
2398 {
2399   HandleMouseCursor();
2400
2401   switch (game_status)
2402   {
2403     case GAME_MODE_PLAYING:
2404       HandleButtonOrFinger(-1, -1, -1);
2405       break;
2406   }
2407 }
2408
2409 void HandleEventActions(void)
2410 {
2411   // if (button_status && game_status != GAME_MODE_PLAYING)
2412   if (button_status && (game_status != GAME_MODE_PLAYING ||
2413                         tape.pausing ||
2414                         level.game_engine_type == GAME_ENGINE_TYPE_MM))
2415   {
2416     HandleButton(0, 0, button_status, -button_status);
2417   }
2418   else
2419   {
2420     HandleJoystick();
2421   }
2422
2423   if (network.enabled)
2424     HandleNetworking();
2425
2426   switch (game_status)
2427   {
2428     case GAME_MODE_MAIN:
2429       DrawPreviewLevelAnimation();
2430       break;
2431
2432     case GAME_MODE_EDITOR:
2433       HandleLevelEditorIdle();
2434       break;
2435
2436     default:
2437       break;
2438   }
2439 }
2440
2441 static void HandleTileCursor(int dx, int dy, int button)
2442 {
2443   if (!dx || !button)
2444     ClearPlayerMouseAction();
2445
2446   if (!dx && !dy)
2447     return;
2448
2449   if (button)
2450   {
2451     SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2452                          (dx < 0 ? MB_LEFTBUTTON :
2453                           dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2454   }
2455   else if (!tile_cursor.moving)
2456   {
2457     int old_xpos = tile_cursor.xpos;
2458     int old_ypos = tile_cursor.ypos;
2459     int new_xpos = old_xpos;
2460     int new_ypos = old_ypos;
2461
2462     if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2463       new_xpos = old_xpos + dx;
2464
2465     if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2466       new_ypos = old_ypos + dy;
2467
2468     SetTileCursorTargetXY(new_xpos, new_ypos);
2469   }
2470 }
2471
2472 static int HandleJoystickForAllPlayers(void)
2473 {
2474   int i;
2475   int result = 0;
2476   boolean no_joysticks_configured = TRUE;
2477   boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2478   static byte joy_action_last[MAX_PLAYERS];
2479
2480   for (i = 0; i < MAX_PLAYERS; i++)
2481     if (setup.input[i].use_joystick)
2482       no_joysticks_configured = FALSE;
2483
2484   // if no joysticks configured, map connected joysticks to players
2485   if (no_joysticks_configured)
2486     use_as_joystick_nr = TRUE;
2487
2488   for (i = 0; i < MAX_PLAYERS; i++)
2489   {
2490     byte joy_action = 0;
2491
2492     joy_action = JoystickExt(i, use_as_joystick_nr);
2493     result |= joy_action;
2494
2495     if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2496         joy_action != joy_action_last[i])
2497       stored_player[i].action = joy_action;
2498
2499     joy_action_last[i] = joy_action;
2500   }
2501
2502   return result;
2503 }
2504
2505 void HandleJoystick(void)
2506 {
2507   static unsigned int joytest_delay = 0;
2508   static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2509   static int joytest_last = 0;
2510   int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2511   int delay_value       = GADGET_FRAME_DELAY;
2512   int joystick  = HandleJoystickForAllPlayers();
2513   int keyboard  = key_joystick_mapping;
2514   int joy       = (joystick | keyboard);
2515   int joytest   = joystick;
2516   int left      = joy & JOY_LEFT;
2517   int right     = joy & JOY_RIGHT;
2518   int up        = joy & JOY_UP;
2519   int down      = joy & JOY_DOWN;
2520   int button    = joy & JOY_BUTTON;
2521   int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2522   int dx        = (left ? -1    : right ? 1     : 0);
2523   int dy        = (up   ? -1    : down  ? 1     : 0);
2524   boolean use_delay_value_first = (joytest != joytest_last);
2525
2526   if (HandleGlobalAnimClicks(-1, -1, newbutton, FALSE))
2527   {
2528     // do not handle this button event anymore
2529     return;
2530   }
2531
2532   if (newbutton && (game_status == GAME_MODE_PSEUDO_TYPENAME ||
2533                     anyTextGadgetActive()))
2534   {
2535     // leave name input in main menu or text input gadget
2536     HandleKey(KSYM_Escape, KEY_PRESSED);
2537     HandleKey(KSYM_Escape, KEY_RELEASED);
2538
2539     return;
2540   }
2541
2542   if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2543   {
2544     if (game_status == GAME_MODE_PLAYING)
2545     {
2546       // when playing MM style levels, also use delay for keyboard events
2547       joytest |= keyboard;
2548
2549       // only use first delay value for new events, but not for changed events
2550       use_delay_value_first = (!joytest != !joytest_last);
2551
2552       // only use delay after the initial keyboard event
2553       delay_value = 0;
2554     }
2555
2556     // for any joystick or keyboard event, enable playfield tile cursor
2557     if (dx || dy || button)
2558       SetTileCursorEnabled(TRUE);
2559   }
2560
2561   if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2562   {
2563     // delay joystick/keyboard actions if axes/keys continually pressed
2564     newbutton = dx = dy = 0;
2565   }
2566   else
2567   {
2568     // first start with longer delay, then continue with shorter delay
2569     joytest_delay_value =
2570       (use_delay_value_first ? delay_value_first : delay_value);
2571   }
2572
2573   joytest_last = joytest;
2574
2575   switch (game_status)
2576   {
2577     case GAME_MODE_TITLE:
2578     case GAME_MODE_MAIN:
2579     case GAME_MODE_LEVELS:
2580     case GAME_MODE_LEVELNR:
2581     case GAME_MODE_SETUP:
2582     case GAME_MODE_INFO:
2583     case GAME_MODE_SCORES:
2584     {
2585       if (anyTextGadgetActive())
2586         break;
2587
2588       if (game_status == GAME_MODE_TITLE)
2589         HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2590       else if (game_status == GAME_MODE_MAIN)
2591         HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2592       else if (game_status == GAME_MODE_LEVELS)
2593         HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2594       else if (game_status == GAME_MODE_LEVELNR)
2595         HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2596       else if (game_status == GAME_MODE_SETUP)
2597         HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2598       else if (game_status == GAME_MODE_INFO)
2599         HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2600       else if (game_status == GAME_MODE_SCORES)
2601         HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2602
2603       break;
2604     }
2605
2606     case GAME_MODE_PLAYING:
2607 #if 0
2608       // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2609       if (tape.playing || keyboard)
2610         newbutton = ((joy & JOY_BUTTON) != 0);
2611 #endif
2612
2613       if (newbutton && game.all_players_gone)
2614       {
2615         GameEnd();
2616
2617         return;
2618       }
2619
2620       if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2621       {
2622         if (joystick & JOY_ACTION)
2623           TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2624       }
2625       else if (tape.recording && tape.pausing && !tape.use_mouse)
2626       {
2627         if (joystick & JOY_ACTION)
2628           TapeTogglePause(TAPE_TOGGLE_MANUAL);
2629       }
2630
2631       if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2632         HandleTileCursor(dx, dy, button);
2633
2634       break;
2635
2636     default:
2637       break;
2638   }
2639 }
2640
2641 void HandleSpecialGameControllerButtons(Event *event)
2642 {
2643   int key_status;
2644   Key key;
2645
2646   switch (event->type)
2647   {
2648     case SDL_CONTROLLERBUTTONDOWN:
2649       key_status = KEY_PRESSED;
2650       break;
2651
2652     case SDL_CONTROLLERBUTTONUP:
2653       key_status = KEY_RELEASED;
2654       break;
2655
2656     default:
2657       return;
2658   }
2659
2660   switch (event->cbutton.button)
2661   {
2662     case SDL_CONTROLLER_BUTTON_START:
2663       key = KSYM_space;
2664       break;
2665
2666     case SDL_CONTROLLER_BUTTON_BACK:
2667       key = KSYM_Escape;
2668       break;
2669
2670     default:
2671       return;
2672   }
2673
2674   HandleKey(key, key_status);
2675 }
2676
2677 void HandleSpecialGameControllerKeys(Key key, int key_status)
2678 {
2679 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2680   int button = SDL_CONTROLLER_BUTTON_INVALID;
2681
2682   // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2683   if (key == KSYM_Rewind)
2684     button = SDL_CONTROLLER_BUTTON_A;
2685   else if (key == KSYM_FastForward || key == KSYM_Menu)
2686     button = SDL_CONTROLLER_BUTTON_B;
2687
2688   if (button != SDL_CONTROLLER_BUTTON_INVALID)
2689   {
2690     Event event;
2691
2692     event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2693                   SDL_CONTROLLERBUTTONUP);
2694
2695     event.cbutton.which = 0;    // first joystick (Amazon Fire TV remote)
2696     event.cbutton.button = button;
2697     event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2698                            SDL_RELEASED);
2699
2700     HandleJoystickEvent(&event);
2701   }
2702 #endif
2703 }
2704
2705 boolean DoKeysymAction(int keysym)
2706 {
2707   if (keysym < 0)
2708   {
2709     Key key = (Key)(-keysym);
2710
2711     HandleKey(key, KEY_PRESSED);
2712     HandleKey(key, KEY_RELEASED);
2713
2714     return TRUE;
2715   }
2716
2717   return FALSE;
2718 }