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)
653 // save active virtual buttons (in case of just configuring them)
654 for (x = 0; x < MAX_GRID_XSIZE; x++)
655 for (y = 0; y < MAX_GRID_YSIZE; y++)
656 overlay.grid_button_all[nr][x][y] = overlay.grid_button[x][y];
659 nr = GRID_ACTIVE_NR();
661 overlay.grid_xsize = overlay.grid_xsize_all[nr];
662 overlay.grid_ysize = overlay.grid_ysize_all[nr];
664 for (x = 0; x < MAX_GRID_XSIZE; x++)
665 for (y = 0; y < MAX_GRID_YSIZE; y++)
666 overlay.grid_button[x][y] = overlay.grid_button_all[nr][x][y];
674 #define NUM_TOUCH_FINGERS 3
679 SDL_FingerID finger_id;
682 } touch_info[NUM_TOUCH_FINGERS];
684 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
686 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
687 float event_x = (event->x);
688 float event_y = (event->y - ypos) / (1 - ypos);
689 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
690 event_y > 2.0 / 3.0 && event_y < 1 ?
691 setup.input[0].key.snap :
692 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
693 event_y > 2.0 / 3.0 && event_y < 1 ?
694 setup.input[0].key.drop :
695 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
696 event_y > 0 && event_y < 1.0 / 3.0 ?
697 setup.input[0].key.up :
698 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
699 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
700 setup.input[0].key.left :
701 event_x > 8.0 / 9.0 && event_x < 1 &&
702 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
703 setup.input[0].key.right :
704 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
705 event_y > 2.0 / 3.0 && event_y < 1 ?
706 setup.input[0].key.down :
708 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
710 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
714 // for any touch input event, enable overlay buttons (if activated)
715 SetOverlayEnabled(TRUE);
717 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
718 getKeyNameFromKey(key), key_status_name, event->fingerId);
720 // check if we already know this touch event's finger id
721 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
723 if (touch_info[i].touched &&
724 touch_info[i].finger_id == event->fingerId)
726 // Error(ERR_DEBUG, "MARK 1: %d", i);
732 if (i >= NUM_TOUCH_FINGERS)
734 if (key_status == KEY_PRESSED)
736 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
738 // unknown finger id -- get new, empty slot, if available
739 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
741 if (touch_info[i].counter < oldest_counter)
744 oldest_counter = touch_info[i].counter;
746 // Error(ERR_DEBUG, "MARK 2: %d", i);
749 if (!touch_info[i].touched)
751 // Error(ERR_DEBUG, "MARK 3: %d", i);
757 if (i >= NUM_TOUCH_FINGERS)
759 // all slots allocated -- use oldest slot
762 // Error(ERR_DEBUG, "MARK 4: %d", i);
767 // release of previously unknown key (should not happen)
769 if (key != KSYM_UNDEFINED)
771 HandleKey(key, KEY_RELEASED);
773 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
774 getKeyNameFromKey(key), "KEY_RELEASED", i);
779 if (i < NUM_TOUCH_FINGERS)
781 if (key_status == KEY_PRESSED)
783 if (touch_info[i].key != key)
785 if (touch_info[i].key != KSYM_UNDEFINED)
787 HandleKey(touch_info[i].key, KEY_RELEASED);
789 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
790 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
793 if (key != KSYM_UNDEFINED)
795 HandleKey(key, KEY_PRESSED);
797 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
798 getKeyNameFromKey(key), "KEY_PRESSED", i);
802 touch_info[i].touched = TRUE;
803 touch_info[i].finger_id = event->fingerId;
804 touch_info[i].counter = Counter();
805 touch_info[i].key = key;
809 if (touch_info[i].key != KSYM_UNDEFINED)
811 HandleKey(touch_info[i].key, KEY_RELEASED);
813 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
814 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
817 touch_info[i].touched = FALSE;
818 touch_info[i].finger_id = 0;
819 touch_info[i].counter = 0;
820 touch_info[i].key = 0;
825 void HandleFingerEvent_WipeGestures(FingerEvent *event)
827 static Key motion_key_x = KSYM_UNDEFINED;
828 static Key motion_key_y = KSYM_UNDEFINED;
829 static Key button_key = KSYM_UNDEFINED;
830 static float motion_x1, motion_y1;
831 static float button_x1, button_y1;
832 static SDL_FingerID motion_id = -1;
833 static SDL_FingerID button_id = -1;
834 int move_trigger_distance_percent = setup.touch.move_distance;
835 int drop_trigger_distance_percent = setup.touch.drop_distance;
836 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
837 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
838 float event_x = event->x;
839 float event_y = event->y;
841 if (event->type == EVENT_FINGERPRESS)
843 if (event_x > 1.0 / 3.0)
847 motion_id = event->fingerId;
852 motion_key_x = KSYM_UNDEFINED;
853 motion_key_y = KSYM_UNDEFINED;
855 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
861 button_id = event->fingerId;
866 button_key = setup.input[0].key.snap;
868 HandleKey(button_key, KEY_PRESSED);
870 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
873 else if (event->type == EVENT_FINGERRELEASE)
875 if (event->fingerId == motion_id)
879 if (motion_key_x != KSYM_UNDEFINED)
880 HandleKey(motion_key_x, KEY_RELEASED);
881 if (motion_key_y != KSYM_UNDEFINED)
882 HandleKey(motion_key_y, KEY_RELEASED);
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
889 else if (event->fingerId == button_id)
893 if (button_key != KSYM_UNDEFINED)
894 HandleKey(button_key, KEY_RELEASED);
896 button_key = KSYM_UNDEFINED;
898 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
901 else if (event->type == EVENT_FINGERMOTION)
903 if (event->fingerId == motion_id)
905 float distance_x = ABS(event_x - motion_x1);
906 float distance_y = ABS(event_y - motion_y1);
907 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
908 event_x > motion_x1 ? setup.input[0].key.right :
910 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
911 event_y > motion_y1 ? setup.input[0].key.down :
914 if (distance_x < move_trigger_distance / 2 ||
915 distance_x < distance_y)
916 new_motion_key_x = KSYM_UNDEFINED;
918 if (distance_y < move_trigger_distance / 2 ||
919 distance_y < distance_x)
920 new_motion_key_y = KSYM_UNDEFINED;
922 if (distance_x > move_trigger_distance ||
923 distance_y > move_trigger_distance)
925 if (new_motion_key_x != motion_key_x)
927 if (motion_key_x != KSYM_UNDEFINED)
928 HandleKey(motion_key_x, KEY_RELEASED);
929 if (new_motion_key_x != KSYM_UNDEFINED)
930 HandleKey(new_motion_key_x, KEY_PRESSED);
933 if (new_motion_key_y != motion_key_y)
935 if (motion_key_y != KSYM_UNDEFINED)
936 HandleKey(motion_key_y, KEY_RELEASED);
937 if (new_motion_key_y != KSYM_UNDEFINED)
938 HandleKey(new_motion_key_y, KEY_PRESSED);
944 motion_key_x = new_motion_key_x;
945 motion_key_y = new_motion_key_y;
947 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
950 else if (event->fingerId == button_id)
952 float distance_x = ABS(event_x - button_x1);
953 float distance_y = ABS(event_y - button_y1);
955 if (distance_x < drop_trigger_distance / 2 &&
956 distance_y > drop_trigger_distance)
958 if (button_key == setup.input[0].key.snap)
959 HandleKey(button_key, KEY_RELEASED);
964 button_key = setup.input[0].key.drop;
966 HandleKey(button_key, KEY_PRESSED);
968 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
974 void HandleFingerEvent(FingerEvent *event)
976 #if DEBUG_EVENTS_FINGER
977 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
978 event->type == EVENT_FINGERPRESS ? "pressed" :
979 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
983 event->dx, event->dy,
987 if (game_status != GAME_MODE_PLAYING)
990 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
992 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
993 local_player->mouse_action.button_hint =
994 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
995 event->x < 0.5 ? MB_LEFTBUTTON :
996 event->x > 0.5 ? MB_RIGHTBUTTON :
1002 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1003 HandleFingerEvent_VirtualButtons(event);
1004 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1005 HandleFingerEvent_WipeGestures(event);
1010 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1012 static int old_mx = 0, old_my = 0;
1013 static int last_button = MB_LEFTBUTTON;
1014 static boolean touched = FALSE;
1015 static boolean tapped = FALSE;
1017 // screen tile was tapped (but finger not touching the screen anymore)
1018 // (this point will also be reached without receiving a touch event)
1019 if (tapped && !touched)
1021 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1026 // stop here if this function was not triggered by a touch event
1030 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1032 // finger started touching the screen
1042 ClearPlayerMouseAction();
1044 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1047 else if (button == MB_RELEASED && touched)
1049 // finger stopped touching the screen
1054 SetPlayerMouseAction(old_mx, old_my, last_button);
1056 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1063 // finger moved while touching the screen
1065 int old_x = getLevelFromScreenX(old_mx);
1066 int old_y = getLevelFromScreenY(old_my);
1067 int new_x = getLevelFromScreenX(mx);
1068 int new_y = getLevelFromScreenY(my);
1070 if (new_x != old_x || new_y != old_y)
1075 // finger moved left or right from (horizontal) starting position
1077 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1079 SetPlayerMouseAction(old_mx, old_my, button_nr);
1081 last_button = button_nr;
1083 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1087 // finger stays at or returned to (horizontal) starting position
1089 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1091 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1096 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1098 static int old_mx = 0, old_my = 0;
1099 static int last_button = MB_LEFTBUTTON;
1100 static boolean touched = FALSE;
1101 static boolean tapped = FALSE;
1103 // screen tile was tapped (but finger not touching the screen anymore)
1104 // (this point will also be reached without receiving a touch event)
1105 if (tapped && !touched)
1107 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1112 // stop here if this function was not triggered by a touch event
1116 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1118 // finger started touching the screen
1128 ClearPlayerMouseAction();
1130 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1133 else if (button == MB_RELEASED && touched)
1135 // finger stopped touching the screen
1140 SetPlayerMouseAction(old_mx, old_my, last_button);
1142 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1149 // finger moved while touching the screen
1151 int old_x = getLevelFromScreenX(old_mx);
1152 int old_y = getLevelFromScreenY(old_my);
1153 int new_x = getLevelFromScreenX(mx);
1154 int new_y = getLevelFromScreenY(my);
1156 if (new_x != old_x || new_y != old_y)
1158 // finger moved away from starting position
1160 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1162 // quickly alternate between clicking and releasing for maximum speed
1163 if (FrameCounter % 2 == 0)
1164 button_nr = MB_RELEASED;
1166 SetPlayerMouseAction(old_mx, old_my, button_nr);
1169 last_button = button_nr;
1173 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1177 // finger stays at or returned to starting position
1179 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1181 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1186 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1188 static int old_mx = 0, old_my = 0;
1189 static Key motion_key_x = KSYM_UNDEFINED;
1190 static Key motion_key_y = KSYM_UNDEFINED;
1191 static boolean touched = FALSE;
1192 static boolean started_on_player = FALSE;
1193 static boolean player_is_dropping = FALSE;
1194 static int player_drop_count = 0;
1195 static int last_player_x = -1;
1196 static int last_player_y = -1;
1198 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1207 started_on_player = FALSE;
1208 player_is_dropping = FALSE;
1209 player_drop_count = 0;
1213 motion_key_x = KSYM_UNDEFINED;
1214 motion_key_y = KSYM_UNDEFINED;
1216 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1219 else if (button == MB_RELEASED && touched)
1226 if (motion_key_x != KSYM_UNDEFINED)
1227 HandleKey(motion_key_x, KEY_RELEASED);
1228 if (motion_key_y != KSYM_UNDEFINED)
1229 HandleKey(motion_key_y, KEY_RELEASED);
1231 if (started_on_player)
1233 if (player_is_dropping)
1235 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1237 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1241 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1243 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1247 motion_key_x = KSYM_UNDEFINED;
1248 motion_key_y = KSYM_UNDEFINED;
1250 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1255 int src_x = local_player->jx;
1256 int src_y = local_player->jy;
1257 int dst_x = getLevelFromScreenX(old_mx);
1258 int dst_y = getLevelFromScreenY(old_my);
1259 int dx = dst_x - src_x;
1260 int dy = dst_y - src_y;
1261 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1262 dx > 0 ? setup.input[0].key.right :
1264 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1265 dy > 0 ? setup.input[0].key.down :
1268 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1269 (last_player_x != local_player->jx ||
1270 last_player_y != local_player->jy))
1272 // in case of asymmetric diagonal movement, use "preferred" direction
1274 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1276 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1277 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1279 local_player->last_move_dir = last_move_dir;
1281 // (required to prevent accidentally forcing direction for next movement)
1282 last_player_x = local_player->jx;
1283 last_player_y = local_player->jy;
1286 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1288 started_on_player = TRUE;
1289 player_drop_count = getPlayerInventorySize(0);
1290 player_is_dropping = (player_drop_count > 0);
1292 if (player_is_dropping)
1294 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1296 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1300 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1302 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1305 else if (dx != 0 || dy != 0)
1307 if (player_is_dropping &&
1308 player_drop_count == getPlayerInventorySize(0))
1310 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1312 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1313 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1315 player_is_dropping = FALSE;
1319 if (new_motion_key_x != motion_key_x)
1321 Error(ERR_DEBUG, "---------- %s %s ----------",
1322 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1323 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1325 if (motion_key_x != KSYM_UNDEFINED)
1326 HandleKey(motion_key_x, KEY_RELEASED);
1327 if (new_motion_key_x != KSYM_UNDEFINED)
1328 HandleKey(new_motion_key_x, KEY_PRESSED);
1331 if (new_motion_key_y != motion_key_y)
1333 Error(ERR_DEBUG, "---------- %s %s ----------",
1334 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1335 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1337 if (motion_key_y != KSYM_UNDEFINED)
1338 HandleKey(motion_key_y, KEY_RELEASED);
1339 if (new_motion_key_y != KSYM_UNDEFINED)
1340 HandleKey(new_motion_key_y, KEY_PRESSED);
1343 motion_key_x = new_motion_key_x;
1344 motion_key_y = new_motion_key_y;
1348 static void HandleButtonOrFinger(int mx, int my, int button)
1350 if (game_status != GAME_MODE_PLAYING)
1353 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1355 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1356 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1357 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1358 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1362 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1363 HandleButtonOrFinger_FollowFinger(mx, my, button);
1367 #if defined(TARGET_SDL2)
1369 static boolean checkTextInputKeyModState()
1371 // when playing, only handle raw key events and ignore text input
1372 if (game_status == GAME_MODE_PLAYING)
1375 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1378 void HandleTextEvent(TextEvent *event)
1380 char *text = event->text;
1381 Key key = getKeyFromKeyName(text);
1383 #if DEBUG_EVENTS_TEXT
1384 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1387 text[0], (int)(text[0]),
1389 getKeyNameFromKey(key),
1393 #if !defined(HAS_SCREEN_KEYBOARD)
1394 // non-mobile devices: only handle key input with modifier keys pressed here
1395 // (every other key input is handled directly as physical key input event)
1396 if (!checkTextInputKeyModState())
1400 // process text input as "classic" (with uppercase etc.) key input event
1401 HandleKey(key, KEY_PRESSED);
1402 HandleKey(key, KEY_RELEASED);
1405 void HandlePauseResumeEvent(PauseResumeEvent *event)
1407 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1411 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1419 void HandleKeyEvent(KeyEvent *event)
1421 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1422 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1423 Key key = GetEventKey(event, with_modifiers);
1424 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1426 #if DEBUG_EVENTS_KEY
1427 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1428 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1429 event->keysym.scancode,
1434 getKeyNameFromKey(key));
1437 #if defined(PLATFORM_ANDROID)
1438 if (key == KSYM_Back)
1440 // always map the "back" button to the "escape" key on Android devices
1445 // for any key event other than "back" button, disable overlay buttons
1446 SetOverlayEnabled(FALSE);
1450 HandleKeyModState(keymod, key_status);
1452 #if defined(TARGET_SDL2)
1453 // only handle raw key input without text modifier keys pressed
1454 if (!checkTextInputKeyModState())
1455 HandleKey(key, key_status);
1457 HandleKey(key, key_status);
1461 void HandleFocusEvent(FocusChangeEvent *event)
1463 static int old_joystick_status = -1;
1465 if (event->type == EVENT_FOCUSOUT)
1467 KeyboardAutoRepeatOn();
1468 old_joystick_status = joystick.status;
1469 joystick.status = JOYSTICK_NOT_AVAILABLE;
1471 ClearPlayerAction();
1473 else if (event->type == EVENT_FOCUSIN)
1475 /* When there are two Rocks'n'Diamonds windows which overlap and
1476 the player moves the pointer from one game window to the other,
1477 a 'FocusOut' event is generated for the window the pointer is
1478 leaving and a 'FocusIn' event is generated for the window the
1479 pointer is entering. In some cases, it can happen that the
1480 'FocusIn' event is handled by the one game process before the
1481 'FocusOut' event by the other game process. In this case the
1482 X11 environment would end up with activated keyboard auto repeat,
1483 because unfortunately this is a global setting and not (which
1484 would be far better) set for each X11 window individually.
1485 The effect would be keyboard auto repeat while playing the game
1486 (game_status == GAME_MODE_PLAYING), which is not desired.
1487 To avoid this special case, we just wait 1/10 second before
1488 processing the 'FocusIn' event.
1491 if (game_status == GAME_MODE_PLAYING)
1494 KeyboardAutoRepeatOffUnlessAutoplay();
1497 if (old_joystick_status != -1)
1498 joystick.status = old_joystick_status;
1502 void HandleClientMessageEvent(ClientMessageEvent *event)
1504 if (CheckCloseWindowEvent(event))
1508 void HandleWindowManagerEvent(Event *event)
1510 #if defined(TARGET_SDL)
1511 SDLHandleWindowManagerEvent(event);
1515 void HandleButton(int mx, int my, int button, int button_nr)
1517 static int old_mx = 0, old_my = 0;
1518 boolean button_hold = FALSE;
1519 boolean handle_gadgets = TRUE;
1525 button_nr = -button_nr;
1534 #if defined(PLATFORM_ANDROID)
1535 // when playing, only handle gadgets when using "follow finger" controls
1536 // or when using touch controls in combination with the MM game engine
1538 (game_status != GAME_MODE_PLAYING ||
1539 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1540 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1543 if (handle_gadgets && HandleGadgets(mx, my, button))
1545 /* do not handle this button event anymore */
1546 mx = my = -32; /* force mouse event to be outside screen tiles */
1549 if (HandleGlobalAnimClicks(mx, my, button))
1551 /* do not handle this button event anymore */
1552 return; /* force mouse event not to be handled at all */
1555 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1558 /* do not use scroll wheel button events for anything other than gadgets */
1559 if (IS_WHEEL_BUTTON(button_nr))
1562 switch (game_status)
1564 case GAME_MODE_TITLE:
1565 HandleTitleScreen(mx, my, 0, 0, button);
1568 case GAME_MODE_MAIN:
1569 HandleMainMenu(mx, my, 0, 0, button);
1572 case GAME_MODE_PSEUDO_TYPENAME:
1573 HandleTypeName(0, KSYM_Return);
1576 case GAME_MODE_LEVELS:
1577 HandleChooseLevelSet(mx, my, 0, 0, button);
1580 case GAME_MODE_LEVELNR:
1581 HandleChooseLevelNr(mx, my, 0, 0, button);
1584 case GAME_MODE_SCORES:
1585 HandleHallOfFame(0, 0, 0, 0, button);
1588 case GAME_MODE_EDITOR:
1589 HandleLevelEditorIdle();
1592 case GAME_MODE_INFO:
1593 HandleInfoScreen(mx, my, 0, 0, button);
1596 case GAME_MODE_SETUP:
1597 HandleSetupScreen(mx, my, 0, 0, button);
1600 case GAME_MODE_PLAYING:
1601 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1602 HandleButtonOrFinger(mx, my, button);
1604 SetPlayerMouseAction(mx, my, button);
1607 if (button == MB_PRESSED && !motion_status && !button_hold &&
1608 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1609 DumpTileFromScreen(mx, my);
1619 static boolean is_string_suffix(char *string, char *suffix)
1621 int string_len = strlen(string);
1622 int suffix_len = strlen(suffix);
1624 if (suffix_len > string_len)
1627 return (strEqual(&string[string_len - suffix_len], suffix));
1630 #define MAX_CHEAT_INPUT_LEN 32
1632 static void HandleKeysSpecial(Key key)
1634 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1635 char letter = getCharFromKey(key);
1636 int cheat_input_len = strlen(cheat_input);
1642 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1644 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1645 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1647 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1650 cheat_input[cheat_input_len++] = letter;
1651 cheat_input[cheat_input_len] = '\0';
1653 #if DEBUG_EVENTS_KEY
1654 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1657 if (game_status == GAME_MODE_MAIN)
1659 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1660 is_string_suffix(cheat_input, ":ist"))
1662 InsertSolutionTape();
1664 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1665 is_string_suffix(cheat_input, ":rg"))
1667 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1670 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1671 is_string_suffix(cheat_input, ":rs"))
1673 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1676 else if (is_string_suffix(cheat_input, ":reload-music") ||
1677 is_string_suffix(cheat_input, ":rm"))
1679 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1682 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1683 is_string_suffix(cheat_input, ":ra"))
1685 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1686 1 << ARTWORK_TYPE_SOUNDS |
1687 1 << ARTWORK_TYPE_MUSIC);
1690 else if (is_string_suffix(cheat_input, ":dump-level") ||
1691 is_string_suffix(cheat_input, ":dl"))
1695 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1696 is_string_suffix(cheat_input, ":dt"))
1700 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1701 is_string_suffix(cheat_input, ":ft"))
1703 /* fix single-player tapes that contain player input for more than one
1704 player (due to a bug in 3.3.1.2 and earlier versions), which results
1705 in playing levels with more than one player in multi-player mode,
1706 even though the tape was originally recorded in single-player mode */
1708 /* remove player input actions for all players but the first one */
1709 for (i = 1; i < MAX_PLAYERS; i++)
1710 tape.player_participates[i] = FALSE;
1712 tape.changed = TRUE;
1714 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1715 is_string_suffix(cheat_input, ":snl"))
1717 SaveNativeLevel(&level);
1719 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1720 is_string_suffix(cheat_input, ":fps"))
1722 global.show_frames_per_second = !global.show_frames_per_second;
1725 else if (game_status == GAME_MODE_PLAYING)
1728 if (is_string_suffix(cheat_input, ".q"))
1729 DEBUG_SetMaximumDynamite();
1732 else if (game_status == GAME_MODE_EDITOR)
1734 if (is_string_suffix(cheat_input, ":dump-brush") ||
1735 is_string_suffix(cheat_input, ":DB"))
1739 else if (is_string_suffix(cheat_input, ":DDB"))
1746 void HandleKeysDebug(Key key)
1751 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1753 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1755 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1757 if (key == setup.debug.frame_delay_key[i] &&
1758 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1760 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1761 setup.debug.frame_delay[i] : setup.game_frame_delay);
1763 if (!setup.debug.frame_delay_game_only)
1764 MenuFrameDelay = GameFrameDelay;
1766 SetVideoFrameDelay(GameFrameDelay);
1768 if (GameFrameDelay > ONE_SECOND_DELAY)
1769 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1770 else if (GameFrameDelay != 0)
1771 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1772 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1773 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1775 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1782 if (game_status == GAME_MODE_PLAYING)
1786 options.debug = !options.debug;
1788 Error(ERR_DEBUG, "debug mode %s",
1789 (options.debug ? "enabled" : "disabled"));
1791 else if (key == KSYM_v)
1793 Error(ERR_DEBUG, "currently using game engine version %d",
1794 game.engine_version);
1800 void HandleKey(Key key, int key_status)
1802 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1803 static boolean ignore_repeated_key = FALSE;
1804 static struct SetupKeyboardInfo ski;
1805 static struct SetupShortcutInfo ssi;
1814 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1815 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1816 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1817 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1818 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1819 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1824 #if defined(TARGET_SDL2)
1825 /* map special keys (media keys / remote control buttons) to default keys */
1826 if (key == KSYM_PlayPause)
1828 else if (key == KSYM_Select)
1832 HandleSpecialGameControllerKeys(key, key_status);
1834 if (game_status == GAME_MODE_PLAYING)
1836 /* only needed for single-step tape recording mode */
1837 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1840 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1842 byte key_action = 0;
1844 if (setup.input[pnr].use_joystick)
1847 ski = setup.input[pnr].key;
1849 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1850 if (key == *key_info[i].key_custom)
1851 key_action |= key_info[i].action;
1853 /* use combined snap+direction keys for the first player only */
1856 ssi = setup.shortcut;
1858 for (i = 0; i < NUM_DIRECTIONS; i++)
1859 if (key == *key_info[i].key_snap)
1860 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1863 if (key_status == KEY_PRESSED)
1864 stored_player[pnr].action |= key_action;
1866 stored_player[pnr].action &= ~key_action;
1868 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1870 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1872 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1874 /* if snap key already pressed, keep pause mode when releasing */
1875 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1876 has_snapped[pnr] = TRUE;
1878 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1880 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1882 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1883 getRedDiskReleaseFlag_SP() == 0)
1885 /* add a single inactive frame before dropping starts */
1886 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1887 stored_player[pnr].force_dropping = TRUE;
1890 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1892 /* if snap key was pressed without direction, leave pause mode */
1893 if (!has_snapped[pnr])
1894 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1896 has_snapped[pnr] = FALSE;
1899 else if (tape.recording && tape.pausing && !tape.use_mouse)
1901 /* prevent key release events from un-pausing a paused game */
1902 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1903 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1906 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1907 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1913 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1914 if (key == key_info[i].key_default)
1915 joy |= key_info[i].action;
1920 if (key_status == KEY_PRESSED)
1921 key_joystick_mapping |= joy;
1923 key_joystick_mapping &= ~joy;
1928 if (game_status != GAME_MODE_PLAYING)
1929 key_joystick_mapping = 0;
1931 if (key_status == KEY_RELEASED)
1933 // reset flag to ignore repeated "key pressed" events after key release
1934 ignore_repeated_key = FALSE;
1939 if ((key == KSYM_F11 ||
1940 ((key == KSYM_Return ||
1941 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1942 video.fullscreen_available &&
1943 !ignore_repeated_key)
1945 setup.fullscreen = !setup.fullscreen;
1947 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1949 if (game_status == GAME_MODE_SETUP)
1950 RedrawSetupScreenAfterFullscreenToggle();
1952 // set flag to ignore repeated "key pressed" events
1953 ignore_repeated_key = TRUE;
1958 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1959 key == KSYM_minus || key == KSYM_KP_Subtract ||
1960 key == KSYM_plus || key == KSYM_KP_Add ||
1961 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1962 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1963 video.window_scaling_available &&
1964 !video.fullscreen_enabled)
1966 if (key == KSYM_0 || key == KSYM_KP_0)
1967 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1968 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1969 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1971 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1973 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1974 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1975 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1976 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1978 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1980 if (game_status == GAME_MODE_SETUP)
1981 RedrawSetupScreenAfterFullscreenToggle();
1986 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
1987 key == KSYM_Return ||
1988 key == KSYM_Escape)))
1990 /* do not handle this key event anymore */
1991 if (key != KSYM_Escape) /* always allow ESC key to be handled */
1995 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
1996 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2003 if (game_status == GAME_MODE_MAIN &&
2004 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2006 StartGameActions(options.network, setup.autorecord, level.random_seed);
2011 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2013 if (key == setup.shortcut.save_game)
2015 else if (key == setup.shortcut.load_game)
2017 else if (key == setup.shortcut.toggle_pause)
2018 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2020 HandleTapeButtonKeys(key);
2021 HandleSoundButtonKeys(key);
2024 if (game_status == GAME_MODE_PLAYING && !network_playing)
2026 int centered_player_nr_next = -999;
2028 if (key == setup.shortcut.focus_player_all)
2029 centered_player_nr_next = -1;
2031 for (i = 0; i < MAX_PLAYERS; i++)
2032 if (key == setup.shortcut.focus_player[i])
2033 centered_player_nr_next = i;
2035 if (centered_player_nr_next != -999)
2037 game.centered_player_nr_next = centered_player_nr_next;
2038 game.set_centered_player = TRUE;
2042 tape.centered_player_nr_next = game.centered_player_nr_next;
2043 tape.set_centered_player = TRUE;
2048 HandleKeysSpecial(key);
2050 if (HandleGadgetsKeyInput(key))
2052 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2053 key = KSYM_UNDEFINED;
2056 switch (game_status)
2058 case GAME_MODE_PSEUDO_TYPENAME:
2059 HandleTypeName(0, key);
2062 case GAME_MODE_TITLE:
2063 case GAME_MODE_MAIN:
2064 case GAME_MODE_LEVELS:
2065 case GAME_MODE_LEVELNR:
2066 case GAME_MODE_SETUP:
2067 case GAME_MODE_INFO:
2068 case GAME_MODE_SCORES:
2073 if (game_status == GAME_MODE_TITLE)
2074 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2075 else if (game_status == GAME_MODE_MAIN)
2076 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2077 else if (game_status == GAME_MODE_LEVELS)
2078 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2079 else if (game_status == GAME_MODE_LEVELNR)
2080 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2081 else if (game_status == GAME_MODE_SETUP)
2082 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2083 else if (game_status == GAME_MODE_INFO)
2084 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2085 else if (game_status == GAME_MODE_SCORES)
2086 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2090 if (game_status != GAME_MODE_MAIN)
2091 FadeSkipNextFadeIn();
2093 if (game_status == GAME_MODE_TITLE)
2094 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2095 else if (game_status == GAME_MODE_LEVELS)
2096 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2097 else if (game_status == GAME_MODE_LEVELNR)
2098 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2099 else if (game_status == GAME_MODE_SETUP)
2100 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2101 else if (game_status == GAME_MODE_INFO)
2102 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2103 else if (game_status == GAME_MODE_SCORES)
2104 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2108 if (game_status == GAME_MODE_LEVELS)
2109 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2110 else if (game_status == GAME_MODE_LEVELNR)
2111 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2112 else if (game_status == GAME_MODE_SETUP)
2113 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2114 else if (game_status == GAME_MODE_INFO)
2115 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2116 else if (game_status == GAME_MODE_SCORES)
2117 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2120 case KSYM_Page_Down:
2121 if (game_status == GAME_MODE_LEVELS)
2122 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2123 else if (game_status == GAME_MODE_LEVELNR)
2124 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2125 else if (game_status == GAME_MODE_SETUP)
2126 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2127 else if (game_status == GAME_MODE_INFO)
2128 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2129 else if (game_status == GAME_MODE_SCORES)
2130 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2138 case GAME_MODE_EDITOR:
2139 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2140 HandleLevelEditorKeyInput(key);
2143 case GAME_MODE_PLAYING:
2148 RequestQuitGame(setup.ask_on_escape);
2158 if (key == KSYM_Escape)
2160 SetGameStatus(GAME_MODE_MAIN);
2168 HandleKeysDebug(key);
2171 void HandleNoEvent()
2173 HandleMouseCursor();
2175 switch (game_status)
2177 case GAME_MODE_PLAYING:
2178 HandleButtonOrFinger(-1, -1, -1);
2183 void HandleEventActions()
2185 // if (button_status && game_status != GAME_MODE_PLAYING)
2186 if (button_status && (game_status != GAME_MODE_PLAYING ||
2188 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2190 HandleButton(0, 0, button_status, -button_status);
2197 #if defined(NETWORK_AVALIABLE)
2198 if (options.network)
2202 switch (game_status)
2204 case GAME_MODE_MAIN:
2205 DrawPreviewLevelAnimation();
2208 case GAME_MODE_EDITOR:
2209 HandleLevelEditorIdle();
2217 static void HandleTileCursor(int dx, int dy, int button)
2220 ClearPlayerMouseAction();
2227 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2228 (dx < 0 ? MB_LEFTBUTTON :
2229 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2231 else if (!tile_cursor.moving)
2233 int old_xpos = tile_cursor.xpos;
2234 int old_ypos = tile_cursor.ypos;
2235 int new_xpos = old_xpos;
2236 int new_ypos = old_ypos;
2238 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2239 new_xpos = old_xpos + dx;
2241 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2242 new_ypos = old_ypos + dy;
2244 SetTileCursorTargetXY(new_xpos, new_ypos);
2248 static int HandleJoystickForAllPlayers()
2252 boolean no_joysticks_configured = TRUE;
2253 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2254 static byte joy_action_last[MAX_PLAYERS];
2256 for (i = 0; i < MAX_PLAYERS; i++)
2257 if (setup.input[i].use_joystick)
2258 no_joysticks_configured = FALSE;
2260 /* if no joysticks configured, map connected joysticks to players */
2261 if (no_joysticks_configured)
2262 use_as_joystick_nr = TRUE;
2264 for (i = 0; i < MAX_PLAYERS; i++)
2266 byte joy_action = 0;
2268 joy_action = JoystickExt(i, use_as_joystick_nr);
2269 result |= joy_action;
2271 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2272 joy_action != joy_action_last[i])
2273 stored_player[i].action = joy_action;
2275 joy_action_last[i] = joy_action;
2281 void HandleJoystick()
2283 static unsigned int joytest_delay = 0;
2284 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2285 static int joytest_last = 0;
2286 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2287 int delay_value = GADGET_FRAME_DELAY;
2288 int joystick = HandleJoystickForAllPlayers();
2289 int keyboard = key_joystick_mapping;
2290 int joy = (joystick | keyboard);
2291 int joytest = joystick;
2292 int left = joy & JOY_LEFT;
2293 int right = joy & JOY_RIGHT;
2294 int up = joy & JOY_UP;
2295 int down = joy & JOY_DOWN;
2296 int button = joy & JOY_BUTTON;
2297 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2298 int dx = (left ? -1 : right ? 1 : 0);
2299 int dy = (up ? -1 : down ? 1 : 0);
2300 boolean use_delay_value_first = (joytest != joytest_last);
2302 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2304 /* do not handle this button event anymore */
2308 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2310 if (game_status == GAME_MODE_PLAYING)
2312 // when playing MM style levels, also use delay for keyboard events
2313 joytest |= keyboard;
2315 // only use first delay value for new events, but not for changed events
2316 use_delay_value_first = (!joytest != !joytest_last);
2318 // only use delay after the initial keyboard event
2322 // for any joystick or keyboard event, enable playfield tile cursor
2323 if (dx || dy || button)
2324 SetTileCursorEnabled(TRUE);
2327 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2329 /* delay joystick/keyboard actions if axes/keys continually pressed */
2330 newbutton = dx = dy = 0;
2334 /* first start with longer delay, then continue with shorter delay */
2335 joytest_delay_value =
2336 (use_delay_value_first ? delay_value_first : delay_value);
2339 joytest_last = joytest;
2341 switch (game_status)
2343 case GAME_MODE_TITLE:
2344 case GAME_MODE_MAIN:
2345 case GAME_MODE_LEVELS:
2346 case GAME_MODE_LEVELNR:
2347 case GAME_MODE_SETUP:
2348 case GAME_MODE_INFO:
2349 case GAME_MODE_SCORES:
2351 if (game_status == GAME_MODE_TITLE)
2352 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2353 else if (game_status == GAME_MODE_MAIN)
2354 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2355 else if (game_status == GAME_MODE_LEVELS)
2356 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2357 else if (game_status == GAME_MODE_LEVELNR)
2358 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2359 else if (game_status == GAME_MODE_SETUP)
2360 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2361 else if (game_status == GAME_MODE_INFO)
2362 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2363 else if (game_status == GAME_MODE_SCORES)
2364 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2369 case GAME_MODE_PLAYING:
2371 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2372 if (tape.playing || keyboard)
2373 newbutton = ((joy & JOY_BUTTON) != 0);
2376 if (newbutton && AllPlayersGone)
2383 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2385 if (joystick & JOY_ACTION)
2386 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2388 else if (tape.recording && tape.pausing && !tape.use_mouse)
2390 if (joystick & JOY_ACTION)
2391 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2394 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2395 HandleTileCursor(dx, dy, button);
2404 void HandleSpecialGameControllerButtons(Event *event)
2406 #if defined(TARGET_SDL2)
2407 switch (event->type)
2409 case SDL_CONTROLLERBUTTONDOWN:
2410 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2411 HandleKey(KSYM_space, KEY_PRESSED);
2412 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2413 HandleKey(KSYM_Escape, KEY_PRESSED);
2417 case SDL_CONTROLLERBUTTONUP:
2418 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2419 HandleKey(KSYM_space, KEY_RELEASED);
2420 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2421 HandleKey(KSYM_Escape, KEY_RELEASED);
2428 void HandleSpecialGameControllerKeys(Key key, int key_status)
2430 #if defined(TARGET_SDL2)
2431 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2432 int button = SDL_CONTROLLER_BUTTON_INVALID;
2434 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2435 if (key == KSYM_Rewind)
2436 button = SDL_CONTROLLER_BUTTON_A;
2437 else if (key == KSYM_FastForward || key == KSYM_Menu)
2438 button = SDL_CONTROLLER_BUTTON_B;
2440 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2444 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2445 SDL_CONTROLLERBUTTONUP);
2447 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2448 event.cbutton.button = button;
2449 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2452 HandleJoystickEvent(&event);