1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
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)
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;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 /* non-motion events are directly passed to event handler functions */
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 /* do no reset mouse cursor before all pending events have been processed */
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 /* skip mouse motion events without pressed button outside level editor */
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 /* to prevent delay problems, skip mouse motion events if the very next
110 event is also a mouse motion event (and therefore effectively only
111 handling the last of a row of mouse motion events in the event queue) */
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 /* nothing to do if the current event is not a mouse motion event */
116 if (event->type != EVENT_MOTIONNOTIFY)
119 /* only skip motion events with pressed button outside the game */
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 /* if next event is also a mouse motion event, skip the current one */
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
309 SetMouseCursor(CURSOR_PLAYFIELD);
312 else if (gfx.cursor_mode != CURSOR_DEFAULT)
314 SetMouseCursor(CURSOR_DEFAULT);
317 /* this is set after all pending events have been processed */
318 cursor_mode_last = gfx.cursor_mode;
330 /* execute event related actions after pending events have been processed */
331 HandleEventActions();
333 /* don't use all CPU time when idle; the main loop while playing
334 has its own synchronization and is CPU friendly, too */
336 if (game_status == GAME_MODE_PLAYING)
339 /* always copy backbuffer to visible screen for every video frame */
342 /* reset video frame delay to default (may change again while playing) */
343 SetVideoFrameDelay(MenuFrameDelay);
345 if (game_status == GAME_MODE_QUIT)
350 void ClearEventQueue()
354 while (NextValidEvent(&event))
358 case EVENT_BUTTONRELEASE:
359 button_status = MB_RELEASED;
362 case EVENT_KEYRELEASE:
366 #if defined(TARGET_SDL2)
367 case SDL_CONTROLLERBUTTONUP:
368 HandleJoystickEvent(&event);
374 HandleOtherEvents(&event);
380 void ClearPlayerMouseAction()
382 local_player->mouse_action.lx = 0;
383 local_player->mouse_action.ly = 0;
384 local_player->mouse_action.button = 0;
387 void ClearPlayerAction()
391 /* simulate key release events for still pressed keys */
392 key_joystick_mapping = 0;
393 for (i = 0; i < MAX_PLAYERS; i++)
394 stored_player[i].action = 0;
396 ClearJoystickState();
397 ClearPlayerMouseAction();
400 void SetPlayerMouseAction(int mx, int my, int button)
402 int lx = getLevelFromScreenX(mx);
403 int ly = getLevelFromScreenY(my);
404 int new_button = (!local_player->mouse_action.button && button);
406 if (local_player->mouse_action.button_hint)
407 button = local_player->mouse_action.button_hint;
409 ClearPlayerMouseAction();
411 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
414 local_player->mouse_action.lx = lx;
415 local_player->mouse_action.ly = ly;
416 local_player->mouse_action.button = button;
418 if (tape.recording && tape.pausing && tape.use_mouse)
420 /* un-pause a paused game only if mouse button was newly pressed down */
422 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
425 SetTileCursorXY(lx, ly);
428 void SleepWhileUnmapped()
430 boolean window_unmapped = TRUE;
432 KeyboardAutoRepeatOn();
434 while (window_unmapped)
438 if (!WaitValidEvent(&event))
443 case EVENT_BUTTONRELEASE:
444 button_status = MB_RELEASED;
447 case EVENT_KEYRELEASE:
448 key_joystick_mapping = 0;
451 #if defined(TARGET_SDL2)
452 case SDL_CONTROLLERBUTTONUP:
453 HandleJoystickEvent(&event);
454 key_joystick_mapping = 0;
458 case EVENT_MAPNOTIFY:
459 window_unmapped = FALSE;
462 case EVENT_UNMAPNOTIFY:
463 /* this is only to surely prevent the 'should not happen' case
464 * of recursively looping between 'SleepWhileUnmapped()' and
465 * 'HandleOtherEvents()' which usually calls this funtion.
470 HandleOtherEvents(&event);
475 if (game_status == GAME_MODE_PLAYING)
476 KeyboardAutoRepeatOffUnlessAutoplay();
479 void HandleExposeEvent(ExposeEvent *event)
483 void HandleButtonEvent(ButtonEvent *event)
485 #if DEBUG_EVENTS_BUTTON
486 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
488 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
492 // for any mouse button event, disable playfield tile cursor
493 SetTileCursorEnabled(FALSE);
495 #if defined(HAS_SCREEN_KEYBOARD)
496 if (video.shifted_up)
497 event->y += video.shifted_up_pos;
500 motion_status = FALSE;
502 if (event->type == EVENT_BUTTONPRESS)
503 button_status = event->button;
505 button_status = MB_RELEASED;
507 HandleButton(event->x, event->y, button_status, event->button);
510 void HandleMotionEvent(MotionEvent *event)
512 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
515 motion_status = TRUE;
517 #if DEBUG_EVENTS_MOTION
518 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
519 button_status, event->x, event->y);
522 HandleButton(event->x, event->y, button_status, button_status);
525 #if defined(TARGET_SDL2)
527 void HandleWheelEvent(WheelEvent *event)
531 #if DEBUG_EVENTS_WHEEL
533 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
534 event->which, event->x, event->y);
536 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
537 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
538 event->which, event->x, event->y,
539 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
540 "SDL_MOUSEWHEEL_FLIPPED"));
544 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
545 event->x > 0 ? MB_WHEEL_RIGHT :
546 event->y < 0 ? MB_WHEEL_DOWN :
547 event->y > 0 ? MB_WHEEL_UP : 0);
549 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
550 // accelerated mouse wheel available on Mac and Windows
551 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
553 // no accelerated mouse wheel available on Unix/Linux
554 wheel_steps = DEFAULT_WHEEL_STEPS;
557 motion_status = FALSE;
559 button_status = button_nr;
560 HandleButton(0, 0, button_status, -button_nr);
562 button_status = MB_RELEASED;
563 HandleButton(0, 0, button_status, -button_nr);
566 void HandleWindowEvent(WindowEvent *event)
568 #if DEBUG_EVENTS_WINDOW
569 int subtype = event->event;
572 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
573 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
574 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
575 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
576 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
577 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
578 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
579 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
580 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
581 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
582 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
583 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
584 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
585 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
588 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
589 event_name, event->data1, event->data2);
593 // (not needed, as the screen gets redrawn every 20 ms anyway)
594 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
595 event->event == SDL_WINDOWEVENT_RESIZED ||
596 event->event == SDL_WINDOWEVENT_EXPOSED)
600 if (event->event == SDL_WINDOWEVENT_RESIZED)
602 if (!video.fullscreen_enabled)
604 int new_window_width = event->data1;
605 int new_window_height = event->data2;
607 // if window size has changed after resizing, calculate new scaling factor
608 if (new_window_width != video.window_width ||
609 new_window_height != video.window_height)
611 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
612 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
614 // (extreme window scaling allowed, but cannot be saved permanently)
615 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
616 setup.window_scaling_percent =
617 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
618 MAX_WINDOW_SCALING_PERCENT);
620 video.window_width = new_window_width;
621 video.window_height = new_window_height;
623 if (game_status == GAME_MODE_SETUP)
624 RedrawSetupScreenAfterFullscreenToggle();
629 #if defined(PLATFORM_ANDROID)
632 int new_display_width = event->data1;
633 int new_display_height = event->data2;
635 // if fullscreen display size has changed, device has been rotated
636 if (new_display_width != video.display_width ||
637 new_display_height != video.display_height)
639 video.display_width = new_display_width;
640 video.display_height = new_display_height;
642 SDLSetScreenProperties();
649 #define NUM_TOUCH_FINGERS 3
654 SDL_FingerID finger_id;
657 } touch_info[NUM_TOUCH_FINGERS];
659 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
661 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
662 float event_x = (event->x);
663 float event_y = (event->y - ypos) / (1 - ypos);
664 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
665 event_y > 2.0 / 3.0 && event_y < 1 ?
666 setup.input[0].key.snap :
667 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
668 event_y > 2.0 / 3.0 && event_y < 1 ?
669 setup.input[0].key.drop :
670 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
671 event_y > 0 && event_y < 1.0 / 3.0 ?
672 setup.input[0].key.up :
673 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
674 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
675 setup.input[0].key.left :
676 event_x > 8.0 / 9.0 && event_x < 1 &&
677 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
678 setup.input[0].key.right :
679 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
680 event_y > 2.0 / 3.0 && event_y < 1 ?
681 setup.input[0].key.down :
683 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
685 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
689 // for any touch input event, enable overlay buttons (if activated)
690 SetOverlayEnabled(TRUE);
692 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
693 getKeyNameFromKey(key), key_status_name, event->fingerId);
695 // check if we already know this touch event's finger id
696 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
698 if (touch_info[i].touched &&
699 touch_info[i].finger_id == event->fingerId)
701 // Error(ERR_DEBUG, "MARK 1: %d", i);
707 if (i >= NUM_TOUCH_FINGERS)
709 if (key_status == KEY_PRESSED)
711 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
713 // unknown finger id -- get new, empty slot, if available
714 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
716 if (touch_info[i].counter < oldest_counter)
719 oldest_counter = touch_info[i].counter;
721 // Error(ERR_DEBUG, "MARK 2: %d", i);
724 if (!touch_info[i].touched)
726 // Error(ERR_DEBUG, "MARK 3: %d", i);
732 if (i >= NUM_TOUCH_FINGERS)
734 // all slots allocated -- use oldest slot
737 // Error(ERR_DEBUG, "MARK 4: %d", i);
742 // release of previously unknown key (should not happen)
744 if (key != KSYM_UNDEFINED)
746 HandleKey(key, KEY_RELEASED);
748 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
749 getKeyNameFromKey(key), "KEY_RELEASED", i);
754 if (i < NUM_TOUCH_FINGERS)
756 if (key_status == KEY_PRESSED)
758 if (touch_info[i].key != key)
760 if (touch_info[i].key != KSYM_UNDEFINED)
762 HandleKey(touch_info[i].key, KEY_RELEASED);
764 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
765 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
768 if (key != KSYM_UNDEFINED)
770 HandleKey(key, KEY_PRESSED);
772 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
773 getKeyNameFromKey(key), "KEY_PRESSED", i);
777 touch_info[i].touched = TRUE;
778 touch_info[i].finger_id = event->fingerId;
779 touch_info[i].counter = Counter();
780 touch_info[i].key = key;
784 if (touch_info[i].key != KSYM_UNDEFINED)
786 HandleKey(touch_info[i].key, KEY_RELEASED);
788 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
789 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
792 touch_info[i].touched = FALSE;
793 touch_info[i].finger_id = 0;
794 touch_info[i].counter = 0;
795 touch_info[i].key = 0;
800 void HandleFingerEvent_WipeGestures(FingerEvent *event)
802 static Key motion_key_x = KSYM_UNDEFINED;
803 static Key motion_key_y = KSYM_UNDEFINED;
804 static Key button_key = KSYM_UNDEFINED;
805 static float motion_x1, motion_y1;
806 static float button_x1, button_y1;
807 static SDL_FingerID motion_id = -1;
808 static SDL_FingerID button_id = -1;
809 int move_trigger_distance_percent = setup.touch.move_distance;
810 int drop_trigger_distance_percent = setup.touch.drop_distance;
811 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
812 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
813 float event_x = event->x;
814 float event_y = event->y;
816 if (event->type == EVENT_FINGERPRESS)
818 if (event_x > 1.0 / 3.0)
822 motion_id = event->fingerId;
827 motion_key_x = KSYM_UNDEFINED;
828 motion_key_y = KSYM_UNDEFINED;
830 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
836 button_id = event->fingerId;
841 button_key = setup.input[0].key.snap;
843 HandleKey(button_key, KEY_PRESSED);
845 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
848 else if (event->type == EVENT_FINGERRELEASE)
850 if (event->fingerId == motion_id)
854 if (motion_key_x != KSYM_UNDEFINED)
855 HandleKey(motion_key_x, KEY_RELEASED);
856 if (motion_key_y != KSYM_UNDEFINED)
857 HandleKey(motion_key_y, KEY_RELEASED);
859 motion_key_x = KSYM_UNDEFINED;
860 motion_key_y = KSYM_UNDEFINED;
862 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
864 else if (event->fingerId == button_id)
868 if (button_key != KSYM_UNDEFINED)
869 HandleKey(button_key, KEY_RELEASED);
871 button_key = KSYM_UNDEFINED;
873 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
876 else if (event->type == EVENT_FINGERMOTION)
878 if (event->fingerId == motion_id)
880 float distance_x = ABS(event_x - motion_x1);
881 float distance_y = ABS(event_y - motion_y1);
882 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
883 event_x > motion_x1 ? setup.input[0].key.right :
885 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
886 event_y > motion_y1 ? setup.input[0].key.down :
889 if (distance_x < move_trigger_distance / 2 ||
890 distance_x < distance_y)
891 new_motion_key_x = KSYM_UNDEFINED;
893 if (distance_y < move_trigger_distance / 2 ||
894 distance_y < distance_x)
895 new_motion_key_y = KSYM_UNDEFINED;
897 if (distance_x > move_trigger_distance ||
898 distance_y > move_trigger_distance)
900 if (new_motion_key_x != motion_key_x)
902 if (motion_key_x != KSYM_UNDEFINED)
903 HandleKey(motion_key_x, KEY_RELEASED);
904 if (new_motion_key_x != KSYM_UNDEFINED)
905 HandleKey(new_motion_key_x, KEY_PRESSED);
908 if (new_motion_key_y != motion_key_y)
910 if (motion_key_y != KSYM_UNDEFINED)
911 HandleKey(motion_key_y, KEY_RELEASED);
912 if (new_motion_key_y != KSYM_UNDEFINED)
913 HandleKey(new_motion_key_y, KEY_PRESSED);
919 motion_key_x = new_motion_key_x;
920 motion_key_y = new_motion_key_y;
922 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
925 else if (event->fingerId == button_id)
927 float distance_x = ABS(event_x - button_x1);
928 float distance_y = ABS(event_y - button_y1);
930 if (distance_x < drop_trigger_distance / 2 &&
931 distance_y > drop_trigger_distance)
933 if (button_key == setup.input[0].key.snap)
934 HandleKey(button_key, KEY_RELEASED);
939 button_key = setup.input[0].key.drop;
941 HandleKey(button_key, KEY_PRESSED);
943 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
949 void HandleFingerEvent(FingerEvent *event)
951 #if DEBUG_EVENTS_FINGER
952 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
953 event->type == EVENT_FINGERPRESS ? "pressed" :
954 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
958 event->dx, event->dy,
962 if (game_status != GAME_MODE_PLAYING)
965 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
967 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
968 local_player->mouse_action.button_hint =
969 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
970 event->x < 0.5 ? MB_LEFTBUTTON :
971 event->x > 0.5 ? MB_RIGHTBUTTON :
977 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
978 HandleFingerEvent_VirtualButtons(event);
979 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
980 HandleFingerEvent_WipeGestures(event);
985 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
987 static int old_mx = 0, old_my = 0;
988 static int last_button = MB_LEFTBUTTON;
989 static boolean touched = FALSE;
990 static boolean tapped = FALSE;
992 // screen tile was tapped (but finger not touching the screen anymore)
993 // (this point will also be reached without receiving a touch event)
994 if (tapped && !touched)
996 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1001 // stop here if this function was not triggered by a touch event
1005 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1007 // finger started touching the screen
1017 ClearPlayerMouseAction();
1019 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1022 else if (button == MB_RELEASED && touched)
1024 // finger stopped touching the screen
1029 SetPlayerMouseAction(old_mx, old_my, last_button);
1031 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1033 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1038 // finger moved while touching the screen
1040 int old_x = getLevelFromScreenX(old_mx);
1041 int old_y = getLevelFromScreenY(old_my);
1042 int new_x = getLevelFromScreenX(mx);
1043 int new_y = getLevelFromScreenY(my);
1045 if (new_x != old_x || new_y != old_y)
1050 // finger moved left or right from (horizontal) starting position
1052 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1054 SetPlayerMouseAction(old_mx, old_my, button_nr);
1056 last_button = button_nr;
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1062 // finger stays at or returned to (horizontal) starting position
1064 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1066 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1071 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1073 static int old_mx = 0, old_my = 0;
1074 static int last_button = MB_LEFTBUTTON;
1075 static boolean touched = FALSE;
1076 static boolean tapped = FALSE;
1078 // screen tile was tapped (but finger not touching the screen anymore)
1079 // (this point will also be reached without receiving a touch event)
1080 if (tapped && !touched)
1082 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1087 // stop here if this function was not triggered by a touch event
1091 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1093 // finger started touching the screen
1103 ClearPlayerMouseAction();
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1108 else if (button == MB_RELEASED && touched)
1110 // finger stopped touching the screen
1115 SetPlayerMouseAction(old_mx, old_my, last_button);
1117 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1119 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1124 // finger moved while touching the screen
1126 int old_x = getLevelFromScreenX(old_mx);
1127 int old_y = getLevelFromScreenY(old_my);
1128 int new_x = getLevelFromScreenX(mx);
1129 int new_y = getLevelFromScreenY(my);
1131 if (new_x != old_x || new_y != old_y)
1133 // finger moved away from starting position
1135 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1137 // quickly alternate between clicking and releasing for maximum speed
1138 if (FrameCounter % 2 == 0)
1139 button_nr = MB_RELEASED;
1141 SetPlayerMouseAction(old_mx, old_my, button_nr);
1144 last_button = button_nr;
1148 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1152 // finger stays at or returned to starting position
1154 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1156 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1161 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1163 static int old_mx = 0, old_my = 0;
1164 static Key motion_key_x = KSYM_UNDEFINED;
1165 static Key motion_key_y = KSYM_UNDEFINED;
1166 static boolean touched = FALSE;
1167 static boolean started_on_player = FALSE;
1168 static boolean player_is_dropping = FALSE;
1169 static int player_drop_count = 0;
1170 static int last_player_x = -1;
1171 static int last_player_y = -1;
1173 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1182 started_on_player = FALSE;
1183 player_is_dropping = FALSE;
1184 player_drop_count = 0;
1188 motion_key_x = KSYM_UNDEFINED;
1189 motion_key_y = KSYM_UNDEFINED;
1191 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1194 else if (button == MB_RELEASED && touched)
1201 if (motion_key_x != KSYM_UNDEFINED)
1202 HandleKey(motion_key_x, KEY_RELEASED);
1203 if (motion_key_y != KSYM_UNDEFINED)
1204 HandleKey(motion_key_y, KEY_RELEASED);
1206 if (started_on_player)
1208 if (player_is_dropping)
1210 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1212 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1216 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1218 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1222 motion_key_x = KSYM_UNDEFINED;
1223 motion_key_y = KSYM_UNDEFINED;
1225 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1230 int src_x = local_player->jx;
1231 int src_y = local_player->jy;
1232 int dst_x = getLevelFromScreenX(old_mx);
1233 int dst_y = getLevelFromScreenY(old_my);
1234 int dx = dst_x - src_x;
1235 int dy = dst_y - src_y;
1236 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1237 dx > 0 ? setup.input[0].key.right :
1239 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1240 dy > 0 ? setup.input[0].key.down :
1243 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1244 (last_player_x != local_player->jx ||
1245 last_player_y != local_player->jy))
1247 // in case of asymmetric diagonal movement, use "preferred" direction
1249 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1251 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1252 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1254 local_player->last_move_dir = last_move_dir;
1256 // (required to prevent accidentally forcing direction for next movement)
1257 last_player_x = local_player->jx;
1258 last_player_y = local_player->jy;
1261 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1263 started_on_player = TRUE;
1264 player_drop_count = getPlayerInventorySize(0);
1265 player_is_dropping = (player_drop_count > 0);
1267 if (player_is_dropping)
1269 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1271 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1275 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1277 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1280 else if (dx != 0 || dy != 0)
1282 if (player_is_dropping &&
1283 player_drop_count == getPlayerInventorySize(0))
1285 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1287 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1288 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1290 player_is_dropping = FALSE;
1294 if (new_motion_key_x != motion_key_x)
1296 Error(ERR_DEBUG, "---------- %s %s ----------",
1297 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1298 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1300 if (motion_key_x != KSYM_UNDEFINED)
1301 HandleKey(motion_key_x, KEY_RELEASED);
1302 if (new_motion_key_x != KSYM_UNDEFINED)
1303 HandleKey(new_motion_key_x, KEY_PRESSED);
1306 if (new_motion_key_y != motion_key_y)
1308 Error(ERR_DEBUG, "---------- %s %s ----------",
1309 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1310 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1312 if (motion_key_y != KSYM_UNDEFINED)
1313 HandleKey(motion_key_y, KEY_RELEASED);
1314 if (new_motion_key_y != KSYM_UNDEFINED)
1315 HandleKey(new_motion_key_y, KEY_PRESSED);
1318 motion_key_x = new_motion_key_x;
1319 motion_key_y = new_motion_key_y;
1323 static void HandleButtonOrFinger(int mx, int my, int button)
1325 if (game_status != GAME_MODE_PLAYING)
1328 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1330 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1331 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1332 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1333 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1337 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1338 HandleButtonOrFinger_FollowFinger(mx, my, button);
1342 #if defined(TARGET_SDL2)
1344 static boolean checkTextInputKeyModState()
1346 // when playing, only handle raw key events and ignore text input
1347 if (game_status == GAME_MODE_PLAYING)
1350 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1353 void HandleTextEvent(TextEvent *event)
1355 char *text = event->text;
1356 Key key = getKeyFromKeyName(text);
1358 #if DEBUG_EVENTS_TEXT
1359 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1362 text[0], (int)(text[0]),
1364 getKeyNameFromKey(key),
1368 #if !defined(HAS_SCREEN_KEYBOARD)
1369 // non-mobile devices: only handle key input with modifier keys pressed here
1370 // (every other key input is handled directly as physical key input event)
1371 if (!checkTextInputKeyModState())
1375 // process text input as "classic" (with uppercase etc.) key input event
1376 HandleKey(key, KEY_PRESSED);
1377 HandleKey(key, KEY_RELEASED);
1380 void HandlePauseResumeEvent(PauseResumeEvent *event)
1382 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1386 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1394 void HandleKeyEvent(KeyEvent *event)
1396 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1397 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1398 Key key = GetEventKey(event, with_modifiers);
1399 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1401 #if DEBUG_EVENTS_KEY
1402 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1403 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1404 event->keysym.scancode,
1409 getKeyNameFromKey(key));
1412 #if defined(PLATFORM_ANDROID)
1413 if (key == KSYM_Back)
1415 // always map the "back" button to the "escape" key on Android devices
1420 // for any key event other than "back" button, disable overlay buttons
1421 SetOverlayEnabled(FALSE);
1425 HandleKeyModState(keymod, key_status);
1427 #if defined(TARGET_SDL2)
1428 // only handle raw key input without text modifier keys pressed
1429 if (!checkTextInputKeyModState())
1430 HandleKey(key, key_status);
1432 HandleKey(key, key_status);
1436 void HandleFocusEvent(FocusChangeEvent *event)
1438 static int old_joystick_status = -1;
1440 if (event->type == EVENT_FOCUSOUT)
1442 KeyboardAutoRepeatOn();
1443 old_joystick_status = joystick.status;
1444 joystick.status = JOYSTICK_NOT_AVAILABLE;
1446 ClearPlayerAction();
1448 else if (event->type == EVENT_FOCUSIN)
1450 /* When there are two Rocks'n'Diamonds windows which overlap and
1451 the player moves the pointer from one game window to the other,
1452 a 'FocusOut' event is generated for the window the pointer is
1453 leaving and a 'FocusIn' event is generated for the window the
1454 pointer is entering. In some cases, it can happen that the
1455 'FocusIn' event is handled by the one game process before the
1456 'FocusOut' event by the other game process. In this case the
1457 X11 environment would end up with activated keyboard auto repeat,
1458 because unfortunately this is a global setting and not (which
1459 would be far better) set for each X11 window individually.
1460 The effect would be keyboard auto repeat while playing the game
1461 (game_status == GAME_MODE_PLAYING), which is not desired.
1462 To avoid this special case, we just wait 1/10 second before
1463 processing the 'FocusIn' event.
1466 if (game_status == GAME_MODE_PLAYING)
1469 KeyboardAutoRepeatOffUnlessAutoplay();
1472 if (old_joystick_status != -1)
1473 joystick.status = old_joystick_status;
1477 void HandleClientMessageEvent(ClientMessageEvent *event)
1479 if (CheckCloseWindowEvent(event))
1483 void HandleWindowManagerEvent(Event *event)
1485 #if defined(TARGET_SDL)
1486 SDLHandleWindowManagerEvent(event);
1490 void HandleButton(int mx, int my, int button, int button_nr)
1492 static int old_mx = 0, old_my = 0;
1493 boolean button_hold = FALSE;
1494 boolean handle_gadgets = TRUE;
1500 button_nr = -button_nr;
1509 #if defined(PLATFORM_ANDROID)
1510 // when playing, only handle gadgets when using "follow finger" controls
1511 // or when using touch controls in combination with the MM game engine
1513 (game_status != GAME_MODE_PLAYING ||
1514 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1515 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1518 if (handle_gadgets && HandleGadgets(mx, my, button))
1520 /* do not handle this button event anymore */
1521 mx = my = -32; /* force mouse event to be outside screen tiles */
1524 if (HandleGlobalAnimClicks(mx, my, button))
1526 /* do not handle this button event anymore */
1527 return; /* force mouse event not to be handled at all */
1530 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1533 /* do not use scroll wheel button events for anything other than gadgets */
1534 if (IS_WHEEL_BUTTON(button_nr))
1537 switch (game_status)
1539 case GAME_MODE_TITLE:
1540 HandleTitleScreen(mx, my, 0, 0, button);
1543 case GAME_MODE_MAIN:
1544 HandleMainMenu(mx, my, 0, 0, button);
1547 case GAME_MODE_PSEUDO_TYPENAME:
1548 HandleTypeName(0, KSYM_Return);
1551 case GAME_MODE_LEVELS:
1552 HandleChooseLevelSet(mx, my, 0, 0, button);
1555 case GAME_MODE_LEVELNR:
1556 HandleChooseLevelNr(mx, my, 0, 0, button);
1559 case GAME_MODE_SCORES:
1560 HandleHallOfFame(0, 0, 0, 0, button);
1563 case GAME_MODE_EDITOR:
1564 HandleLevelEditorIdle();
1567 case GAME_MODE_INFO:
1568 HandleInfoScreen(mx, my, 0, 0, button);
1571 case GAME_MODE_SETUP:
1572 HandleSetupScreen(mx, my, 0, 0, button);
1575 case GAME_MODE_PLAYING:
1576 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1577 HandleButtonOrFinger(mx, my, button);
1579 SetPlayerMouseAction(mx, my, button);
1582 if (button == MB_PRESSED && !motion_status && !button_hold &&
1583 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1584 DumpTileFromScreen(mx, my);
1594 static boolean is_string_suffix(char *string, char *suffix)
1596 int string_len = strlen(string);
1597 int suffix_len = strlen(suffix);
1599 if (suffix_len > string_len)
1602 return (strEqual(&string[string_len - suffix_len], suffix));
1605 #define MAX_CHEAT_INPUT_LEN 32
1607 static void HandleKeysSpecial(Key key)
1609 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1610 char letter = getCharFromKey(key);
1611 int cheat_input_len = strlen(cheat_input);
1617 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1619 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1620 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1622 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1625 cheat_input[cheat_input_len++] = letter;
1626 cheat_input[cheat_input_len] = '\0';
1628 #if DEBUG_EVENTS_KEY
1629 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1632 if (game_status == GAME_MODE_MAIN)
1634 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1635 is_string_suffix(cheat_input, ":ist"))
1637 InsertSolutionTape();
1639 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1640 is_string_suffix(cheat_input, ":rg"))
1642 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1645 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1646 is_string_suffix(cheat_input, ":rs"))
1648 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1651 else if (is_string_suffix(cheat_input, ":reload-music") ||
1652 is_string_suffix(cheat_input, ":rm"))
1654 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1657 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1658 is_string_suffix(cheat_input, ":ra"))
1660 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1661 1 << ARTWORK_TYPE_SOUNDS |
1662 1 << ARTWORK_TYPE_MUSIC);
1665 else if (is_string_suffix(cheat_input, ":dump-level") ||
1666 is_string_suffix(cheat_input, ":dl"))
1670 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1671 is_string_suffix(cheat_input, ":dt"))
1675 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1676 is_string_suffix(cheat_input, ":ft"))
1678 /* fix single-player tapes that contain player input for more than one
1679 player (due to a bug in 3.3.1.2 and earlier versions), which results
1680 in playing levels with more than one player in multi-player mode,
1681 even though the tape was originally recorded in single-player mode */
1683 /* remove player input actions for all players but the first one */
1684 for (i = 1; i < MAX_PLAYERS; i++)
1685 tape.player_participates[i] = FALSE;
1687 tape.changed = TRUE;
1689 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1690 is_string_suffix(cheat_input, ":snl"))
1692 SaveNativeLevel(&level);
1694 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1695 is_string_suffix(cheat_input, ":fps"))
1697 global.show_frames_per_second = !global.show_frames_per_second;
1700 else if (game_status == GAME_MODE_PLAYING)
1703 if (is_string_suffix(cheat_input, ".q"))
1704 DEBUG_SetMaximumDynamite();
1707 else if (game_status == GAME_MODE_EDITOR)
1709 if (is_string_suffix(cheat_input, ":dump-brush") ||
1710 is_string_suffix(cheat_input, ":DB"))
1714 else if (is_string_suffix(cheat_input, ":DDB"))
1721 void HandleKeysDebug(Key key)
1726 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1728 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1730 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1732 if (key == setup.debug.frame_delay_key[i] &&
1733 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1735 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1736 setup.debug.frame_delay[i] : setup.game_frame_delay);
1738 if (!setup.debug.frame_delay_game_only)
1739 MenuFrameDelay = GameFrameDelay;
1741 SetVideoFrameDelay(GameFrameDelay);
1743 if (GameFrameDelay > ONE_SECOND_DELAY)
1744 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1745 else if (GameFrameDelay != 0)
1746 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1747 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1748 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1750 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1757 if (game_status == GAME_MODE_PLAYING)
1761 options.debug = !options.debug;
1763 Error(ERR_DEBUG, "debug mode %s",
1764 (options.debug ? "enabled" : "disabled"));
1766 else if (key == KSYM_v)
1768 Error(ERR_DEBUG, "currently using game engine version %d",
1769 game.engine_version);
1775 void HandleKey(Key key, int key_status)
1777 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1778 static boolean ignore_repeated_key = FALSE;
1779 static struct SetupKeyboardInfo ski;
1780 static struct SetupShortcutInfo ssi;
1789 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1790 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1791 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1792 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1793 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1794 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1799 #if defined(TARGET_SDL2)
1800 /* map special keys (media keys / remote control buttons) to default keys */
1801 if (key == KSYM_PlayPause)
1803 else if (key == KSYM_Select)
1807 HandleSpecialGameControllerKeys(key, key_status);
1809 if (game_status == GAME_MODE_PLAYING)
1811 /* only needed for single-step tape recording mode */
1812 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1815 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1817 byte key_action = 0;
1819 if (setup.input[pnr].use_joystick)
1822 ski = setup.input[pnr].key;
1824 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1825 if (key == *key_info[i].key_custom)
1826 key_action |= key_info[i].action;
1828 /* use combined snap+direction keys for the first player only */
1831 ssi = setup.shortcut;
1833 for (i = 0; i < NUM_DIRECTIONS; i++)
1834 if (key == *key_info[i].key_snap)
1835 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1838 if (key_status == KEY_PRESSED)
1839 stored_player[pnr].action |= key_action;
1841 stored_player[pnr].action &= ~key_action;
1843 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1845 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1847 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1849 /* if snap key already pressed, keep pause mode when releasing */
1850 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1851 has_snapped[pnr] = TRUE;
1853 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1855 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1857 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1858 getRedDiskReleaseFlag_SP() == 0)
1860 /* add a single inactive frame before dropping starts */
1861 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1862 stored_player[pnr].force_dropping = TRUE;
1865 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1867 /* if snap key was pressed without direction, leave pause mode */
1868 if (!has_snapped[pnr])
1869 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1871 has_snapped[pnr] = FALSE;
1874 else if (tape.recording && tape.pausing && !tape.use_mouse)
1876 /* prevent key release events from un-pausing a paused game */
1877 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1878 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1881 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1882 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1888 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1889 if (key == key_info[i].key_default)
1890 joy |= key_info[i].action;
1895 if (key_status == KEY_PRESSED)
1896 key_joystick_mapping |= joy;
1898 key_joystick_mapping &= ~joy;
1903 if (game_status != GAME_MODE_PLAYING)
1904 key_joystick_mapping = 0;
1906 if (key_status == KEY_RELEASED)
1908 // reset flag to ignore repeated "key pressed" events after key release
1909 ignore_repeated_key = FALSE;
1914 if ((key == KSYM_F11 ||
1915 ((key == KSYM_Return ||
1916 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1917 video.fullscreen_available &&
1918 !ignore_repeated_key)
1920 setup.fullscreen = !setup.fullscreen;
1922 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1924 if (game_status == GAME_MODE_SETUP)
1925 RedrawSetupScreenAfterFullscreenToggle();
1927 // set flag to ignore repeated "key pressed" events
1928 ignore_repeated_key = TRUE;
1933 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1934 key == KSYM_minus || key == KSYM_KP_Subtract ||
1935 key == KSYM_plus || key == KSYM_KP_Add ||
1936 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1937 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1938 video.window_scaling_available &&
1939 !video.fullscreen_enabled)
1941 if (key == KSYM_0 || key == KSYM_KP_0)
1942 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1943 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1944 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1946 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1948 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1949 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1950 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1951 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1953 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1955 if (game_status == GAME_MODE_SETUP)
1956 RedrawSetupScreenAfterFullscreenToggle();
1961 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1962 key == KSYM_Return ||
1963 key == KSYM_Escape)))
1965 /* do not handle this key event anymore */
1966 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1970 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1971 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
1978 if (game_status == GAME_MODE_MAIN &&
1979 (key == setup.shortcut.toggle_pause || key == KSYM_space))
1981 StartGameActions(options.network, setup.autorecord, level.random_seed);
1986 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
1988 if (key == setup.shortcut.save_game)
1990 else if (key == setup.shortcut.load_game)
1992 else if (key == setup.shortcut.toggle_pause)
1993 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
1995 HandleTapeButtonKeys(key);
1996 HandleSoundButtonKeys(key);
1999 if (game_status == GAME_MODE_PLAYING && !network_playing)
2001 int centered_player_nr_next = -999;
2003 if (key == setup.shortcut.focus_player_all)
2004 centered_player_nr_next = -1;
2006 for (i = 0; i < MAX_PLAYERS; i++)
2007 if (key == setup.shortcut.focus_player[i])
2008 centered_player_nr_next = i;
2010 if (centered_player_nr_next != -999)
2012 game.centered_player_nr_next = centered_player_nr_next;
2013 game.set_centered_player = TRUE;
2017 tape.centered_player_nr_next = game.centered_player_nr_next;
2018 tape.set_centered_player = TRUE;
2023 HandleKeysSpecial(key);
2025 if (HandleGadgetsKeyInput(key))
2027 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2028 key = KSYM_UNDEFINED;
2031 switch (game_status)
2033 case GAME_MODE_PSEUDO_TYPENAME:
2034 HandleTypeName(0, key);
2037 case GAME_MODE_TITLE:
2038 case GAME_MODE_MAIN:
2039 case GAME_MODE_LEVELS:
2040 case GAME_MODE_LEVELNR:
2041 case GAME_MODE_SETUP:
2042 case GAME_MODE_INFO:
2043 case GAME_MODE_SCORES:
2048 if (game_status == GAME_MODE_TITLE)
2049 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2050 else if (game_status == GAME_MODE_MAIN)
2051 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2052 else if (game_status == GAME_MODE_LEVELS)
2053 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2054 else if (game_status == GAME_MODE_LEVELNR)
2055 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2056 else if (game_status == GAME_MODE_SETUP)
2057 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2058 else if (game_status == GAME_MODE_INFO)
2059 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2060 else if (game_status == GAME_MODE_SCORES)
2061 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2065 if (game_status != GAME_MODE_MAIN)
2066 FadeSkipNextFadeIn();
2068 if (game_status == GAME_MODE_TITLE)
2069 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2070 else if (game_status == GAME_MODE_LEVELS)
2071 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2072 else if (game_status == GAME_MODE_LEVELNR)
2073 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2074 else if (game_status == GAME_MODE_SETUP)
2075 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2076 else if (game_status == GAME_MODE_INFO)
2077 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2078 else if (game_status == GAME_MODE_SCORES)
2079 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2083 if (game_status == GAME_MODE_LEVELS)
2084 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2085 else if (game_status == GAME_MODE_LEVELNR)
2086 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2087 else if (game_status == GAME_MODE_SETUP)
2088 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2089 else if (game_status == GAME_MODE_INFO)
2090 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2091 else if (game_status == GAME_MODE_SCORES)
2092 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2095 case KSYM_Page_Down:
2096 if (game_status == GAME_MODE_LEVELS)
2097 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2098 else if (game_status == GAME_MODE_LEVELNR)
2099 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2100 else if (game_status == GAME_MODE_SETUP)
2101 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2102 else if (game_status == GAME_MODE_INFO)
2103 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2104 else if (game_status == GAME_MODE_SCORES)
2105 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2113 case GAME_MODE_EDITOR:
2114 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2115 HandleLevelEditorKeyInput(key);
2118 case GAME_MODE_PLAYING:
2123 RequestQuitGame(setup.ask_on_escape);
2133 if (key == KSYM_Escape)
2135 SetGameStatus(GAME_MODE_MAIN);
2143 HandleKeysDebug(key);
2146 void HandleNoEvent()
2148 HandleMouseCursor();
2150 switch (game_status)
2152 case GAME_MODE_PLAYING:
2153 HandleButtonOrFinger(-1, -1, -1);
2158 void HandleEventActions()
2160 // if (button_status && game_status != GAME_MODE_PLAYING)
2161 if (button_status && (game_status != GAME_MODE_PLAYING ||
2163 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2165 HandleButton(0, 0, button_status, -button_status);
2172 #if defined(NETWORK_AVALIABLE)
2173 if (options.network)
2177 switch (game_status)
2179 case GAME_MODE_MAIN:
2180 DrawPreviewLevelAnimation();
2183 case GAME_MODE_EDITOR:
2184 HandleLevelEditorIdle();
2192 static void HandleTileCursor(int dx, int dy, int button)
2195 ClearPlayerMouseAction();
2202 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2203 (dx < 0 ? MB_LEFTBUTTON :
2204 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2206 else if (!tile_cursor.moving)
2208 int old_xpos = tile_cursor.xpos;
2209 int old_ypos = tile_cursor.ypos;
2210 int new_xpos = old_xpos;
2211 int new_ypos = old_ypos;
2213 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2214 new_xpos = old_xpos + dx;
2216 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2217 new_ypos = old_ypos + dy;
2219 SetTileCursorTargetXY(new_xpos, new_ypos);
2223 static int HandleJoystickForAllPlayers()
2227 boolean no_joysticks_configured = TRUE;
2228 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2229 static byte joy_action_last[MAX_PLAYERS];
2231 for (i = 0; i < MAX_PLAYERS; i++)
2232 if (setup.input[i].use_joystick)
2233 no_joysticks_configured = FALSE;
2235 /* if no joysticks configured, map connected joysticks to players */
2236 if (no_joysticks_configured)
2237 use_as_joystick_nr = TRUE;
2239 for (i = 0; i < MAX_PLAYERS; i++)
2241 byte joy_action = 0;
2243 joy_action = JoystickExt(i, use_as_joystick_nr);
2244 result |= joy_action;
2246 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2247 joy_action != joy_action_last[i])
2248 stored_player[i].action = joy_action;
2250 joy_action_last[i] = joy_action;
2256 void HandleJoystick()
2258 static unsigned int joytest_delay = 0;
2259 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2260 static int joytest_last = 0;
2261 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2262 int delay_value = GADGET_FRAME_DELAY;
2263 int joystick = HandleJoystickForAllPlayers();
2264 int keyboard = key_joystick_mapping;
2265 int joy = (joystick | keyboard);
2266 int joytest = joystick;
2267 int left = joy & JOY_LEFT;
2268 int right = joy & JOY_RIGHT;
2269 int up = joy & JOY_UP;
2270 int down = joy & JOY_DOWN;
2271 int button = joy & JOY_BUTTON;
2272 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2273 int dx = (left ? -1 : right ? 1 : 0);
2274 int dy = (up ? -1 : down ? 1 : 0);
2275 boolean use_delay_value_first = (joytest != joytest_last);
2277 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2279 /* do not handle this button event anymore */
2283 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2285 if (game_status == GAME_MODE_PLAYING)
2287 // when playing MM style levels, also use delay for keyboard events
2288 joytest |= keyboard;
2290 // only use first delay value for new events, but not for changed events
2291 use_delay_value_first = (!joytest != !joytest_last);
2293 // only use delay after the initial keyboard event
2297 // for any joystick or keyboard event, enable playfield tile cursor
2298 if (dx || dy || button)
2299 SetTileCursorEnabled(TRUE);
2302 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2304 /* delay joystick/keyboard actions if axes/keys continually pressed */
2305 newbutton = dx = dy = 0;
2309 /* first start with longer delay, then continue with shorter delay */
2310 joytest_delay_value =
2311 (use_delay_value_first ? delay_value_first : delay_value);
2314 joytest_last = joytest;
2316 switch (game_status)
2318 case GAME_MODE_TITLE:
2319 case GAME_MODE_MAIN:
2320 case GAME_MODE_LEVELS:
2321 case GAME_MODE_LEVELNR:
2322 case GAME_MODE_SETUP:
2323 case GAME_MODE_INFO:
2324 case GAME_MODE_SCORES:
2326 if (game_status == GAME_MODE_TITLE)
2327 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2328 else if (game_status == GAME_MODE_MAIN)
2329 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2330 else if (game_status == GAME_MODE_LEVELS)
2331 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2332 else if (game_status == GAME_MODE_LEVELNR)
2333 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2334 else if (game_status == GAME_MODE_SETUP)
2335 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2336 else if (game_status == GAME_MODE_INFO)
2337 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2338 else if (game_status == GAME_MODE_SCORES)
2339 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2344 case GAME_MODE_PLAYING:
2346 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2347 if (tape.playing || keyboard)
2348 newbutton = ((joy & JOY_BUTTON) != 0);
2351 if (newbutton && AllPlayersGone)
2358 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2360 if (joystick & JOY_ACTION)
2361 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2363 else if (tape.recording && tape.pausing && !tape.use_mouse)
2365 if (joystick & JOY_ACTION)
2366 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2369 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2370 HandleTileCursor(dx, dy, button);
2379 void HandleSpecialGameControllerButtons(Event *event)
2381 #if defined(TARGET_SDL2)
2382 switch (event->type)
2384 case SDL_CONTROLLERBUTTONDOWN:
2385 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2386 HandleKey(KSYM_space, KEY_PRESSED);
2387 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2388 HandleKey(KSYM_Escape, KEY_PRESSED);
2392 case SDL_CONTROLLERBUTTONUP:
2393 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2394 HandleKey(KSYM_space, KEY_RELEASED);
2395 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2396 HandleKey(KSYM_Escape, KEY_RELEASED);
2403 void HandleSpecialGameControllerKeys(Key key, int key_status)
2405 #if defined(TARGET_SDL2)
2406 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2407 int button = SDL_CONTROLLER_BUTTON_INVALID;
2409 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2410 if (key == KSYM_Rewind)
2411 button = SDL_CONTROLLER_BUTTON_A;
2412 else if (key == KSYM_FastForward || key == KSYM_Menu)
2413 button = SDL_CONTROLLER_BUTTON_B;
2415 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2419 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2420 SDL_CONTROLLERBUTTONUP);
2422 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2423 event.cbutton.button = button;
2424 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2427 HandleJoystickEvent(&event);