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 setup.touch.grid_button[nr][x][y] = overlay.grid_button[x][y];
659 nr = GRID_ACTIVE_NR();
661 overlay.grid_xsize = setup.touch.grid_xsize[nr];
662 overlay.grid_ysize = setup.touch.grid_ysize[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] = setup.touch.grid_button[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)
687 int x = event->x * overlay.grid_xsize;
688 int y = event->y * overlay.grid_ysize;
689 int grid_button = overlay.grid_button[x][y];
690 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
691 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
692 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
693 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
694 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
695 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
698 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
699 float event_x = (event->x);
700 float event_y = (event->y - ypos) / (1 - ypos);
701 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
702 event_y > 2.0 / 3.0 && event_y < 1 ?
703 setup.input[0].key.snap :
704 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
705 event_y > 2.0 / 3.0 && event_y < 1 ?
706 setup.input[0].key.drop :
707 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
708 event_y > 0 && event_y < 1.0 / 3.0 ?
709 setup.input[0].key.up :
710 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
711 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
712 setup.input[0].key.left :
713 event_x > 8.0 / 9.0 && event_x < 1 &&
714 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
715 setup.input[0].key.right :
716 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
717 event_y > 2.0 / 3.0 && event_y < 1 ?
718 setup.input[0].key.down :
721 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
723 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
727 // for any touch input event, enable overlay buttons (if activated)
728 SetOverlayEnabled(TRUE);
730 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
731 getKeyNameFromKey(key), key_status_name, event->fingerId);
733 // check if we already know this touch event's finger id
734 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
736 if (touch_info[i].touched &&
737 touch_info[i].finger_id == event->fingerId)
739 // Error(ERR_DEBUG, "MARK 1: %d", i);
745 if (i >= NUM_TOUCH_FINGERS)
747 if (key_status == KEY_PRESSED)
749 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
751 // unknown finger id -- get new, empty slot, if available
752 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
754 if (touch_info[i].counter < oldest_counter)
757 oldest_counter = touch_info[i].counter;
759 // Error(ERR_DEBUG, "MARK 2: %d", i);
762 if (!touch_info[i].touched)
764 // Error(ERR_DEBUG, "MARK 3: %d", i);
770 if (i >= NUM_TOUCH_FINGERS)
772 // all slots allocated -- use oldest slot
775 // Error(ERR_DEBUG, "MARK 4: %d", i);
780 // release of previously unknown key (should not happen)
782 if (key != KSYM_UNDEFINED)
784 HandleKey(key, KEY_RELEASED);
786 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
787 getKeyNameFromKey(key), "KEY_RELEASED", i);
792 if (i < NUM_TOUCH_FINGERS)
794 if (key_status == KEY_PRESSED)
796 if (touch_info[i].key != key)
798 if (touch_info[i].key != KSYM_UNDEFINED)
800 HandleKey(touch_info[i].key, KEY_RELEASED);
802 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
803 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
806 if (key != KSYM_UNDEFINED)
808 HandleKey(key, KEY_PRESSED);
810 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
811 getKeyNameFromKey(key), "KEY_PRESSED", i);
815 touch_info[i].touched = TRUE;
816 touch_info[i].finger_id = event->fingerId;
817 touch_info[i].counter = Counter();
818 touch_info[i].key = key;
822 if (touch_info[i].key != KSYM_UNDEFINED)
824 HandleKey(touch_info[i].key, KEY_RELEASED);
826 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
827 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
830 touch_info[i].touched = FALSE;
831 touch_info[i].finger_id = 0;
832 touch_info[i].counter = 0;
833 touch_info[i].key = 0;
838 void HandleFingerEvent_WipeGestures(FingerEvent *event)
840 static Key motion_key_x = KSYM_UNDEFINED;
841 static Key motion_key_y = KSYM_UNDEFINED;
842 static Key button_key = KSYM_UNDEFINED;
843 static float motion_x1, motion_y1;
844 static float button_x1, button_y1;
845 static SDL_FingerID motion_id = -1;
846 static SDL_FingerID button_id = -1;
847 int move_trigger_distance_percent = setup.touch.move_distance;
848 int drop_trigger_distance_percent = setup.touch.drop_distance;
849 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
850 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
851 float event_x = event->x;
852 float event_y = event->y;
854 if (event->type == EVENT_FINGERPRESS)
856 if (event_x > 1.0 / 3.0)
860 motion_id = event->fingerId;
865 motion_key_x = KSYM_UNDEFINED;
866 motion_key_y = KSYM_UNDEFINED;
868 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
874 button_id = event->fingerId;
879 button_key = setup.input[0].key.snap;
881 HandleKey(button_key, KEY_PRESSED);
883 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
886 else if (event->type == EVENT_FINGERRELEASE)
888 if (event->fingerId == motion_id)
892 if (motion_key_x != KSYM_UNDEFINED)
893 HandleKey(motion_key_x, KEY_RELEASED);
894 if (motion_key_y != KSYM_UNDEFINED)
895 HandleKey(motion_key_y, KEY_RELEASED);
897 motion_key_x = KSYM_UNDEFINED;
898 motion_key_y = KSYM_UNDEFINED;
900 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
902 else if (event->fingerId == button_id)
906 if (button_key != KSYM_UNDEFINED)
907 HandleKey(button_key, KEY_RELEASED);
909 button_key = KSYM_UNDEFINED;
911 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
914 else if (event->type == EVENT_FINGERMOTION)
916 if (event->fingerId == motion_id)
918 float distance_x = ABS(event_x - motion_x1);
919 float distance_y = ABS(event_y - motion_y1);
920 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
921 event_x > motion_x1 ? setup.input[0].key.right :
923 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
924 event_y > motion_y1 ? setup.input[0].key.down :
927 if (distance_x < move_trigger_distance / 2 ||
928 distance_x < distance_y)
929 new_motion_key_x = KSYM_UNDEFINED;
931 if (distance_y < move_trigger_distance / 2 ||
932 distance_y < distance_x)
933 new_motion_key_y = KSYM_UNDEFINED;
935 if (distance_x > move_trigger_distance ||
936 distance_y > move_trigger_distance)
938 if (new_motion_key_x != motion_key_x)
940 if (motion_key_x != KSYM_UNDEFINED)
941 HandleKey(motion_key_x, KEY_RELEASED);
942 if (new_motion_key_x != KSYM_UNDEFINED)
943 HandleKey(new_motion_key_x, KEY_PRESSED);
946 if (new_motion_key_y != motion_key_y)
948 if (motion_key_y != KSYM_UNDEFINED)
949 HandleKey(motion_key_y, KEY_RELEASED);
950 if (new_motion_key_y != KSYM_UNDEFINED)
951 HandleKey(new_motion_key_y, KEY_PRESSED);
957 motion_key_x = new_motion_key_x;
958 motion_key_y = new_motion_key_y;
960 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
963 else if (event->fingerId == button_id)
965 float distance_x = ABS(event_x - button_x1);
966 float distance_y = ABS(event_y - button_y1);
968 if (distance_x < drop_trigger_distance / 2 &&
969 distance_y > drop_trigger_distance)
971 if (button_key == setup.input[0].key.snap)
972 HandleKey(button_key, KEY_RELEASED);
977 button_key = setup.input[0].key.drop;
979 HandleKey(button_key, KEY_PRESSED);
981 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
987 void HandleFingerEvent(FingerEvent *event)
989 #if DEBUG_EVENTS_FINGER
990 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
991 event->type == EVENT_FINGERPRESS ? "pressed" :
992 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
996 event->dx, event->dy,
1000 if (game_status != GAME_MODE_PLAYING)
1003 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1005 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1006 local_player->mouse_action.button_hint =
1007 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1008 event->x < 0.5 ? MB_LEFTBUTTON :
1009 event->x > 0.5 ? MB_RIGHTBUTTON :
1015 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1016 HandleFingerEvent_VirtualButtons(event);
1017 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1018 HandleFingerEvent_WipeGestures(event);
1023 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1025 static int old_mx = 0, old_my = 0;
1026 static int last_button = MB_LEFTBUTTON;
1027 static boolean touched = FALSE;
1028 static boolean tapped = FALSE;
1030 // screen tile was tapped (but finger not touching the screen anymore)
1031 // (this point will also be reached without receiving a touch event)
1032 if (tapped && !touched)
1034 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1039 // stop here if this function was not triggered by a touch event
1043 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1045 // finger started touching the screen
1055 ClearPlayerMouseAction();
1057 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1060 else if (button == MB_RELEASED && touched)
1062 // finger stopped touching the screen
1067 SetPlayerMouseAction(old_mx, old_my, last_button);
1069 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1071 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1076 // finger moved while touching the screen
1078 int old_x = getLevelFromScreenX(old_mx);
1079 int old_y = getLevelFromScreenY(old_my);
1080 int new_x = getLevelFromScreenX(mx);
1081 int new_y = getLevelFromScreenY(my);
1083 if (new_x != old_x || new_y != old_y)
1088 // finger moved left or right from (horizontal) starting position
1090 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1092 SetPlayerMouseAction(old_mx, old_my, button_nr);
1094 last_button = button_nr;
1096 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1100 // finger stays at or returned to (horizontal) starting position
1102 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1104 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1109 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1111 static int old_mx = 0, old_my = 0;
1112 static int last_button = MB_LEFTBUTTON;
1113 static boolean touched = FALSE;
1114 static boolean tapped = FALSE;
1116 // screen tile was tapped (but finger not touching the screen anymore)
1117 // (this point will also be reached without receiving a touch event)
1118 if (tapped && !touched)
1120 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1125 // stop here if this function was not triggered by a touch event
1129 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1131 // finger started touching the screen
1141 ClearPlayerMouseAction();
1143 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1146 else if (button == MB_RELEASED && touched)
1148 // finger stopped touching the screen
1153 SetPlayerMouseAction(old_mx, old_my, last_button);
1155 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1157 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1162 // finger moved while touching the screen
1164 int old_x = getLevelFromScreenX(old_mx);
1165 int old_y = getLevelFromScreenY(old_my);
1166 int new_x = getLevelFromScreenX(mx);
1167 int new_y = getLevelFromScreenY(my);
1169 if (new_x != old_x || new_y != old_y)
1171 // finger moved away from starting position
1173 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1175 // quickly alternate between clicking and releasing for maximum speed
1176 if (FrameCounter % 2 == 0)
1177 button_nr = MB_RELEASED;
1179 SetPlayerMouseAction(old_mx, old_my, button_nr);
1182 last_button = button_nr;
1186 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1190 // finger stays at or returned to starting position
1192 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1194 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1199 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1201 static int old_mx = 0, old_my = 0;
1202 static Key motion_key_x = KSYM_UNDEFINED;
1203 static Key motion_key_y = KSYM_UNDEFINED;
1204 static boolean touched = FALSE;
1205 static boolean started_on_player = FALSE;
1206 static boolean player_is_dropping = FALSE;
1207 static int player_drop_count = 0;
1208 static int last_player_x = -1;
1209 static int last_player_y = -1;
1211 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1220 started_on_player = FALSE;
1221 player_is_dropping = FALSE;
1222 player_drop_count = 0;
1226 motion_key_x = KSYM_UNDEFINED;
1227 motion_key_y = KSYM_UNDEFINED;
1229 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1232 else if (button == MB_RELEASED && touched)
1239 if (motion_key_x != KSYM_UNDEFINED)
1240 HandleKey(motion_key_x, KEY_RELEASED);
1241 if (motion_key_y != KSYM_UNDEFINED)
1242 HandleKey(motion_key_y, KEY_RELEASED);
1244 if (started_on_player)
1246 if (player_is_dropping)
1248 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1250 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1254 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1256 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1260 motion_key_x = KSYM_UNDEFINED;
1261 motion_key_y = KSYM_UNDEFINED;
1263 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1268 int src_x = local_player->jx;
1269 int src_y = local_player->jy;
1270 int dst_x = getLevelFromScreenX(old_mx);
1271 int dst_y = getLevelFromScreenY(old_my);
1272 int dx = dst_x - src_x;
1273 int dy = dst_y - src_y;
1274 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1275 dx > 0 ? setup.input[0].key.right :
1277 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1278 dy > 0 ? setup.input[0].key.down :
1281 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1282 (last_player_x != local_player->jx ||
1283 last_player_y != local_player->jy))
1285 // in case of asymmetric diagonal movement, use "preferred" direction
1287 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1289 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1290 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1292 local_player->last_move_dir = last_move_dir;
1294 // (required to prevent accidentally forcing direction for next movement)
1295 last_player_x = local_player->jx;
1296 last_player_y = local_player->jy;
1299 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1301 started_on_player = TRUE;
1302 player_drop_count = getPlayerInventorySize(0);
1303 player_is_dropping = (player_drop_count > 0);
1305 if (player_is_dropping)
1307 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1309 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1313 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1315 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1318 else if (dx != 0 || dy != 0)
1320 if (player_is_dropping &&
1321 player_drop_count == getPlayerInventorySize(0))
1323 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1325 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1326 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1328 player_is_dropping = FALSE;
1332 if (new_motion_key_x != motion_key_x)
1334 Error(ERR_DEBUG, "---------- %s %s ----------",
1335 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1336 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1338 if (motion_key_x != KSYM_UNDEFINED)
1339 HandleKey(motion_key_x, KEY_RELEASED);
1340 if (new_motion_key_x != KSYM_UNDEFINED)
1341 HandleKey(new_motion_key_x, KEY_PRESSED);
1344 if (new_motion_key_y != motion_key_y)
1346 Error(ERR_DEBUG, "---------- %s %s ----------",
1347 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1348 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1350 if (motion_key_y != KSYM_UNDEFINED)
1351 HandleKey(motion_key_y, KEY_RELEASED);
1352 if (new_motion_key_y != KSYM_UNDEFINED)
1353 HandleKey(new_motion_key_y, KEY_PRESSED);
1356 motion_key_x = new_motion_key_x;
1357 motion_key_y = new_motion_key_y;
1361 static void HandleButtonOrFinger(int mx, int my, int button)
1363 if (game_status != GAME_MODE_PLAYING)
1366 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1368 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1369 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1370 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1371 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1375 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1376 HandleButtonOrFinger_FollowFinger(mx, my, button);
1380 #if defined(TARGET_SDL2)
1382 static boolean checkTextInputKeyModState()
1384 // when playing, only handle raw key events and ignore text input
1385 if (game_status == GAME_MODE_PLAYING)
1388 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1391 void HandleTextEvent(TextEvent *event)
1393 char *text = event->text;
1394 Key key = getKeyFromKeyName(text);
1396 #if DEBUG_EVENTS_TEXT
1397 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1400 text[0], (int)(text[0]),
1402 getKeyNameFromKey(key),
1406 #if !defined(HAS_SCREEN_KEYBOARD)
1407 // non-mobile devices: only handle key input with modifier keys pressed here
1408 // (every other key input is handled directly as physical key input event)
1409 if (!checkTextInputKeyModState())
1413 // process text input as "classic" (with uppercase etc.) key input event
1414 HandleKey(key, KEY_PRESSED);
1415 HandleKey(key, KEY_RELEASED);
1418 void HandlePauseResumeEvent(PauseResumeEvent *event)
1420 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1424 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1432 void HandleKeyEvent(KeyEvent *event)
1434 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1435 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1436 Key key = GetEventKey(event, with_modifiers);
1437 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1439 #if DEBUG_EVENTS_KEY
1440 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1441 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1442 event->keysym.scancode,
1447 getKeyNameFromKey(key));
1450 #if defined(PLATFORM_ANDROID)
1451 if (key == KSYM_Back)
1453 // always map the "back" button to the "escape" key on Android devices
1458 // for any key event other than "back" button, disable overlay buttons
1459 SetOverlayEnabled(FALSE);
1463 HandleKeyModState(keymod, key_status);
1465 #if defined(TARGET_SDL2)
1466 // only handle raw key input without text modifier keys pressed
1467 if (!checkTextInputKeyModState())
1468 HandleKey(key, key_status);
1470 HandleKey(key, key_status);
1474 void HandleFocusEvent(FocusChangeEvent *event)
1476 static int old_joystick_status = -1;
1478 if (event->type == EVENT_FOCUSOUT)
1480 KeyboardAutoRepeatOn();
1481 old_joystick_status = joystick.status;
1482 joystick.status = JOYSTICK_NOT_AVAILABLE;
1484 ClearPlayerAction();
1486 else if (event->type == EVENT_FOCUSIN)
1488 /* When there are two Rocks'n'Diamonds windows which overlap and
1489 the player moves the pointer from one game window to the other,
1490 a 'FocusOut' event is generated for the window the pointer is
1491 leaving and a 'FocusIn' event is generated for the window the
1492 pointer is entering. In some cases, it can happen that the
1493 'FocusIn' event is handled by the one game process before the
1494 'FocusOut' event by the other game process. In this case the
1495 X11 environment would end up with activated keyboard auto repeat,
1496 because unfortunately this is a global setting and not (which
1497 would be far better) set for each X11 window individually.
1498 The effect would be keyboard auto repeat while playing the game
1499 (game_status == GAME_MODE_PLAYING), which is not desired.
1500 To avoid this special case, we just wait 1/10 second before
1501 processing the 'FocusIn' event.
1504 if (game_status == GAME_MODE_PLAYING)
1507 KeyboardAutoRepeatOffUnlessAutoplay();
1510 if (old_joystick_status != -1)
1511 joystick.status = old_joystick_status;
1515 void HandleClientMessageEvent(ClientMessageEvent *event)
1517 if (CheckCloseWindowEvent(event))
1521 void HandleWindowManagerEvent(Event *event)
1523 #if defined(TARGET_SDL)
1524 SDLHandleWindowManagerEvent(event);
1528 void HandleButton(int mx, int my, int button, int button_nr)
1530 static int old_mx = 0, old_my = 0;
1531 boolean button_hold = FALSE;
1532 boolean handle_gadgets = TRUE;
1538 button_nr = -button_nr;
1547 #if defined(PLATFORM_ANDROID)
1548 // when playing, only handle gadgets when using "follow finger" controls
1549 // or when using touch controls in combination with the MM game engine
1551 (game_status != GAME_MODE_PLAYING ||
1552 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1553 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1556 if (handle_gadgets && HandleGadgets(mx, my, button))
1558 /* do not handle this button event anymore */
1559 mx = my = -32; /* force mouse event to be outside screen tiles */
1562 if (HandleGlobalAnimClicks(mx, my, button))
1564 /* do not handle this button event anymore */
1565 return; /* force mouse event not to be handled at all */
1568 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1571 /* do not use scroll wheel button events for anything other than gadgets */
1572 if (IS_WHEEL_BUTTON(button_nr))
1575 switch (game_status)
1577 case GAME_MODE_TITLE:
1578 HandleTitleScreen(mx, my, 0, 0, button);
1581 case GAME_MODE_MAIN:
1582 HandleMainMenu(mx, my, 0, 0, button);
1585 case GAME_MODE_PSEUDO_TYPENAME:
1586 HandleTypeName(0, KSYM_Return);
1589 case GAME_MODE_LEVELS:
1590 HandleChooseLevelSet(mx, my, 0, 0, button);
1593 case GAME_MODE_LEVELNR:
1594 HandleChooseLevelNr(mx, my, 0, 0, button);
1597 case GAME_MODE_SCORES:
1598 HandleHallOfFame(0, 0, 0, 0, button);
1601 case GAME_MODE_EDITOR:
1602 HandleLevelEditorIdle();
1605 case GAME_MODE_INFO:
1606 HandleInfoScreen(mx, my, 0, 0, button);
1609 case GAME_MODE_SETUP:
1610 HandleSetupScreen(mx, my, 0, 0, button);
1613 case GAME_MODE_PLAYING:
1614 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1615 HandleButtonOrFinger(mx, my, button);
1617 SetPlayerMouseAction(mx, my, button);
1620 if (button == MB_PRESSED && !motion_status && !button_hold &&
1621 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1622 DumpTileFromScreen(mx, my);
1632 static boolean is_string_suffix(char *string, char *suffix)
1634 int string_len = strlen(string);
1635 int suffix_len = strlen(suffix);
1637 if (suffix_len > string_len)
1640 return (strEqual(&string[string_len - suffix_len], suffix));
1643 #define MAX_CHEAT_INPUT_LEN 32
1645 static void HandleKeysSpecial(Key key)
1647 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1648 char letter = getCharFromKey(key);
1649 int cheat_input_len = strlen(cheat_input);
1655 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1657 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1658 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1660 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1663 cheat_input[cheat_input_len++] = letter;
1664 cheat_input[cheat_input_len] = '\0';
1666 #if DEBUG_EVENTS_KEY
1667 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1670 if (game_status == GAME_MODE_MAIN)
1672 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1673 is_string_suffix(cheat_input, ":ist"))
1675 InsertSolutionTape();
1677 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1678 is_string_suffix(cheat_input, ":rg"))
1680 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1683 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1684 is_string_suffix(cheat_input, ":rs"))
1686 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1689 else if (is_string_suffix(cheat_input, ":reload-music") ||
1690 is_string_suffix(cheat_input, ":rm"))
1692 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1695 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1696 is_string_suffix(cheat_input, ":ra"))
1698 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1699 1 << ARTWORK_TYPE_SOUNDS |
1700 1 << ARTWORK_TYPE_MUSIC);
1703 else if (is_string_suffix(cheat_input, ":dump-level") ||
1704 is_string_suffix(cheat_input, ":dl"))
1708 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1709 is_string_suffix(cheat_input, ":dt"))
1713 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1714 is_string_suffix(cheat_input, ":ft"))
1716 /* fix single-player tapes that contain player input for more than one
1717 player (due to a bug in 3.3.1.2 and earlier versions), which results
1718 in playing levels with more than one player in multi-player mode,
1719 even though the tape was originally recorded in single-player mode */
1721 /* remove player input actions for all players but the first one */
1722 for (i = 1; i < MAX_PLAYERS; i++)
1723 tape.player_participates[i] = FALSE;
1725 tape.changed = TRUE;
1727 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1728 is_string_suffix(cheat_input, ":snl"))
1730 SaveNativeLevel(&level);
1732 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1733 is_string_suffix(cheat_input, ":fps"))
1735 global.show_frames_per_second = !global.show_frames_per_second;
1738 else if (game_status == GAME_MODE_PLAYING)
1741 if (is_string_suffix(cheat_input, ".q"))
1742 DEBUG_SetMaximumDynamite();
1745 else if (game_status == GAME_MODE_EDITOR)
1747 if (is_string_suffix(cheat_input, ":dump-brush") ||
1748 is_string_suffix(cheat_input, ":DB"))
1752 else if (is_string_suffix(cheat_input, ":DDB"))
1759 void HandleKeysDebug(Key key)
1764 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1766 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1768 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1770 if (key == setup.debug.frame_delay_key[i] &&
1771 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1773 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1774 setup.debug.frame_delay[i] : setup.game_frame_delay);
1776 if (!setup.debug.frame_delay_game_only)
1777 MenuFrameDelay = GameFrameDelay;
1779 SetVideoFrameDelay(GameFrameDelay);
1781 if (GameFrameDelay > ONE_SECOND_DELAY)
1782 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1783 else if (GameFrameDelay != 0)
1784 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1785 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1786 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1788 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1795 if (game_status == GAME_MODE_PLAYING)
1799 options.debug = !options.debug;
1801 Error(ERR_DEBUG, "debug mode %s",
1802 (options.debug ? "enabled" : "disabled"));
1804 else if (key == KSYM_v)
1806 Error(ERR_DEBUG, "currently using game engine version %d",
1807 game.engine_version);
1813 void HandleKey(Key key, int key_status)
1815 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1816 static boolean ignore_repeated_key = FALSE;
1817 static struct SetupKeyboardInfo ski;
1818 static struct SetupShortcutInfo ssi;
1827 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1828 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1829 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1830 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1831 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1832 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1837 #if defined(TARGET_SDL2)
1838 /* map special keys (media keys / remote control buttons) to default keys */
1839 if (key == KSYM_PlayPause)
1841 else if (key == KSYM_Select)
1845 HandleSpecialGameControllerKeys(key, key_status);
1847 if (game_status == GAME_MODE_PLAYING)
1849 /* only needed for single-step tape recording mode */
1850 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1853 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1855 byte key_action = 0;
1857 if (setup.input[pnr].use_joystick)
1860 ski = setup.input[pnr].key;
1862 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1863 if (key == *key_info[i].key_custom)
1864 key_action |= key_info[i].action;
1866 /* use combined snap+direction keys for the first player only */
1869 ssi = setup.shortcut;
1871 for (i = 0; i < NUM_DIRECTIONS; i++)
1872 if (key == *key_info[i].key_snap)
1873 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1876 if (key_status == KEY_PRESSED)
1877 stored_player[pnr].action |= key_action;
1879 stored_player[pnr].action &= ~key_action;
1881 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1883 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1885 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1887 /* if snap key already pressed, keep pause mode when releasing */
1888 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1889 has_snapped[pnr] = TRUE;
1891 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1893 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1895 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1896 getRedDiskReleaseFlag_SP() == 0)
1898 /* add a single inactive frame before dropping starts */
1899 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1900 stored_player[pnr].force_dropping = TRUE;
1903 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1905 /* if snap key was pressed without direction, leave pause mode */
1906 if (!has_snapped[pnr])
1907 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1909 has_snapped[pnr] = FALSE;
1912 else if (tape.recording && tape.pausing && !tape.use_mouse)
1914 /* prevent key release events from un-pausing a paused game */
1915 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1916 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1919 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1920 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1926 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1927 if (key == key_info[i].key_default)
1928 joy |= key_info[i].action;
1933 if (key_status == KEY_PRESSED)
1934 key_joystick_mapping |= joy;
1936 key_joystick_mapping &= ~joy;
1941 if (game_status != GAME_MODE_PLAYING)
1942 key_joystick_mapping = 0;
1944 if (key_status == KEY_RELEASED)
1946 // reset flag to ignore repeated "key pressed" events after key release
1947 ignore_repeated_key = FALSE;
1952 if ((key == KSYM_F11 ||
1953 ((key == KSYM_Return ||
1954 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1955 video.fullscreen_available &&
1956 !ignore_repeated_key)
1958 setup.fullscreen = !setup.fullscreen;
1960 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1962 if (game_status == GAME_MODE_SETUP)
1963 RedrawSetupScreenAfterFullscreenToggle();
1965 // set flag to ignore repeated "key pressed" events
1966 ignore_repeated_key = TRUE;
1971 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1972 key == KSYM_minus || key == KSYM_KP_Subtract ||
1973 key == KSYM_plus || key == KSYM_KP_Add ||
1974 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1975 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1976 video.window_scaling_available &&
1977 !video.fullscreen_enabled)
1979 if (key == KSYM_0 || key == KSYM_KP_0)
1980 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1981 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1982 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1984 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
1986 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
1987 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
1988 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
1989 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
1991 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1993 if (game_status == GAME_MODE_SETUP)
1994 RedrawSetupScreenAfterFullscreenToggle();
1999 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2000 key == KSYM_Return ||
2001 key == KSYM_Escape)))
2003 /* do not handle this key event anymore */
2004 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2008 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2009 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2016 if (game_status == GAME_MODE_MAIN &&
2017 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2019 StartGameActions(options.network, setup.autorecord, level.random_seed);
2024 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2026 if (key == setup.shortcut.save_game)
2028 else if (key == setup.shortcut.load_game)
2030 else if (key == setup.shortcut.toggle_pause)
2031 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2033 HandleTapeButtonKeys(key);
2034 HandleSoundButtonKeys(key);
2037 if (game_status == GAME_MODE_PLAYING && !network_playing)
2039 int centered_player_nr_next = -999;
2041 if (key == setup.shortcut.focus_player_all)
2042 centered_player_nr_next = -1;
2044 for (i = 0; i < MAX_PLAYERS; i++)
2045 if (key == setup.shortcut.focus_player[i])
2046 centered_player_nr_next = i;
2048 if (centered_player_nr_next != -999)
2050 game.centered_player_nr_next = centered_player_nr_next;
2051 game.set_centered_player = TRUE;
2055 tape.centered_player_nr_next = game.centered_player_nr_next;
2056 tape.set_centered_player = TRUE;
2061 HandleKeysSpecial(key);
2063 if (HandleGadgetsKeyInput(key))
2065 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2066 key = KSYM_UNDEFINED;
2069 switch (game_status)
2071 case GAME_MODE_PSEUDO_TYPENAME:
2072 HandleTypeName(0, key);
2075 case GAME_MODE_TITLE:
2076 case GAME_MODE_MAIN:
2077 case GAME_MODE_LEVELS:
2078 case GAME_MODE_LEVELNR:
2079 case GAME_MODE_SETUP:
2080 case GAME_MODE_INFO:
2081 case GAME_MODE_SCORES:
2086 if (game_status == GAME_MODE_TITLE)
2087 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2088 else if (game_status == GAME_MODE_MAIN)
2089 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2090 else if (game_status == GAME_MODE_LEVELS)
2091 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2092 else if (game_status == GAME_MODE_LEVELNR)
2093 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2094 else if (game_status == GAME_MODE_SETUP)
2095 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2096 else if (game_status == GAME_MODE_INFO)
2097 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2098 else if (game_status == GAME_MODE_SCORES)
2099 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2103 if (game_status != GAME_MODE_MAIN)
2104 FadeSkipNextFadeIn();
2106 if (game_status == GAME_MODE_TITLE)
2107 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2108 else if (game_status == GAME_MODE_LEVELS)
2109 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2110 else if (game_status == GAME_MODE_LEVELNR)
2111 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2112 else if (game_status == GAME_MODE_SETUP)
2113 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2114 else if (game_status == GAME_MODE_INFO)
2115 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2116 else if (game_status == GAME_MODE_SCORES)
2117 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
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);
2133 case KSYM_Page_Down:
2134 if (game_status == GAME_MODE_LEVELS)
2135 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2136 else if (game_status == GAME_MODE_LEVELNR)
2137 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2138 else if (game_status == GAME_MODE_SETUP)
2139 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2140 else if (game_status == GAME_MODE_INFO)
2141 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2142 else if (game_status == GAME_MODE_SCORES)
2143 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2151 case GAME_MODE_EDITOR:
2152 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2153 HandleLevelEditorKeyInput(key);
2156 case GAME_MODE_PLAYING:
2161 RequestQuitGame(setup.ask_on_escape);
2171 if (key == KSYM_Escape)
2173 SetGameStatus(GAME_MODE_MAIN);
2181 HandleKeysDebug(key);
2184 void HandleNoEvent()
2186 HandleMouseCursor();
2188 switch (game_status)
2190 case GAME_MODE_PLAYING:
2191 HandleButtonOrFinger(-1, -1, -1);
2196 void HandleEventActions()
2198 // if (button_status && game_status != GAME_MODE_PLAYING)
2199 if (button_status && (game_status != GAME_MODE_PLAYING ||
2201 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2203 HandleButton(0, 0, button_status, -button_status);
2210 #if defined(NETWORK_AVALIABLE)
2211 if (options.network)
2215 switch (game_status)
2217 case GAME_MODE_MAIN:
2218 DrawPreviewLevelAnimation();
2221 case GAME_MODE_EDITOR:
2222 HandleLevelEditorIdle();
2230 static void HandleTileCursor(int dx, int dy, int button)
2233 ClearPlayerMouseAction();
2240 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2241 (dx < 0 ? MB_LEFTBUTTON :
2242 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2244 else if (!tile_cursor.moving)
2246 int old_xpos = tile_cursor.xpos;
2247 int old_ypos = tile_cursor.ypos;
2248 int new_xpos = old_xpos;
2249 int new_ypos = old_ypos;
2251 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2252 new_xpos = old_xpos + dx;
2254 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2255 new_ypos = old_ypos + dy;
2257 SetTileCursorTargetXY(new_xpos, new_ypos);
2261 static int HandleJoystickForAllPlayers()
2265 boolean no_joysticks_configured = TRUE;
2266 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2267 static byte joy_action_last[MAX_PLAYERS];
2269 for (i = 0; i < MAX_PLAYERS; i++)
2270 if (setup.input[i].use_joystick)
2271 no_joysticks_configured = FALSE;
2273 /* if no joysticks configured, map connected joysticks to players */
2274 if (no_joysticks_configured)
2275 use_as_joystick_nr = TRUE;
2277 for (i = 0; i < MAX_PLAYERS; i++)
2279 byte joy_action = 0;
2281 joy_action = JoystickExt(i, use_as_joystick_nr);
2282 result |= joy_action;
2284 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2285 joy_action != joy_action_last[i])
2286 stored_player[i].action = joy_action;
2288 joy_action_last[i] = joy_action;
2294 void HandleJoystick()
2296 static unsigned int joytest_delay = 0;
2297 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2298 static int joytest_last = 0;
2299 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2300 int delay_value = GADGET_FRAME_DELAY;
2301 int joystick = HandleJoystickForAllPlayers();
2302 int keyboard = key_joystick_mapping;
2303 int joy = (joystick | keyboard);
2304 int joytest = joystick;
2305 int left = joy & JOY_LEFT;
2306 int right = joy & JOY_RIGHT;
2307 int up = joy & JOY_UP;
2308 int down = joy & JOY_DOWN;
2309 int button = joy & JOY_BUTTON;
2310 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2311 int dx = (left ? -1 : right ? 1 : 0);
2312 int dy = (up ? -1 : down ? 1 : 0);
2313 boolean use_delay_value_first = (joytest != joytest_last);
2315 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2317 /* do not handle this button event anymore */
2321 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2323 if (game_status == GAME_MODE_PLAYING)
2325 // when playing MM style levels, also use delay for keyboard events
2326 joytest |= keyboard;
2328 // only use first delay value for new events, but not for changed events
2329 use_delay_value_first = (!joytest != !joytest_last);
2331 // only use delay after the initial keyboard event
2335 // for any joystick or keyboard event, enable playfield tile cursor
2336 if (dx || dy || button)
2337 SetTileCursorEnabled(TRUE);
2340 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2342 /* delay joystick/keyboard actions if axes/keys continually pressed */
2343 newbutton = dx = dy = 0;
2347 /* first start with longer delay, then continue with shorter delay */
2348 joytest_delay_value =
2349 (use_delay_value_first ? delay_value_first : delay_value);
2352 joytest_last = joytest;
2354 switch (game_status)
2356 case GAME_MODE_TITLE:
2357 case GAME_MODE_MAIN:
2358 case GAME_MODE_LEVELS:
2359 case GAME_MODE_LEVELNR:
2360 case GAME_MODE_SETUP:
2361 case GAME_MODE_INFO:
2362 case GAME_MODE_SCORES:
2364 if (game_status == GAME_MODE_TITLE)
2365 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2366 else if (game_status == GAME_MODE_MAIN)
2367 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2368 else if (game_status == GAME_MODE_LEVELS)
2369 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2370 else if (game_status == GAME_MODE_LEVELNR)
2371 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2372 else if (game_status == GAME_MODE_SETUP)
2373 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2374 else if (game_status == GAME_MODE_INFO)
2375 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2376 else if (game_status == GAME_MODE_SCORES)
2377 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2382 case GAME_MODE_PLAYING:
2384 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2385 if (tape.playing || keyboard)
2386 newbutton = ((joy & JOY_BUTTON) != 0);
2389 if (newbutton && AllPlayersGone)
2396 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2398 if (joystick & JOY_ACTION)
2399 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2401 else if (tape.recording && tape.pausing && !tape.use_mouse)
2403 if (joystick & JOY_ACTION)
2404 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2407 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2408 HandleTileCursor(dx, dy, button);
2417 void HandleSpecialGameControllerButtons(Event *event)
2419 #if defined(TARGET_SDL2)
2420 switch (event->type)
2422 case SDL_CONTROLLERBUTTONDOWN:
2423 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2424 HandleKey(KSYM_space, KEY_PRESSED);
2425 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2426 HandleKey(KSYM_Escape, KEY_PRESSED);
2430 case SDL_CONTROLLERBUTTONUP:
2431 if (event->cbutton.button == SDL_CONTROLLER_BUTTON_START)
2432 HandleKey(KSYM_space, KEY_RELEASED);
2433 else if (event->cbutton.button == SDL_CONTROLLER_BUTTON_BACK)
2434 HandleKey(KSYM_Escape, KEY_RELEASED);
2441 void HandleSpecialGameControllerKeys(Key key, int key_status)
2443 #if defined(TARGET_SDL2)
2444 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2445 int button = SDL_CONTROLLER_BUTTON_INVALID;
2447 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2448 if (key == KSYM_Rewind)
2449 button = SDL_CONTROLLER_BUTTON_A;
2450 else if (key == KSYM_FastForward || key == KSYM_Menu)
2451 button = SDL_CONTROLLER_BUTTON_B;
2453 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2457 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2458 SDL_CONTROLLERBUTTONUP);
2460 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2461 event.cbutton.button = button;
2462 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2465 HandleJoystickEvent(&event);