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