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