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 int nr = GRID_ACTIVE_NR(); // previous screen orientation
641 video.display_width = new_display_width;
642 video.display_height = new_display_height;
644 SDLSetScreenProperties();
646 // check if screen orientation has changed (should always be true here)
647 if (nr != GRID_ACTIVE_NR())
651 if (game_status == GAME_MODE_SETUP)
652 RedrawSetupScreenAfterScreenRotation(nr);
654 nr = GRID_ACTIVE_NR();
656 overlay.grid_xsize = setup.touch.grid_xsize[nr];
657 overlay.grid_ysize = setup.touch.grid_ysize[nr];
659 for (x = 0; x < MAX_GRID_XSIZE; x++)
660 for (y = 0; y < MAX_GRID_YSIZE; y++)
661 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
669 #define NUM_TOUCH_FINGERS 3
674 SDL_FingerID finger_id;
677 } touch_info[NUM_TOUCH_FINGERS];
679 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
682 int x = event->x * overlay.grid_xsize;
683 int y = event->y * overlay.grid_ysize;
684 int grid_button = overlay.grid_button[x][y];
685 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
686 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
687 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
688 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
689 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
690 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
693 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
694 float event_x = (event->x);
695 float event_y = (event->y - ypos) / (1 - ypos);
696 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
697 event_y > 2.0 / 3.0 && event_y < 1 ?
698 setup.input[0].key.snap :
699 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
700 event_y > 2.0 / 3.0 && event_y < 1 ?
701 setup.input[0].key.drop :
702 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
703 event_y > 0 && event_y < 1.0 / 3.0 ?
704 setup.input[0].key.up :
705 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
706 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
707 setup.input[0].key.left :
708 event_x > 8.0 / 9.0 && event_x < 1 &&
709 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
710 setup.input[0].key.right :
711 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
712 event_y > 2.0 / 3.0 && event_y < 1 ?
713 setup.input[0].key.down :
716 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
718 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
722 // for any touch input event, enable overlay buttons (if activated)
723 SetOverlayEnabled(TRUE);
725 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
726 getKeyNameFromKey(key), key_status_name, event->fingerId);
728 // check if we already know this touch event's finger id
729 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
731 if (touch_info[i].touched &&
732 touch_info[i].finger_id == event->fingerId)
734 // Error(ERR_DEBUG, "MARK 1: %d", i);
740 if (i >= NUM_TOUCH_FINGERS)
742 if (key_status == KEY_PRESSED)
744 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
746 // unknown finger id -- get new, empty slot, if available
747 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
749 if (touch_info[i].counter < oldest_counter)
752 oldest_counter = touch_info[i].counter;
754 // Error(ERR_DEBUG, "MARK 2: %d", i);
757 if (!touch_info[i].touched)
759 // Error(ERR_DEBUG, "MARK 3: %d", i);
765 if (i >= NUM_TOUCH_FINGERS)
767 // all slots allocated -- use oldest slot
770 // Error(ERR_DEBUG, "MARK 4: %d", i);
775 // release of previously unknown key (should not happen)
777 if (key != KSYM_UNDEFINED)
779 HandleKey(key, KEY_RELEASED);
781 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
782 getKeyNameFromKey(key), "KEY_RELEASED", i);
787 if (i < NUM_TOUCH_FINGERS)
789 if (key_status == KEY_PRESSED)
791 if (touch_info[i].key != key)
793 if (touch_info[i].key != KSYM_UNDEFINED)
795 HandleKey(touch_info[i].key, KEY_RELEASED);
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
798 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
801 if (key != KSYM_UNDEFINED)
803 HandleKey(key, KEY_PRESSED);
805 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
806 getKeyNameFromKey(key), "KEY_PRESSED", i);
810 touch_info[i].touched = TRUE;
811 touch_info[i].finger_id = event->fingerId;
812 touch_info[i].counter = Counter();
813 touch_info[i].key = key;
817 if (touch_info[i].key != KSYM_UNDEFINED)
819 HandleKey(touch_info[i].key, KEY_RELEASED);
821 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
822 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
825 touch_info[i].touched = FALSE;
826 touch_info[i].finger_id = 0;
827 touch_info[i].counter = 0;
828 touch_info[i].key = 0;
833 void HandleFingerEvent_WipeGestures(FingerEvent *event)
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;
849 if (event->type == EVENT_FINGERPRESS)
851 if (event_x > 1.0 / 3.0)
855 motion_id = event->fingerId;
860 motion_key_x = KSYM_UNDEFINED;
861 motion_key_y = KSYM_UNDEFINED;
863 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
869 button_id = event->fingerId;
874 button_key = setup.input[0].key.snap;
876 HandleKey(button_key, KEY_PRESSED);
878 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
881 else if (event->type == EVENT_FINGERRELEASE)
883 if (event->fingerId == motion_id)
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);
892 motion_key_x = KSYM_UNDEFINED;
893 motion_key_y = KSYM_UNDEFINED;
895 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
897 else if (event->fingerId == button_id)
901 if (button_key != KSYM_UNDEFINED)
902 HandleKey(button_key, KEY_RELEASED);
904 button_key = KSYM_UNDEFINED;
906 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
909 else if (event->type == EVENT_FINGERMOTION)
911 if (event->fingerId == motion_id)
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 :
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 :
922 if (distance_x < move_trigger_distance / 2 ||
923 distance_x < distance_y)
924 new_motion_key_x = KSYM_UNDEFINED;
926 if (distance_y < move_trigger_distance / 2 ||
927 distance_y < distance_x)
928 new_motion_key_y = KSYM_UNDEFINED;
930 if (distance_x > move_trigger_distance ||
931 distance_y > move_trigger_distance)
933 if (new_motion_key_x != motion_key_x)
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);
941 if (new_motion_key_y != motion_key_y)
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);
952 motion_key_x = new_motion_key_x;
953 motion_key_y = new_motion_key_y;
955 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
958 else if (event->fingerId == button_id)
960 float distance_x = ABS(event_x - button_x1);
961 float distance_y = ABS(event_y - button_y1);
963 if (distance_x < drop_trigger_distance / 2 &&
964 distance_y > drop_trigger_distance)
966 if (button_key == setup.input[0].key.snap)
967 HandleKey(button_key, KEY_RELEASED);
972 button_key = setup.input[0].key.drop;
974 HandleKey(button_key, KEY_PRESSED);
976 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
982 void HandleFingerEvent(FingerEvent *event)
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",
991 event->dx, event->dy,
995 if (game_status != GAME_MODE_PLAYING)
998 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1000 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1001 local_player->mouse_action.button_hint =
1002 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1003 event->x < 0.5 ? MB_LEFTBUTTON :
1004 event->x > 0.5 ? MB_RIGHTBUTTON :
1010 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1011 HandleFingerEvent_VirtualButtons(event);
1012 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1013 HandleFingerEvent_WipeGestures(event);
1018 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
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;
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)
1029 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1034 // stop here if this function was not triggered by a touch event
1038 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1040 // finger started touching the screen
1050 ClearPlayerMouseAction();
1052 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1055 else if (button == MB_RELEASED && touched)
1057 // finger stopped touching the screen
1062 SetPlayerMouseAction(old_mx, old_my, last_button);
1064 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1066 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1071 // finger moved while touching the screen
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);
1078 if (new_x != old_x || new_y != old_y)
1083 // finger moved left or right from (horizontal) starting position
1085 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1087 SetPlayerMouseAction(old_mx, old_my, button_nr);
1089 last_button = button_nr;
1091 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1095 // finger stays at or returned to (horizontal) starting position
1097 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1099 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1104 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
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;
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)
1115 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1120 // stop here if this function was not triggered by a touch event
1124 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1126 // finger started touching the screen
1136 ClearPlayerMouseAction();
1138 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1141 else if (button == MB_RELEASED && touched)
1143 // finger stopped touching the screen
1148 SetPlayerMouseAction(old_mx, old_my, last_button);
1150 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1152 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1157 // finger moved while touching the screen
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);
1164 if (new_x != old_x || new_y != old_y)
1166 // finger moved away from starting position
1168 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1170 // quickly alternate between clicking and releasing for maximum speed
1171 if (FrameCounter % 2 == 0)
1172 button_nr = MB_RELEASED;
1174 SetPlayerMouseAction(old_mx, old_my, button_nr);
1177 last_button = button_nr;
1181 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1185 // finger stays at or returned to starting position
1187 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1189 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1194 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
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;
1206 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1215 started_on_player = FALSE;
1216 player_is_dropping = FALSE;
1217 player_drop_count = 0;
1221 motion_key_x = KSYM_UNDEFINED;
1222 motion_key_y = KSYM_UNDEFINED;
1224 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1227 else if (button == MB_RELEASED && touched)
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);
1239 if (started_on_player)
1241 if (player_is_dropping)
1243 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1245 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1249 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1251 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1255 motion_key_x = KSYM_UNDEFINED;
1256 motion_key_y = KSYM_UNDEFINED;
1258 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
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 :
1272 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1273 dy > 0 ? setup.input[0].key.down :
1276 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1277 (last_player_x != local_player->jx ||
1278 last_player_y != local_player->jy))
1280 // in case of asymmetric diagonal movement, use "preferred" direction
1282 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1284 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1285 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1287 local_player->last_move_dir = last_move_dir;
1289 // (required to prevent accidentally forcing direction for next movement)
1290 last_player_x = local_player->jx;
1291 last_player_y = local_player->jy;
1294 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1296 started_on_player = TRUE;
1297 player_drop_count = getPlayerInventorySize(0);
1298 player_is_dropping = (player_drop_count > 0);
1300 if (player_is_dropping)
1302 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1304 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1308 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1310 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1313 else if (dx != 0 || dy != 0)
1315 if (player_is_dropping &&
1316 player_drop_count == getPlayerInventorySize(0))
1318 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1320 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1321 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1323 player_is_dropping = FALSE;
1327 if (new_motion_key_x != motion_key_x)
1329 Error(ERR_DEBUG, "---------- %s %s ----------",
1330 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1331 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
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);
1339 if (new_motion_key_y != motion_key_y)
1341 Error(ERR_DEBUG, "---------- %s %s ----------",
1342 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1343 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
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);
1351 motion_key_x = new_motion_key_x;
1352 motion_key_y = new_motion_key_y;
1356 static void HandleButtonOrFinger(int mx, int my, int button)
1358 if (game_status != GAME_MODE_PLAYING)
1361 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
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);
1370 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1371 HandleButtonOrFinger_FollowFinger(mx, my, button);
1375 #if defined(TARGET_SDL2)
1377 static boolean checkTextInputKeyModState()
1379 // when playing, only handle raw key events and ignore text input
1380 if (game_status == GAME_MODE_PLAYING)
1383 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1386 void HandleTextEvent(TextEvent *event)
1388 char *text = event->text;
1389 Key key = getKeyFromKeyName(text);
1391 #if DEBUG_EVENTS_TEXT
1392 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1395 text[0], (int)(text[0]),
1397 getKeyNameFromKey(key),
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())
1408 // process text input as "classic" (with uppercase etc.) key input event
1409 HandleKey(key, KEY_PRESSED);
1410 HandleKey(key, KEY_RELEASED);
1413 void HandlePauseResumeEvent(PauseResumeEvent *event)
1415 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1419 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1427 void HandleKeyEvent(KeyEvent *event)
1429 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1430 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1431 Key key = GetEventKey(event, with_modifiers);
1432 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1434 #if DEBUG_EVENTS_KEY
1435 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1436 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1437 event->keysym.scancode,
1442 getKeyNameFromKey(key));
1445 #if defined(PLATFORM_ANDROID)
1446 if (key == KSYM_Back)
1448 // always map the "back" button to the "escape" key on Android devices
1453 // for any key event other than "back" button, disable overlay buttons
1454 SetOverlayEnabled(FALSE);
1458 HandleKeyModState(keymod, key_status);
1460 #if defined(TARGET_SDL2)
1461 // only handle raw key input without text modifier keys pressed
1462 if (!checkTextInputKeyModState())
1463 HandleKey(key, key_status);
1465 HandleKey(key, key_status);
1469 void HandleFocusEvent(FocusChangeEvent *event)
1471 static int old_joystick_status = -1;
1473 if (event->type == EVENT_FOCUSOUT)
1475 KeyboardAutoRepeatOn();
1476 old_joystick_status = joystick.status;
1477 joystick.status = JOYSTICK_NOT_AVAILABLE;
1479 ClearPlayerAction();
1481 else if (event->type == EVENT_FOCUSIN)
1483 /* When there are two Rocks'n'Diamonds windows which overlap and
1484 the player moves the pointer from one game window to the other,
1485 a 'FocusOut' event is generated for the window the pointer is
1486 leaving and a 'FocusIn' event is generated for the window the
1487 pointer is entering. In some cases, it can happen that the
1488 'FocusIn' event is handled by the one game process before the
1489 'FocusOut' event by the other game process. In this case the
1490 X11 environment would end up with activated keyboard auto repeat,
1491 because unfortunately this is a global setting and not (which
1492 would be far better) set for each X11 window individually.
1493 The effect would be keyboard auto repeat while playing the game
1494 (game_status == GAME_MODE_PLAYING), which is not desired.
1495 To avoid this special case, we just wait 1/10 second before
1496 processing the 'FocusIn' event.
1499 if (game_status == GAME_MODE_PLAYING)
1502 KeyboardAutoRepeatOffUnlessAutoplay();
1505 if (old_joystick_status != -1)
1506 joystick.status = old_joystick_status;
1510 void HandleClientMessageEvent(ClientMessageEvent *event)
1512 if (CheckCloseWindowEvent(event))
1516 void HandleWindowManagerEvent(Event *event)
1518 #if defined(TARGET_SDL)
1519 SDLHandleWindowManagerEvent(event);
1523 void HandleButton(int mx, int my, int button, int button_nr)
1525 static int old_mx = 0, old_my = 0;
1526 boolean button_hold = FALSE;
1527 boolean handle_gadgets = TRUE;
1533 button_nr = -button_nr;
1542 #if defined(PLATFORM_ANDROID)
1543 // when playing, only handle gadgets when using "follow finger" controls
1544 // or when using touch controls in combination with the MM game engine
1546 (game_status != GAME_MODE_PLAYING ||
1547 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1548 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1551 if (handle_gadgets && HandleGadgets(mx, my, button))
1553 /* do not handle this button event anymore */
1554 mx = my = -32; /* force mouse event to be outside screen tiles */
1557 if (HandleGlobalAnimClicks(mx, my, button))
1559 /* do not handle this button event anymore */
1560 return; /* force mouse event not to be handled at all */
1563 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1566 /* do not use scroll wheel button events for anything other than gadgets */
1567 if (IS_WHEEL_BUTTON(button_nr))
1570 switch (game_status)
1572 case GAME_MODE_TITLE:
1573 HandleTitleScreen(mx, my, 0, 0, button);
1576 case GAME_MODE_MAIN:
1577 HandleMainMenu(mx, my, 0, 0, button);
1580 case GAME_MODE_PSEUDO_TYPENAME:
1581 HandleTypeName(0, KSYM_Return);
1584 case GAME_MODE_LEVELS:
1585 HandleChooseLevelSet(mx, my, 0, 0, button);
1588 case GAME_MODE_LEVELNR:
1589 HandleChooseLevelNr(mx, my, 0, 0, button);
1592 case GAME_MODE_SCORES:
1593 HandleHallOfFame(0, 0, 0, 0, button);
1596 case GAME_MODE_EDITOR:
1597 HandleLevelEditorIdle();
1600 case GAME_MODE_INFO:
1601 HandleInfoScreen(mx, my, 0, 0, button);
1604 case GAME_MODE_SETUP:
1605 HandleSetupScreen(mx, my, 0, 0, button);
1608 case GAME_MODE_PLAYING:
1609 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1610 HandleButtonOrFinger(mx, my, button);
1612 SetPlayerMouseAction(mx, my, button);
1615 if (button == MB_PRESSED && !motion_status && !button_hold &&
1616 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1617 DumpTileFromScreen(mx, my);
1627 static boolean is_string_suffix(char *string, char *suffix)
1629 int string_len = strlen(string);
1630 int suffix_len = strlen(suffix);
1632 if (suffix_len > string_len)
1635 return (strEqual(&string[string_len - suffix_len], suffix));
1638 #define MAX_CHEAT_INPUT_LEN 32
1640 static void HandleKeysSpecial(Key key)
1642 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1643 char letter = getCharFromKey(key);
1644 int cheat_input_len = strlen(cheat_input);
1650 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1652 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1653 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1655 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1658 cheat_input[cheat_input_len++] = letter;
1659 cheat_input[cheat_input_len] = '\0';
1661 #if DEBUG_EVENTS_KEY
1662 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1665 if (game_status == GAME_MODE_MAIN)
1667 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1668 is_string_suffix(cheat_input, ":ist"))
1670 InsertSolutionTape();
1672 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1673 is_string_suffix(cheat_input, ":rg"))
1675 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1678 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1679 is_string_suffix(cheat_input, ":rs"))
1681 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1684 else if (is_string_suffix(cheat_input, ":reload-music") ||
1685 is_string_suffix(cheat_input, ":rm"))
1687 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1690 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1691 is_string_suffix(cheat_input, ":ra"))
1693 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1694 1 << ARTWORK_TYPE_SOUNDS |
1695 1 << ARTWORK_TYPE_MUSIC);
1698 else if (is_string_suffix(cheat_input, ":dump-level") ||
1699 is_string_suffix(cheat_input, ":dl"))
1703 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1704 is_string_suffix(cheat_input, ":dt"))
1708 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1709 is_string_suffix(cheat_input, ":ft"))
1711 /* fix single-player tapes that contain player input for more than one
1712 player (due to a bug in 3.3.1.2 and earlier versions), which results
1713 in playing levels with more than one player in multi-player mode,
1714 even though the tape was originally recorded in single-player mode */
1716 /* remove player input actions for all players but the first one */
1717 for (i = 1; i < MAX_PLAYERS; i++)
1718 tape.player_participates[i] = FALSE;
1720 tape.changed = TRUE;
1722 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1723 is_string_suffix(cheat_input, ":snl"))
1725 SaveNativeLevel(&level);
1727 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1728 is_string_suffix(cheat_input, ":fps"))
1730 global.show_frames_per_second = !global.show_frames_per_second;
1733 else if (game_status == GAME_MODE_PLAYING)
1736 if (is_string_suffix(cheat_input, ".q"))
1737 DEBUG_SetMaximumDynamite();
1740 else if (game_status == GAME_MODE_EDITOR)
1742 if (is_string_suffix(cheat_input, ":dump-brush") ||
1743 is_string_suffix(cheat_input, ":DB"))
1747 else if (is_string_suffix(cheat_input, ":DDB"))
1754 void HandleKeysDebug(Key key)
1759 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1761 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1763 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1765 if (key == setup.debug.frame_delay_key[i] &&
1766 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1768 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1769 setup.debug.frame_delay[i] : setup.game_frame_delay);
1771 if (!setup.debug.frame_delay_game_only)
1772 MenuFrameDelay = GameFrameDelay;
1774 SetVideoFrameDelay(GameFrameDelay);
1776 if (GameFrameDelay > ONE_SECOND_DELAY)
1777 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1778 else if (GameFrameDelay != 0)
1779 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1780 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1781 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1783 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1790 if (game_status == GAME_MODE_PLAYING)
1794 options.debug = !options.debug;
1796 Error(ERR_DEBUG, "debug mode %s",
1797 (options.debug ? "enabled" : "disabled"));
1799 else if (key == KSYM_v)
1801 Error(ERR_DEBUG, "currently using game engine version %d",
1802 game.engine_version);
1808 void HandleKey(Key key, int key_status)
1810 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1811 static boolean ignore_repeated_key = FALSE;
1812 static struct SetupKeyboardInfo ski;
1813 static struct SetupShortcutInfo ssi;
1822 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1823 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1824 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1825 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1826 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1827 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1832 #if defined(TARGET_SDL2)
1833 /* map special keys (media keys / remote control buttons) to default keys */
1834 if (key == KSYM_PlayPause)
1836 else if (key == KSYM_Select)
1840 HandleSpecialGameControllerKeys(key, key_status);
1842 if (game_status == GAME_MODE_PLAYING)
1844 /* only needed for single-step tape recording mode */
1845 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1848 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1850 byte key_action = 0;
1852 if (setup.input[pnr].use_joystick)
1855 ski = setup.input[pnr].key;
1857 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1858 if (key == *key_info[i].key_custom)
1859 key_action |= key_info[i].action;
1861 /* use combined snap+direction keys for the first player only */
1864 ssi = setup.shortcut;
1866 for (i = 0; i < NUM_DIRECTIONS; i++)
1867 if (key == *key_info[i].key_snap)
1868 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1871 if (key_status == KEY_PRESSED)
1872 stored_player[pnr].action |= key_action;
1874 stored_player[pnr].action &= ~key_action;
1876 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1878 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1880 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1882 /* if snap key already pressed, keep pause mode when releasing */
1883 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1884 has_snapped[pnr] = TRUE;
1886 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1888 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1890 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1891 getRedDiskReleaseFlag_SP() == 0)
1893 /* add a single inactive frame before dropping starts */
1894 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1895 stored_player[pnr].force_dropping = TRUE;
1898 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1900 /* if snap key was pressed without direction, leave pause mode */
1901 if (!has_snapped[pnr])
1902 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1904 has_snapped[pnr] = FALSE;
1907 else if (tape.recording && tape.pausing && !tape.use_mouse)
1909 /* prevent key release events from un-pausing a paused game */
1910 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1911 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1914 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1915 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1921 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1922 if (key == key_info[i].key_default)
1923 joy |= key_info[i].action;
1928 if (key_status == KEY_PRESSED)
1929 key_joystick_mapping |= joy;
1931 key_joystick_mapping &= ~joy;
1936 if (game_status != GAME_MODE_PLAYING)
1937 key_joystick_mapping = 0;
1939 if (key_status == KEY_RELEASED)
1941 // reset flag to ignore repeated "key pressed" events after key release
1942 ignore_repeated_key = FALSE;
1947 if ((key == KSYM_F11 ||
1948 ((key == KSYM_Return ||
1949 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1950 video.fullscreen_available &&
1951 !ignore_repeated_key)
1953 setup.fullscreen = !setup.fullscreen;
1955 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1957 if (game_status == GAME_MODE_SETUP)
1958 RedrawSetupScreenAfterFullscreenToggle();
1960 // set flag to ignore repeated "key pressed" events
1961 ignore_repeated_key = TRUE;
1966 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1967 key == KSYM_minus || key == KSYM_KP_Subtract ||
1968 key == KSYM_plus || key == KSYM_KP_Add ||
1969 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1970 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1971 video.window_scaling_available &&
1972 !video.fullscreen_enabled)
1974 if (key == KSYM_0 || key == KSYM_KP_0)
1975 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1976 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1977 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1979 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1981 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1982 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1983 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1984 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1986 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1988 if (game_status == GAME_MODE_SETUP)
1989 RedrawSetupScreenAfterFullscreenToggle();
1994 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1995 key == KSYM_Return ||
1996 key == KSYM_Escape)))
1998 /* do not handle this key event anymore */
1999 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2003 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2004 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2011 if (game_status == GAME_MODE_MAIN &&
2012 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2014 StartGameActions(options.network, setup.autorecord, level.random_seed);
2019 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2021 if (key == setup.shortcut.save_game)
2023 else if (key == setup.shortcut.load_game)
2025 else if (key == setup.shortcut.toggle_pause)
2026 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2028 HandleTapeButtonKeys(key);
2029 HandleSoundButtonKeys(key);
2032 if (game_status == GAME_MODE_PLAYING && !network_playing)
2034 int centered_player_nr_next = -999;
2036 if (key == setup.shortcut.focus_player_all)
2037 centered_player_nr_next = -1;
2039 for (i = 0; i < MAX_PLAYERS; i++)
2040 if (key == setup.shortcut.focus_player[i])
2041 centered_player_nr_next = i;
2043 if (centered_player_nr_next != -999)
2045 game.centered_player_nr_next = centered_player_nr_next;
2046 game.set_centered_player = TRUE;
2050 tape.centered_player_nr_next = game.centered_player_nr_next;
2051 tape.set_centered_player = TRUE;
2056 HandleKeysSpecial(key);
2058 if (HandleGadgetsKeyInput(key))
2060 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2061 key = KSYM_UNDEFINED;
2064 switch (game_status)
2066 case GAME_MODE_PSEUDO_TYPENAME:
2067 HandleTypeName(0, key);
2070 case GAME_MODE_TITLE:
2071 case GAME_MODE_MAIN:
2072 case GAME_MODE_LEVELS:
2073 case GAME_MODE_LEVELNR:
2074 case GAME_MODE_SETUP:
2075 case GAME_MODE_INFO:
2076 case GAME_MODE_SCORES:
2081 if (game_status == GAME_MODE_TITLE)
2082 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2083 else if (game_status == GAME_MODE_MAIN)
2084 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2085 else if (game_status == GAME_MODE_LEVELS)
2086 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2087 else if (game_status == GAME_MODE_LEVELNR)
2088 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2089 else if (game_status == GAME_MODE_SETUP)
2090 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2091 else if (game_status == GAME_MODE_INFO)
2092 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2093 else if (game_status == GAME_MODE_SCORES)
2094 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2098 if (game_status != GAME_MODE_MAIN)
2099 FadeSkipNextFadeIn();
2101 if (game_status == GAME_MODE_TITLE)
2102 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2103 else if (game_status == GAME_MODE_LEVELS)
2104 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2105 else if (game_status == GAME_MODE_LEVELNR)
2106 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2107 else if (game_status == GAME_MODE_SETUP)
2108 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2109 else if (game_status == GAME_MODE_INFO)
2110 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2111 else if (game_status == GAME_MODE_SCORES)
2112 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2116 if (game_status == GAME_MODE_LEVELS)
2117 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2118 else if (game_status == GAME_MODE_LEVELNR)
2119 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2120 else if (game_status == GAME_MODE_SETUP)
2121 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2122 else if (game_status == GAME_MODE_INFO)
2123 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2124 else if (game_status == GAME_MODE_SCORES)
2125 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2128 case KSYM_Page_Down:
2129 if (game_status == GAME_MODE_LEVELS)
2130 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2131 else if (game_status == GAME_MODE_LEVELNR)
2132 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2133 else if (game_status == GAME_MODE_SETUP)
2134 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2135 else if (game_status == GAME_MODE_INFO)
2136 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2137 else if (game_status == GAME_MODE_SCORES)
2138 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2146 case GAME_MODE_EDITOR:
2147 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2148 HandleLevelEditorKeyInput(key);
2151 case GAME_MODE_PLAYING:
2156 RequestQuitGame(setup.ask_on_escape);
2166 if (key == KSYM_Escape)
2168 SetGameStatus(GAME_MODE_MAIN);
2176 HandleKeysDebug(key);
2179 void HandleNoEvent()
2181 HandleMouseCursor();
2183 switch (game_status)
2185 case GAME_MODE_PLAYING:
2186 HandleButtonOrFinger(-1, -1, -1);
2191 void HandleEventActions()
2193 // if (button_status && game_status != GAME_MODE_PLAYING)
2194 if (button_status && (game_status != GAME_MODE_PLAYING ||
2196 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2198 HandleButton(0, 0, button_status, -button_status);
2205 #if defined(NETWORK_AVALIABLE)
2206 if (options.network)
2210 switch (game_status)
2212 case GAME_MODE_MAIN:
2213 DrawPreviewLevelAnimation();
2216 case GAME_MODE_EDITOR:
2217 HandleLevelEditorIdle();
2225 static void HandleTileCursor(int dx, int dy, int button)
2228 ClearPlayerMouseAction();
2235 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2236 (dx < 0 ? MB_LEFTBUTTON :
2237 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2239 else if (!tile_cursor.moving)
2241 int old_xpos = tile_cursor.xpos;
2242 int old_ypos = tile_cursor.ypos;
2243 int new_xpos = old_xpos;
2244 int new_ypos = old_ypos;
2246 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2247 new_xpos = old_xpos + dx;
2249 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2250 new_ypos = old_ypos + dy;
2252 SetTileCursorTargetXY(new_xpos, new_ypos);
2256 static int HandleJoystickForAllPlayers()
2260 boolean no_joysticks_configured = TRUE;
2261 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2262 static byte joy_action_last[MAX_PLAYERS];
2264 for (i = 0; i < MAX_PLAYERS; i++)
2265 if (setup.input[i].use_joystick)
2266 no_joysticks_configured = FALSE;
2268 /* if no joysticks configured, map connected joysticks to players */
2269 if (no_joysticks_configured)
2270 use_as_joystick_nr = TRUE;
2272 for (i = 0; i < MAX_PLAYERS; i++)
2274 byte joy_action = 0;
2276 joy_action = JoystickExt(i, use_as_joystick_nr);
2277 result |= joy_action;
2279 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2280 joy_action != joy_action_last[i])
2281 stored_player[i].action = joy_action;
2283 joy_action_last[i] = joy_action;
2289 void HandleJoystick()
2291 static unsigned int joytest_delay = 0;
2292 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2293 static int joytest_last = 0;
2294 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2295 int delay_value = GADGET_FRAME_DELAY;
2296 int joystick = HandleJoystickForAllPlayers();
2297 int keyboard = key_joystick_mapping;
2298 int joy = (joystick | keyboard);
2299 int joytest = joystick;
2300 int left = joy & JOY_LEFT;
2301 int right = joy & JOY_RIGHT;
2302 int up = joy & JOY_UP;
2303 int down = joy & JOY_DOWN;
2304 int button = joy & JOY_BUTTON;
2305 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2306 int dx = (left ? -1 : right ? 1 : 0);
2307 int dy = (up ? -1 : down ? 1 : 0);
2308 boolean use_delay_value_first = (joytest != joytest_last);
2310 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2312 /* do not handle this button event anymore */
2316 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2318 if (game_status == GAME_MODE_PLAYING)
2320 // when playing MM style levels, also use delay for keyboard events
2321 joytest |= keyboard;
2323 // only use first delay value for new events, but not for changed events
2324 use_delay_value_first = (!joytest != !joytest_last);
2326 // only use delay after the initial keyboard event
2330 // for any joystick or keyboard event, enable playfield tile cursor
2331 if (dx || dy || button)
2332 SetTileCursorEnabled(TRUE);
2335 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2337 /* delay joystick/keyboard actions if axes/keys continually pressed */
2338 newbutton = dx = dy = 0;
2342 /* first start with longer delay, then continue with shorter delay */
2343 joytest_delay_value =
2344 (use_delay_value_first ? delay_value_first : delay_value);
2347 joytest_last = joytest;
2349 switch (game_status)
2351 case GAME_MODE_TITLE:
2352 case GAME_MODE_MAIN:
2353 case GAME_MODE_LEVELS:
2354 case GAME_MODE_LEVELNR:
2355 case GAME_MODE_SETUP:
2356 case GAME_MODE_INFO:
2357 case GAME_MODE_SCORES:
2359 if (game_status == GAME_MODE_TITLE)
2360 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2361 else if (game_status == GAME_MODE_MAIN)
2362 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2363 else if (game_status == GAME_MODE_LEVELS)
2364 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2365 else if (game_status == GAME_MODE_LEVELNR)
2366 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2367 else if (game_status == GAME_MODE_SETUP)
2368 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2369 else if (game_status == GAME_MODE_INFO)
2370 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2371 else if (game_status == GAME_MODE_SCORES)
2372 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2377 case GAME_MODE_PLAYING:
2379 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2380 if (tape.playing || keyboard)
2381 newbutton = ((joy & JOY_BUTTON) != 0);
2384 if (newbutton && AllPlayersGone)
2391 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2393 if (joystick & JOY_ACTION)
2394 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2396 else if (tape.recording && tape.pausing && !tape.use_mouse)
2398 if (joystick & JOY_ACTION)
2399 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2402 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2403 HandleTileCursor(dx, dy, button);
2412 void HandleSpecialGameControllerButtons(Event *event)
2414 #if defined(TARGET_SDL2)
2415 switch (event->type)
2417 case SDL_CONTROLLERBUTTONDOWN:
2418 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2419 HandleKey(KSYM_space, KEY_PRESSED);
2420 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2421 HandleKey(KSYM_Escape, KEY_PRESSED);
2425 case SDL_CONTROLLERBUTTONUP:
2426 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2427 HandleKey(KSYM_space, KEY_RELEASED);
2428 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2429 HandleKey(KSYM_Escape, KEY_RELEASED);
2436 void HandleSpecialGameControllerKeys(Key key, int key_status)
2438 #if defined(TARGET_SDL2)
2439 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2440 int button = SDL_CONTROLLER_BUTTON_INVALID;
2442 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2443 if (key == KSYM_Rewind)
2444 button = SDL_CONTROLLER_BUTTON_A;
2445 else if (key == KSYM_FastForward || key == KSYM_Menu)
2446 button = SDL_CONTROLLER_BUTTON_B;
2448 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2452 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2453 SDL_CONTROLLERBUTTONUP);
2455 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2456 event.cbutton.button = button;
2457 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2460 HandleJoystickEvent(&event);