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 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
686 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
687 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
688 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
689 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
690 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
691 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
694 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
695 float event_x = (event->x);
696 float event_y = (event->y - ypos) / (1 - ypos);
697 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
698 event_y > 2.0 / 3.0 && event_y < 1 ?
699 setup.input[0].key.snap :
700 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
701 event_y > 2.0 / 3.0 && event_y < 1 ?
702 setup.input[0].key.drop :
703 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
704 event_y > 0 && event_y < 1.0 / 3.0 ?
705 setup.input[0].key.up :
706 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
707 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
708 setup.input[0].key.left :
709 event_x > 8.0 / 9.0 && event_x < 1 &&
710 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
711 setup.input[0].key.right :
712 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
713 event_y > 2.0 / 3.0 && event_y < 1 ?
714 setup.input[0].key.down :
717 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
719 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
723 // for any touch input event, enable overlay buttons (if activated)
724 SetOverlayEnabled(TRUE);
726 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
727 getKeyNameFromKey(key), key_status_name, event->fingerId);
729 if (key_status == KEY_PRESSED)
730 overlay.grid_button_action |= grid_button_action;
732 overlay.grid_button_action &= ~grid_button_action;
734 // check if we already know this touch event's finger id
735 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
737 if (touch_info[i].touched &&
738 touch_info[i].finger_id == event->fingerId)
740 // Error(ERR_DEBUG, "MARK 1: %d", i);
746 if (i >= NUM_TOUCH_FINGERS)
748 if (key_status == KEY_PRESSED)
750 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
752 // unknown finger id -- get new, empty slot, if available
753 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755 if (touch_info[i].counter < oldest_counter)
758 oldest_counter = touch_info[i].counter;
760 // Error(ERR_DEBUG, "MARK 2: %d", i);
763 if (!touch_info[i].touched)
765 // Error(ERR_DEBUG, "MARK 3: %d", i);
771 if (i >= NUM_TOUCH_FINGERS)
773 // all slots allocated -- use oldest slot
776 // Error(ERR_DEBUG, "MARK 4: %d", i);
781 // release of previously unknown key (should not happen)
783 if (key != KSYM_UNDEFINED)
785 HandleKey(key, KEY_RELEASED);
787 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
788 getKeyNameFromKey(key), "KEY_RELEASED", i);
793 if (i < NUM_TOUCH_FINGERS)
795 if (key_status == KEY_PRESSED)
797 if (touch_info[i].key != key)
799 if (touch_info[i].key != KSYM_UNDEFINED)
801 HandleKey(touch_info[i].key, KEY_RELEASED);
803 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
804 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
807 if (key != KSYM_UNDEFINED)
809 HandleKey(key, KEY_PRESSED);
811 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
812 getKeyNameFromKey(key), "KEY_PRESSED", i);
816 touch_info[i].touched = TRUE;
817 touch_info[i].finger_id = event->fingerId;
818 touch_info[i].counter = Counter();
819 touch_info[i].key = key;
823 if (touch_info[i].key != KSYM_UNDEFINED)
825 HandleKey(touch_info[i].key, KEY_RELEASED);
827 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
828 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
831 touch_info[i].touched = FALSE;
832 touch_info[i].finger_id = 0;
833 touch_info[i].counter = 0;
834 touch_info[i].key = 0;
839 void HandleFingerEvent_WipeGestures(FingerEvent *event)
841 static Key motion_key_x = KSYM_UNDEFINED;
842 static Key motion_key_y = KSYM_UNDEFINED;
843 static Key button_key = KSYM_UNDEFINED;
844 static float motion_x1, motion_y1;
845 static float button_x1, button_y1;
846 static SDL_FingerID motion_id = -1;
847 static SDL_FingerID button_id = -1;
848 int move_trigger_distance_percent = setup.touch.move_distance;
849 int drop_trigger_distance_percent = setup.touch.drop_distance;
850 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
851 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
852 float event_x = event->x;
853 float event_y = event->y;
855 if (event->type == EVENT_FINGERPRESS)
857 if (event_x > 1.0 / 3.0)
861 motion_id = event->fingerId;
866 motion_key_x = KSYM_UNDEFINED;
867 motion_key_y = KSYM_UNDEFINED;
869 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
875 button_id = event->fingerId;
880 button_key = setup.input[0].key.snap;
882 HandleKey(button_key, KEY_PRESSED);
884 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
887 else if (event->type == EVENT_FINGERRELEASE)
889 if (event->fingerId == motion_id)
893 if (motion_key_x != KSYM_UNDEFINED)
894 HandleKey(motion_key_x, KEY_RELEASED);
895 if (motion_key_y != KSYM_UNDEFINED)
896 HandleKey(motion_key_y, KEY_RELEASED);
898 motion_key_x = KSYM_UNDEFINED;
899 motion_key_y = KSYM_UNDEFINED;
901 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
903 else if (event->fingerId == button_id)
907 if (button_key != KSYM_UNDEFINED)
908 HandleKey(button_key, KEY_RELEASED);
910 button_key = KSYM_UNDEFINED;
912 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
915 else if (event->type == EVENT_FINGERMOTION)
917 if (event->fingerId == motion_id)
919 float distance_x = ABS(event_x - motion_x1);
920 float distance_y = ABS(event_y - motion_y1);
921 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
922 event_x > motion_x1 ? setup.input[0].key.right :
924 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
925 event_y > motion_y1 ? setup.input[0].key.down :
928 if (distance_x < move_trigger_distance / 2 ||
929 distance_x < distance_y)
930 new_motion_key_x = KSYM_UNDEFINED;
932 if (distance_y < move_trigger_distance / 2 ||
933 distance_y < distance_x)
934 new_motion_key_y = KSYM_UNDEFINED;
936 if (distance_x > move_trigger_distance ||
937 distance_y > move_trigger_distance)
939 if (new_motion_key_x != motion_key_x)
941 if (motion_key_x != KSYM_UNDEFINED)
942 HandleKey(motion_key_x, KEY_RELEASED);
943 if (new_motion_key_x != KSYM_UNDEFINED)
944 HandleKey(new_motion_key_x, KEY_PRESSED);
947 if (new_motion_key_y != motion_key_y)
949 if (motion_key_y != KSYM_UNDEFINED)
950 HandleKey(motion_key_y, KEY_RELEASED);
951 if (new_motion_key_y != KSYM_UNDEFINED)
952 HandleKey(new_motion_key_y, KEY_PRESSED);
958 motion_key_x = new_motion_key_x;
959 motion_key_y = new_motion_key_y;
961 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
964 else if (event->fingerId == button_id)
966 float distance_x = ABS(event_x - button_x1);
967 float distance_y = ABS(event_y - button_y1);
969 if (distance_x < drop_trigger_distance / 2 &&
970 distance_y > drop_trigger_distance)
972 if (button_key == setup.input[0].key.snap)
973 HandleKey(button_key, KEY_RELEASED);
978 button_key = setup.input[0].key.drop;
980 HandleKey(button_key, KEY_PRESSED);
982 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
988 void HandleFingerEvent(FingerEvent *event)
990 #if DEBUG_EVENTS_FINGER
991 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
992 event->type == EVENT_FINGERPRESS ? "pressed" :
993 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
997 event->dx, event->dy,
1001 if (game_status != GAME_MODE_PLAYING)
1004 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1006 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1007 local_player->mouse_action.button_hint =
1008 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1009 event->x < 0.5 ? MB_LEFTBUTTON :
1010 event->x > 0.5 ? MB_RIGHTBUTTON :
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1017 HandleFingerEvent_VirtualButtons(event);
1018 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1019 HandleFingerEvent_WipeGestures(event);
1024 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1026 static int old_mx = 0, old_my = 0;
1027 static int last_button = MB_LEFTBUTTON;
1028 static boolean touched = FALSE;
1029 static boolean tapped = FALSE;
1031 // screen tile was tapped (but finger not touching the screen anymore)
1032 // (this point will also be reached without receiving a touch event)
1033 if (tapped && !touched)
1035 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1040 // stop here if this function was not triggered by a touch event
1044 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1046 // finger started touching the screen
1056 ClearPlayerMouseAction();
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1061 else if (button == MB_RELEASED && touched)
1063 // finger stopped touching the screen
1068 SetPlayerMouseAction(old_mx, old_my, last_button);
1070 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1072 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1077 // finger moved while touching the screen
1079 int old_x = getLevelFromScreenX(old_mx);
1080 int old_y = getLevelFromScreenY(old_my);
1081 int new_x = getLevelFromScreenX(mx);
1082 int new_y = getLevelFromScreenY(my);
1084 if (new_x != old_x || new_y != old_y)
1089 // finger moved left or right from (horizontal) starting position
1091 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1093 SetPlayerMouseAction(old_mx, old_my, button_nr);
1095 last_button = button_nr;
1097 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1101 // finger stays at or returned to (horizontal) starting position
1103 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1110 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1112 static int old_mx = 0, old_my = 0;
1113 static int last_button = MB_LEFTBUTTON;
1114 static boolean touched = FALSE;
1115 static boolean tapped = FALSE;
1117 // screen tile was tapped (but finger not touching the screen anymore)
1118 // (this point will also be reached without receiving a touch event)
1119 if (tapped && !touched)
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1126 // stop here if this function was not triggered by a touch event
1130 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1132 // finger started touching the screen
1142 ClearPlayerMouseAction();
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1147 else if (button == MB_RELEASED && touched)
1149 // finger stopped touching the screen
1154 SetPlayerMouseAction(old_mx, old_my, last_button);
1156 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1158 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1163 // finger moved while touching the screen
1165 int old_x = getLevelFromScreenX(old_mx);
1166 int old_y = getLevelFromScreenY(old_my);
1167 int new_x = getLevelFromScreenX(mx);
1168 int new_y = getLevelFromScreenY(my);
1170 if (new_x != old_x || new_y != old_y)
1172 // finger moved away from starting position
1174 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1176 // quickly alternate between clicking and releasing for maximum speed
1177 if (FrameCounter % 2 == 0)
1178 button_nr = MB_RELEASED;
1180 SetPlayerMouseAction(old_mx, old_my, button_nr);
1183 last_button = button_nr;
1187 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1191 // finger stays at or returned to starting position
1193 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1195 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1200 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1202 static int old_mx = 0, old_my = 0;
1203 static Key motion_key_x = KSYM_UNDEFINED;
1204 static Key motion_key_y = KSYM_UNDEFINED;
1205 static boolean touched = FALSE;
1206 static boolean started_on_player = FALSE;
1207 static boolean player_is_dropping = FALSE;
1208 static int player_drop_count = 0;
1209 static int last_player_x = -1;
1210 static int last_player_y = -1;
1212 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1221 started_on_player = FALSE;
1222 player_is_dropping = FALSE;
1223 player_drop_count = 0;
1227 motion_key_x = KSYM_UNDEFINED;
1228 motion_key_y = KSYM_UNDEFINED;
1230 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1233 else if (button == MB_RELEASED && touched)
1240 if (motion_key_x != KSYM_UNDEFINED)
1241 HandleKey(motion_key_x, KEY_RELEASED);
1242 if (motion_key_y != KSYM_UNDEFINED)
1243 HandleKey(motion_key_y, KEY_RELEASED);
1245 if (started_on_player)
1247 if (player_is_dropping)
1249 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1251 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1255 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1257 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1261 motion_key_x = KSYM_UNDEFINED;
1262 motion_key_y = KSYM_UNDEFINED;
1264 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1269 int src_x = local_player->jx;
1270 int src_y = local_player->jy;
1271 int dst_x = getLevelFromScreenX(old_mx);
1272 int dst_y = getLevelFromScreenY(old_my);
1273 int dx = dst_x - src_x;
1274 int dy = dst_y - src_y;
1275 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1276 dx > 0 ? setup.input[0].key.right :
1278 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1279 dy > 0 ? setup.input[0].key.down :
1282 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1283 (last_player_x != local_player->jx ||
1284 last_player_y != local_player->jy))
1286 // in case of asymmetric diagonal movement, use "preferred" direction
1288 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1291 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1293 local_player->last_move_dir = last_move_dir;
1295 // (required to prevent accidentally forcing direction for next movement)
1296 last_player_x = local_player->jx;
1297 last_player_y = local_player->jy;
1300 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1302 started_on_player = TRUE;
1303 player_drop_count = getPlayerInventorySize(0);
1304 player_is_dropping = (player_drop_count > 0);
1306 if (player_is_dropping)
1308 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1310 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1314 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1316 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1319 else if (dx != 0 || dy != 0)
1321 if (player_is_dropping &&
1322 player_drop_count == getPlayerInventorySize(0))
1324 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1327 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1329 player_is_dropping = FALSE;
1333 if (new_motion_key_x != motion_key_x)
1335 Error(ERR_DEBUG, "---------- %s %s ----------",
1336 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1337 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1339 if (motion_key_x != KSYM_UNDEFINED)
1340 HandleKey(motion_key_x, KEY_RELEASED);
1341 if (new_motion_key_x != KSYM_UNDEFINED)
1342 HandleKey(new_motion_key_x, KEY_PRESSED);
1345 if (new_motion_key_y != motion_key_y)
1347 Error(ERR_DEBUG, "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1351 if (motion_key_y != KSYM_UNDEFINED)
1352 HandleKey(motion_key_y, KEY_RELEASED);
1353 if (new_motion_key_y != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_y, KEY_PRESSED);
1357 motion_key_x = new_motion_key_x;
1358 motion_key_y = new_motion_key_y;
1362 static void HandleButtonOrFinger(int mx, int my, int button)
1364 if (game_status != GAME_MODE_PLAYING)
1367 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1369 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1370 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1372 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1376 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger(mx, my, button);
1381 #if defined(TARGET_SDL2)
1383 static boolean checkTextInputKeyModState()
1385 // when playing, only handle raw key events and ignore text input
1386 if (game_status == GAME_MODE_PLAYING)
1389 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1392 void HandleTextEvent(TextEvent *event)
1394 char *text = event->text;
1395 Key key = getKeyFromKeyName(text);
1397 #if DEBUG_EVENTS_TEXT
1398 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1401 text[0], (int)(text[0]),
1403 getKeyNameFromKey(key),
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408 // non-mobile devices: only handle key input with modifier keys pressed here
1409 // (every other key input is handled directly as physical key input event)
1410 if (!checkTextInputKeyModState())
1414 // process text input as "classic" (with uppercase etc.) key input event
1415 HandleKey(key, KEY_PRESSED);
1416 HandleKey(key, KEY_RELEASED);
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1421 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1425 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1433 void HandleKeyEvent(KeyEvent *event)
1435 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1436 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1437 Key key = GetEventKey(event, with_modifiers);
1438 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1440 #if DEBUG_EVENTS_KEY
1441 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1442 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1443 event->keysym.scancode,
1448 getKeyNameFromKey(key));
1451 #if defined(PLATFORM_ANDROID)
1452 if (key == KSYM_Back)
1454 // always map the "back" button to the "escape" key on Android devices
1459 // for any key event other than "back" button, disable overlay buttons
1460 SetOverlayEnabled(FALSE);
1464 HandleKeyModState(keymod, key_status);
1466 #if defined(TARGET_SDL2)
1467 // only handle raw key input without text modifier keys pressed
1468 if (!checkTextInputKeyModState())
1469 HandleKey(key, key_status);
1471 HandleKey(key, key_status);
1475 void HandleFocusEvent(FocusChangeEvent *event)
1477 static int old_joystick_status = -1;
1479 if (event->type == EVENT_FOCUSOUT)
1481 KeyboardAutoRepeatOn();
1482 old_joystick_status = joystick.status;
1483 joystick.status = JOYSTICK_NOT_AVAILABLE;
1485 ClearPlayerAction();
1487 else if (event->type == EVENT_FOCUSIN)
1489 /* When there are two Rocks'n'Diamonds windows which overlap and
1490 the player moves the pointer from one game window to the other,
1491 a 'FocusOut' event is generated for the window the pointer is
1492 leaving and a 'FocusIn' event is generated for the window the
1493 pointer is entering. In some cases, it can happen that the
1494 'FocusIn' event is handled by the one game process before the
1495 'FocusOut' event by the other game process. In this case the
1496 X11 environment would end up with activated keyboard auto repeat,
1497 because unfortunately this is a global setting and not (which
1498 would be far better) set for each X11 window individually.
1499 The effect would be keyboard auto repeat while playing the game
1500 (game_status == GAME_MODE_PLAYING), which is not desired.
1501 To avoid this special case, we just wait 1/10 second before
1502 processing the 'FocusIn' event.
1505 if (game_status == GAME_MODE_PLAYING)
1508 KeyboardAutoRepeatOffUnlessAutoplay();
1511 if (old_joystick_status != -1)
1512 joystick.status = old_joystick_status;
1516 void HandleClientMessageEvent(ClientMessageEvent *event)
1518 if (CheckCloseWindowEvent(event))
1522 void HandleWindowManagerEvent(Event *event)
1524 #if defined(TARGET_SDL)
1525 SDLHandleWindowManagerEvent(event);
1529 void HandleButton(int mx, int my, int button, int button_nr)
1531 static int old_mx = 0, old_my = 0;
1532 boolean button_hold = FALSE;
1533 boolean handle_gadgets = TRUE;
1539 button_nr = -button_nr;
1548 #if defined(PLATFORM_ANDROID)
1549 // when playing, only handle gadgets when using "follow finger" controls
1550 // or when using touch controls in combination with the MM game engine
1552 (game_status != GAME_MODE_PLAYING ||
1553 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1554 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1557 if (handle_gadgets && HandleGadgets(mx, my, button))
1559 /* do not handle this button event anymore */
1560 mx = my = -32; /* force mouse event to be outside screen tiles */
1563 if (HandleGlobalAnimClicks(mx, my, button))
1565 /* do not handle this button event anymore */
1566 return; /* force mouse event not to be handled at all */
1569 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1572 /* do not use scroll wheel button events for anything other than gadgets */
1573 if (IS_WHEEL_BUTTON(button_nr))
1576 switch (game_status)
1578 case GAME_MODE_TITLE:
1579 HandleTitleScreen(mx, my, 0, 0, button);
1582 case GAME_MODE_MAIN:
1583 HandleMainMenu(mx, my, 0, 0, button);
1586 case GAME_MODE_PSEUDO_TYPENAME:
1587 HandleTypeName(0, KSYM_Return);
1590 case GAME_MODE_LEVELS:
1591 HandleChooseLevelSet(mx, my, 0, 0, button);
1594 case GAME_MODE_LEVELNR:
1595 HandleChooseLevelNr(mx, my, 0, 0, button);
1598 case GAME_MODE_SCORES:
1599 HandleHallOfFame(0, 0, 0, 0, button);
1602 case GAME_MODE_EDITOR:
1603 HandleLevelEditorIdle();
1606 case GAME_MODE_INFO:
1607 HandleInfoScreen(mx, my, 0, 0, button);
1610 case GAME_MODE_SETUP:
1611 HandleSetupScreen(mx, my, 0, 0, button);
1614 case GAME_MODE_PLAYING:
1615 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1616 HandleButtonOrFinger(mx, my, button);
1618 SetPlayerMouseAction(mx, my, button);
1621 if (button == MB_PRESSED && !motion_status && !button_hold &&
1622 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1623 DumpTileFromScreen(mx, my);
1633 static boolean is_string_suffix(char *string, char *suffix)
1635 int string_len = strlen(string);
1636 int suffix_len = strlen(suffix);
1638 if (suffix_len > string_len)
1641 return (strEqual(&string[string_len - suffix_len], suffix));
1644 #define MAX_CHEAT_INPUT_LEN 32
1646 static void HandleKeysSpecial(Key key)
1648 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1649 char letter = getCharFromKey(key);
1650 int cheat_input_len = strlen(cheat_input);
1656 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1658 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1659 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1661 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1664 cheat_input[cheat_input_len++] = letter;
1665 cheat_input[cheat_input_len] = '\0';
1667 #if DEBUG_EVENTS_KEY
1668 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1671 if (game_status == GAME_MODE_MAIN)
1673 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1674 is_string_suffix(cheat_input, ":ist"))
1676 InsertSolutionTape();
1678 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1679 is_string_suffix(cheat_input, ":rg"))
1681 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1684 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1685 is_string_suffix(cheat_input, ":rs"))
1687 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1690 else if (is_string_suffix(cheat_input, ":reload-music") ||
1691 is_string_suffix(cheat_input, ":rm"))
1693 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1696 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1697 is_string_suffix(cheat_input, ":ra"))
1699 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1700 1 << ARTWORK_TYPE_SOUNDS |
1701 1 << ARTWORK_TYPE_MUSIC);
1704 else if (is_string_suffix(cheat_input, ":dump-level") ||
1705 is_string_suffix(cheat_input, ":dl"))
1709 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1710 is_string_suffix(cheat_input, ":dt"))
1714 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1715 is_string_suffix(cheat_input, ":ft"))
1717 /* fix single-player tapes that contain player input for more than one
1718 player (due to a bug in 3.3.1.2 and earlier versions), which results
1719 in playing levels with more than one player in multi-player mode,
1720 even though the tape was originally recorded in single-player mode */
1722 /* remove player input actions for all players but the first one */
1723 for (i = 1; i < MAX_PLAYERS; i++)
1724 tape.player_participates[i] = FALSE;
1726 tape.changed = TRUE;
1728 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1729 is_string_suffix(cheat_input, ":snl"))
1731 SaveNativeLevel(&level);
1733 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1734 is_string_suffix(cheat_input, ":fps"))
1736 global.show_frames_per_second = !global.show_frames_per_second;
1739 else if (game_status == GAME_MODE_PLAYING)
1742 if (is_string_suffix(cheat_input, ".q"))
1743 DEBUG_SetMaximumDynamite();
1746 else if (game_status == GAME_MODE_EDITOR)
1748 if (is_string_suffix(cheat_input, ":dump-brush") ||
1749 is_string_suffix(cheat_input, ":DB"))
1753 else if (is_string_suffix(cheat_input, ":DDB"))
1760 void HandleKeysDebug(Key key)
1765 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1767 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1769 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1771 if (key == setup.debug.frame_delay_key[i] &&
1772 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1774 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1775 setup.debug.frame_delay[i] : setup.game_frame_delay);
1777 if (!setup.debug.frame_delay_game_only)
1778 MenuFrameDelay = GameFrameDelay;
1780 SetVideoFrameDelay(GameFrameDelay);
1782 if (GameFrameDelay > ONE_SECOND_DELAY)
1783 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1784 else if (GameFrameDelay != 0)
1785 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1786 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1787 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1789 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1796 if (game_status == GAME_MODE_PLAYING)
1800 options.debug = !options.debug;
1802 Error(ERR_DEBUG, "debug mode %s",
1803 (options.debug ? "enabled" : "disabled"));
1805 else if (key == KSYM_v)
1807 Error(ERR_DEBUG, "currently using game engine version %d",
1808 game.engine_version);
1814 void HandleKey(Key key, int key_status)
1816 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1817 static boolean ignore_repeated_key = FALSE;
1818 static struct SetupKeyboardInfo ski;
1819 static struct SetupShortcutInfo ssi;
1828 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1829 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1830 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1831 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1832 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1833 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1838 #if defined(TARGET_SDL2)
1839 /* map special keys (media keys / remote control buttons) to default keys */
1840 if (key == KSYM_PlayPause)
1842 else if (key == KSYM_Select)
1846 HandleSpecialGameControllerKeys(key, key_status);
1848 if (game_status == GAME_MODE_PLAYING)
1850 /* only needed for single-step tape recording mode */
1851 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1854 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1856 byte key_action = 0;
1858 if (setup.input[pnr].use_joystick)
1861 ski = setup.input[pnr].key;
1863 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1864 if (key == *key_info[i].key_custom)
1865 key_action |= key_info[i].action;
1867 /* use combined snap+direction keys for the first player only */
1870 ssi = setup.shortcut;
1872 for (i = 0; i < NUM_DIRECTIONS; i++)
1873 if (key == *key_info[i].key_snap)
1874 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1877 if (key_status == KEY_PRESSED)
1878 stored_player[pnr].action |= key_action;
1880 stored_player[pnr].action &= ~key_action;
1882 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1884 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1886 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1888 /* if snap key already pressed, keep pause mode when releasing */
1889 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1890 has_snapped[pnr] = TRUE;
1892 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1894 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1896 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1897 getRedDiskReleaseFlag_SP() == 0)
1899 /* add a single inactive frame before dropping starts */
1900 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1901 stored_player[pnr].force_dropping = TRUE;
1904 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1906 /* if snap key was pressed without direction, leave pause mode */
1907 if (!has_snapped[pnr])
1908 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1910 has_snapped[pnr] = FALSE;
1913 else if (tape.recording && tape.pausing && !tape.use_mouse)
1915 /* prevent key release events from un-pausing a paused game */
1916 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1917 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1920 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1921 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1927 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1928 if (key == key_info[i].key_default)
1929 joy |= key_info[i].action;
1934 if (key_status == KEY_PRESSED)
1935 key_joystick_mapping |= joy;
1937 key_joystick_mapping &= ~joy;
1942 if (game_status != GAME_MODE_PLAYING)
1943 key_joystick_mapping = 0;
1945 if (key_status == KEY_RELEASED)
1947 // reset flag to ignore repeated "key pressed" events after key release
1948 ignore_repeated_key = FALSE;
1953 if ((key == KSYM_F11 ||
1954 ((key == KSYM_Return ||
1955 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1956 video.fullscreen_available &&
1957 !ignore_repeated_key)
1959 setup.fullscreen = !setup.fullscreen;
1961 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1963 if (game_status == GAME_MODE_SETUP)
1964 RedrawSetupScreenAfterFullscreenToggle();
1966 // set flag to ignore repeated "key pressed" events
1967 ignore_repeated_key = TRUE;
1972 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1973 key == KSYM_minus || key == KSYM_KP_Subtract ||
1974 key == KSYM_plus || key == KSYM_KP_Add ||
1975 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1976 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1977 video.window_scaling_available &&
1978 !video.fullscreen_enabled)
1980 if (key == KSYM_0 || key == KSYM_KP_0)
1981 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1982 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1983 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1985 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1987 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1988 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1989 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1990 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1992 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1994 if (game_status == GAME_MODE_SETUP)
1995 RedrawSetupScreenAfterFullscreenToggle();
2000 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2001 key == KSYM_Return ||
2002 key == KSYM_Escape)))
2004 /* do not handle this key event anymore */
2005 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2009 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2010 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2017 if (game_status == GAME_MODE_MAIN &&
2018 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2020 StartGameActions(options.network, setup.autorecord, level.random_seed);
2025 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2027 if (key == setup.shortcut.save_game)
2029 else if (key == setup.shortcut.load_game)
2031 else if (key == setup.shortcut.toggle_pause)
2032 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2034 HandleTapeButtonKeys(key);
2035 HandleSoundButtonKeys(key);
2038 if (game_status == GAME_MODE_PLAYING && !network_playing)
2040 int centered_player_nr_next = -999;
2042 if (key == setup.shortcut.focus_player_all)
2043 centered_player_nr_next = -1;
2045 for (i = 0; i < MAX_PLAYERS; i++)
2046 if (key == setup.shortcut.focus_player[i])
2047 centered_player_nr_next = i;
2049 if (centered_player_nr_next != -999)
2051 game.centered_player_nr_next = centered_player_nr_next;
2052 game.set_centered_player = TRUE;
2056 tape.centered_player_nr_next = game.centered_player_nr_next;
2057 tape.set_centered_player = TRUE;
2062 HandleKeysSpecial(key);
2064 if (HandleGadgetsKeyInput(key))
2066 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2067 key = KSYM_UNDEFINED;
2070 switch (game_status)
2072 case GAME_MODE_PSEUDO_TYPENAME:
2073 HandleTypeName(0, key);
2076 case GAME_MODE_TITLE:
2077 case GAME_MODE_MAIN:
2078 case GAME_MODE_LEVELS:
2079 case GAME_MODE_LEVELNR:
2080 case GAME_MODE_SETUP:
2081 case GAME_MODE_INFO:
2082 case GAME_MODE_SCORES:
2087 if (game_status == GAME_MODE_TITLE)
2088 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2089 else if (game_status == GAME_MODE_MAIN)
2090 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2091 else if (game_status == GAME_MODE_LEVELS)
2092 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2093 else if (game_status == GAME_MODE_LEVELNR)
2094 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2095 else if (game_status == GAME_MODE_SETUP)
2096 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2097 else if (game_status == GAME_MODE_INFO)
2098 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2099 else if (game_status == GAME_MODE_SCORES)
2100 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2104 if (game_status != GAME_MODE_MAIN)
2105 FadeSkipNextFadeIn();
2107 if (game_status == GAME_MODE_TITLE)
2108 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2109 else if (game_status == GAME_MODE_LEVELS)
2110 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2111 else if (game_status == GAME_MODE_LEVELNR)
2112 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2113 else if (game_status == GAME_MODE_SETUP)
2114 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2115 else if (game_status == GAME_MODE_INFO)
2116 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2117 else if (game_status == GAME_MODE_SCORES)
2118 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2122 if (game_status == GAME_MODE_LEVELS)
2123 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2124 else if (game_status == GAME_MODE_LEVELNR)
2125 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2126 else if (game_status == GAME_MODE_SETUP)
2127 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2128 else if (game_status == GAME_MODE_INFO)
2129 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2130 else if (game_status == GAME_MODE_SCORES)
2131 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2134 case KSYM_Page_Down:
2135 if (game_status == GAME_MODE_LEVELS)
2136 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2137 else if (game_status == GAME_MODE_LEVELNR)
2138 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2139 else if (game_status == GAME_MODE_SETUP)
2140 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2141 else if (game_status == GAME_MODE_INFO)
2142 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2143 else if (game_status == GAME_MODE_SCORES)
2144 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2152 case GAME_MODE_EDITOR:
2153 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2154 HandleLevelEditorKeyInput(key);
2157 case GAME_MODE_PLAYING:
2162 RequestQuitGame(setup.ask_on_escape);
2172 if (key == KSYM_Escape)
2174 SetGameStatus(GAME_MODE_MAIN);
2182 HandleKeysDebug(key);
2185 void HandleNoEvent()
2187 HandleMouseCursor();
2189 switch (game_status)
2191 case GAME_MODE_PLAYING:
2192 HandleButtonOrFinger(-1, -1, -1);
2197 void HandleEventActions()
2199 // if (button_status && game_status != GAME_MODE_PLAYING)
2200 if (button_status && (game_status != GAME_MODE_PLAYING ||
2202 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2204 HandleButton(0, 0, button_status, -button_status);
2211 #if defined(NETWORK_AVALIABLE)
2212 if (options.network)
2216 switch (game_status)
2218 case GAME_MODE_MAIN:
2219 DrawPreviewLevelAnimation();
2222 case GAME_MODE_EDITOR:
2223 HandleLevelEditorIdle();
2231 static void HandleTileCursor(int dx, int dy, int button)
2234 ClearPlayerMouseAction();
2241 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2242 (dx < 0 ? MB_LEFTBUTTON :
2243 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2245 else if (!tile_cursor.moving)
2247 int old_xpos = tile_cursor.xpos;
2248 int old_ypos = tile_cursor.ypos;
2249 int new_xpos = old_xpos;
2250 int new_ypos = old_ypos;
2252 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2253 new_xpos = old_xpos + dx;
2255 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2256 new_ypos = old_ypos + dy;
2258 SetTileCursorTargetXY(new_xpos, new_ypos);
2262 static int HandleJoystickForAllPlayers()
2266 boolean no_joysticks_configured = TRUE;
2267 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2268 static byte joy_action_last[MAX_PLAYERS];
2270 for (i = 0; i < MAX_PLAYERS; i++)
2271 if (setup.input[i].use_joystick)
2272 no_joysticks_configured = FALSE;
2274 /* if no joysticks configured, map connected joysticks to players */
2275 if (no_joysticks_configured)
2276 use_as_joystick_nr = TRUE;
2278 for (i = 0; i < MAX_PLAYERS; i++)
2280 byte joy_action = 0;
2282 joy_action = JoystickExt(i, use_as_joystick_nr);
2283 result |= joy_action;
2285 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2286 joy_action != joy_action_last[i])
2287 stored_player[i].action = joy_action;
2289 joy_action_last[i] = joy_action;
2295 void HandleJoystick()
2297 static unsigned int joytest_delay = 0;
2298 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2299 static int joytest_last = 0;
2300 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2301 int delay_value = GADGET_FRAME_DELAY;
2302 int joystick = HandleJoystickForAllPlayers();
2303 int keyboard = key_joystick_mapping;
2304 int joy = (joystick | keyboard);
2305 int joytest = joystick;
2306 int left = joy & JOY_LEFT;
2307 int right = joy & JOY_RIGHT;
2308 int up = joy & JOY_UP;
2309 int down = joy & JOY_DOWN;
2310 int button = joy & JOY_BUTTON;
2311 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2312 int dx = (left ? -1 : right ? 1 : 0);
2313 int dy = (up ? -1 : down ? 1 : 0);
2314 boolean use_delay_value_first = (joytest != joytest_last);
2316 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2318 /* do not handle this button event anymore */
2322 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2324 if (game_status == GAME_MODE_PLAYING)
2326 // when playing MM style levels, also use delay for keyboard events
2327 joytest |= keyboard;
2329 // only use first delay value for new events, but not for changed events
2330 use_delay_value_first = (!joytest != !joytest_last);
2332 // only use delay after the initial keyboard event
2336 // for any joystick or keyboard event, enable playfield tile cursor
2337 if (dx || dy || button)
2338 SetTileCursorEnabled(TRUE);
2341 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2343 /* delay joystick/keyboard actions if axes/keys continually pressed */
2344 newbutton = dx = dy = 0;
2348 /* first start with longer delay, then continue with shorter delay */
2349 joytest_delay_value =
2350 (use_delay_value_first ? delay_value_first : delay_value);
2353 joytest_last = joytest;
2355 switch (game_status)
2357 case GAME_MODE_TITLE:
2358 case GAME_MODE_MAIN:
2359 case GAME_MODE_LEVELS:
2360 case GAME_MODE_LEVELNR:
2361 case GAME_MODE_SETUP:
2362 case GAME_MODE_INFO:
2363 case GAME_MODE_SCORES:
2365 if (game_status == GAME_MODE_TITLE)
2366 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2367 else if (game_status == GAME_MODE_MAIN)
2368 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2369 else if (game_status == GAME_MODE_LEVELS)
2370 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2371 else if (game_status == GAME_MODE_LEVELNR)
2372 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2373 else if (game_status == GAME_MODE_SETUP)
2374 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2375 else if (game_status == GAME_MODE_INFO)
2376 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2377 else if (game_status == GAME_MODE_SCORES)
2378 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2383 case GAME_MODE_PLAYING:
2385 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2386 if (tape.playing || keyboard)
2387 newbutton = ((joy & JOY_BUTTON) != 0);
2390 if (newbutton && AllPlayersGone)
2397 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2399 if (joystick & JOY_ACTION)
2400 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2402 else if (tape.recording && tape.pausing && !tape.use_mouse)
2404 if (joystick & JOY_ACTION)
2405 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2408 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2409 HandleTileCursor(dx, dy, button);
2418 void HandleSpecialGameControllerButtons(Event *event)
2420 #if defined(TARGET_SDL2)
2421 switch (event->type)
2423 case SDL_CONTROLLERBUTTONDOWN:
2424 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2425 HandleKey(KSYM_space, KEY_PRESSED);
2426 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2427 HandleKey(KSYM_Escape, KEY_PRESSED);
2431 case SDL_CONTROLLERBUTTONUP:
2432 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2433 HandleKey(KSYM_space, KEY_RELEASED);
2434 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2435 HandleKey(KSYM_Escape, KEY_RELEASED);
2442 void HandleSpecialGameControllerKeys(Key key, int key_status)
2444 #if defined(TARGET_SDL2)
2445 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2446 int button = SDL_CONTROLLER_BUTTON_INVALID;
2448 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2449 if (key == KSYM_Rewind)
2450 button = SDL_CONTROLLER_BUTTON_A;
2451 else if (key == KSYM_FastForward || key == KSYM_Menu)
2452 button = SDL_CONTROLLER_BUTTON_B;
2454 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2458 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2459 SDL_CONTROLLERBUTTONUP);
2461 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2462 event.cbutton.button = button;
2463 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2466 HandleJoystickEvent(&event);