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))
167 static void HandleEvents(void)
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 static void HandleMouseCursor(void)
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 ClearAutoRepeatKeyEvents(void)
352 while (PendingEvent())
356 PeekEvent(&next_event);
358 /* if event is repeated key press event, remove it from event queue */
359 if (next_event.type == EVENT_KEYPRESS &&
360 next_event.key.repeat)
361 WaitEvent(&next_event);
367 void ClearEventQueue(void)
371 while (NextValidEvent(&event))
375 case EVENT_BUTTONRELEASE:
376 button_status = MB_RELEASED;
379 case EVENT_KEYRELEASE:
383 #if defined(TARGET_SDL2)
384 case SDL_CONTROLLERBUTTONUP:
385 HandleJoystickEvent(&event);
391 HandleOtherEvents(&event);
397 static void ClearPlayerMouseAction(void)
399 local_player->mouse_action.lx = 0;
400 local_player->mouse_action.ly = 0;
401 local_player->mouse_action.button = 0;
404 void ClearPlayerAction(void)
408 /* simulate key release events for still pressed keys */
409 key_joystick_mapping = 0;
410 for (i = 0; i < MAX_PLAYERS; i++)
411 stored_player[i].action = 0;
413 ClearJoystickState();
414 ClearPlayerMouseAction();
417 static void SetPlayerMouseAction(int mx, int my, int button)
419 int lx = getLevelFromScreenX(mx);
420 int ly = getLevelFromScreenY(my);
421 int new_button = (!local_player->mouse_action.button && button);
423 if (local_player->mouse_action.button_hint)
424 button = local_player->mouse_action.button_hint;
426 ClearPlayerMouseAction();
428 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
431 local_player->mouse_action.lx = lx;
432 local_player->mouse_action.ly = ly;
433 local_player->mouse_action.button = button;
435 if (tape.recording && tape.pausing && tape.use_mouse)
437 /* un-pause a paused game only if mouse button was newly pressed down */
439 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
442 SetTileCursorXY(lx, ly);
445 void SleepWhileUnmapped(void)
447 boolean window_unmapped = TRUE;
449 KeyboardAutoRepeatOn();
451 while (window_unmapped)
455 if (!WaitValidEvent(&event))
460 case EVENT_BUTTONRELEASE:
461 button_status = MB_RELEASED;
464 case EVENT_KEYRELEASE:
465 key_joystick_mapping = 0;
468 #if defined(TARGET_SDL2)
469 case SDL_CONTROLLERBUTTONUP:
470 HandleJoystickEvent(&event);
471 key_joystick_mapping = 0;
475 case EVENT_MAPNOTIFY:
476 window_unmapped = FALSE;
479 case EVENT_UNMAPNOTIFY:
480 /* this is only to surely prevent the 'should not happen' case
481 * of recursively looping between 'SleepWhileUnmapped()' and
482 * 'HandleOtherEvents()' which usually calls this funtion.
487 HandleOtherEvents(&event);
492 if (game_status == GAME_MODE_PLAYING)
493 KeyboardAutoRepeatOffUnlessAutoplay();
496 void HandleExposeEvent(ExposeEvent *event)
500 void HandleButtonEvent(ButtonEvent *event)
502 #if DEBUG_EVENTS_BUTTON
503 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
505 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
509 // for any mouse button event, disable playfield tile cursor
510 SetTileCursorEnabled(FALSE);
512 #if defined(HAS_SCREEN_KEYBOARD)
513 if (video.shifted_up)
514 event->y += video.shifted_up_pos;
517 motion_status = FALSE;
519 if (event->type == EVENT_BUTTONPRESS)
520 button_status = event->button;
522 button_status = MB_RELEASED;
524 HandleButton(event->x, event->y, button_status, event->button);
527 void HandleMotionEvent(MotionEvent *event)
529 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
532 motion_status = TRUE;
534 #if DEBUG_EVENTS_MOTION
535 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
536 button_status, event->x, event->y);
539 HandleButton(event->x, event->y, button_status, button_status);
542 #if defined(TARGET_SDL2)
544 void HandleWheelEvent(WheelEvent *event)
548 #if DEBUG_EVENTS_WHEEL
550 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
551 event->which, event->x, event->y);
553 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
554 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
555 event->which, event->x, event->y,
556 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
557 "SDL_MOUSEWHEEL_FLIPPED"));
561 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
562 event->x > 0 ? MB_WHEEL_RIGHT :
563 event->y < 0 ? MB_WHEEL_DOWN :
564 event->y > 0 ? MB_WHEEL_UP : 0);
566 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
567 // accelerated mouse wheel available on Mac and Windows
568 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
570 // no accelerated mouse wheel available on Unix/Linux
571 wheel_steps = DEFAULT_WHEEL_STEPS;
574 motion_status = FALSE;
576 button_status = button_nr;
577 HandleButton(0, 0, button_status, -button_nr);
579 button_status = MB_RELEASED;
580 HandleButton(0, 0, button_status, -button_nr);
583 void HandleWindowEvent(WindowEvent *event)
585 #if DEBUG_EVENTS_WINDOW
586 int subtype = event->event;
589 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
590 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
591 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
592 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
593 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
594 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
595 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
596 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
597 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
598 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
599 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
600 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
601 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
602 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
605 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
606 event_name, event->data1, event->data2);
610 // (not needed, as the screen gets redrawn every 20 ms anyway)
611 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
612 event->event == SDL_WINDOWEVENT_RESIZED ||
613 event->event == SDL_WINDOWEVENT_EXPOSED)
617 if (event->event == SDL_WINDOWEVENT_RESIZED)
619 if (!video.fullscreen_enabled)
621 int new_window_width = event->data1;
622 int new_window_height = event->data2;
624 // if window size has changed after resizing, calculate new scaling factor
625 if (new_window_width != video.window_width ||
626 new_window_height != video.window_height)
628 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
629 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
631 // (extreme window scaling allowed, but cannot be saved permanently)
632 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
633 setup.window_scaling_percent =
634 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
635 MAX_WINDOW_SCALING_PERCENT);
637 video.window_width = new_window_width;
638 video.window_height = new_window_height;
640 if (game_status == GAME_MODE_SETUP)
641 RedrawSetupScreenAfterFullscreenToggle();
646 #if defined(PLATFORM_ANDROID)
649 int new_display_width = event->data1;
650 int new_display_height = event->data2;
652 // if fullscreen display size has changed, device has been rotated
653 if (new_display_width != video.display_width ||
654 new_display_height != video.display_height)
656 int nr = GRID_ACTIVE_NR(); // previous screen orientation
658 video.display_width = new_display_width;
659 video.display_height = new_display_height;
661 SDLSetScreenProperties();
663 // check if screen orientation has changed (should always be true here)
664 if (nr != GRID_ACTIVE_NR())
668 if (game_status == GAME_MODE_SETUP)
669 RedrawSetupScreenAfterScreenRotation(nr);
671 nr = GRID_ACTIVE_NR();
673 overlay.grid_xsize = setup.touch.grid_xsize[nr];
674 overlay.grid_ysize = setup.touch.grid_ysize[nr];
676 for (x = 0; x < MAX_GRID_XSIZE; x++)
677 for (y = 0; y < MAX_GRID_YSIZE; y++)
678 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
686 #define NUM_TOUCH_FINGERS 3
691 SDL_FingerID finger_id;
694 } touch_info[NUM_TOUCH_FINGERS];
696 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
699 int x = event->x * overlay.grid_xsize;
700 int y = event->y * overlay.grid_ysize;
701 int grid_button = overlay.grid_button[x][y];
702 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
703 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
704 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
705 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
706 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
707 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
708 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
711 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
712 float event_x = (event->x);
713 float event_y = (event->y - ypos) / (1 - ypos);
714 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
715 event_y > 2.0 / 3.0 && event_y < 1 ?
716 setup.input[0].key.snap :
717 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
718 event_y > 2.0 / 3.0 && event_y < 1 ?
719 setup.input[0].key.drop :
720 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
721 event_y > 0 && event_y < 1.0 / 3.0 ?
722 setup.input[0].key.up :
723 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
724 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
725 setup.input[0].key.left :
726 event_x > 8.0 / 9.0 && event_x < 1 &&
727 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
728 setup.input[0].key.right :
729 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
730 event_y > 2.0 / 3.0 && event_y < 1 ?
731 setup.input[0].key.down :
734 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
736 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
740 // for any touch input event, enable overlay buttons (if activated)
741 SetOverlayEnabled(TRUE);
743 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
744 getKeyNameFromKey(key), key_status_name, event->fingerId);
746 if (key_status == KEY_PRESSED)
747 overlay.grid_button_action |= grid_button_action;
749 overlay.grid_button_action &= ~grid_button_action;
751 // check if we already know this touch event's finger id
752 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
754 if (touch_info[i].touched &&
755 touch_info[i].finger_id == event->fingerId)
757 // Error(ERR_DEBUG, "MARK 1: %d", i);
763 if (i >= NUM_TOUCH_FINGERS)
765 if (key_status == KEY_PRESSED)
767 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
769 // unknown finger id -- get new, empty slot, if available
770 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
772 if (touch_info[i].counter < oldest_counter)
775 oldest_counter = touch_info[i].counter;
777 // Error(ERR_DEBUG, "MARK 2: %d", i);
780 if (!touch_info[i].touched)
782 // Error(ERR_DEBUG, "MARK 3: %d", i);
788 if (i >= NUM_TOUCH_FINGERS)
790 // all slots allocated -- use oldest slot
793 // Error(ERR_DEBUG, "MARK 4: %d", i);
798 // release of previously unknown key (should not happen)
800 if (key != KSYM_UNDEFINED)
802 HandleKey(key, KEY_RELEASED);
804 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
805 getKeyNameFromKey(key), "KEY_RELEASED", i);
810 if (i < NUM_TOUCH_FINGERS)
812 if (key_status == KEY_PRESSED)
814 if (touch_info[i].key != key)
816 if (touch_info[i].key != KSYM_UNDEFINED)
818 HandleKey(touch_info[i].key, KEY_RELEASED);
820 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
821 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
824 if (key != KSYM_UNDEFINED)
826 HandleKey(key, KEY_PRESSED);
828 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
829 getKeyNameFromKey(key), "KEY_PRESSED", i);
833 touch_info[i].touched = TRUE;
834 touch_info[i].finger_id = event->fingerId;
835 touch_info[i].counter = Counter();
836 touch_info[i].key = key;
840 if (touch_info[i].key != KSYM_UNDEFINED)
842 HandleKey(touch_info[i].key, KEY_RELEASED);
844 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
845 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
848 touch_info[i].touched = FALSE;
849 touch_info[i].finger_id = 0;
850 touch_info[i].counter = 0;
851 touch_info[i].key = 0;
856 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
858 static Key motion_key_x = KSYM_UNDEFINED;
859 static Key motion_key_y = KSYM_UNDEFINED;
860 static Key button_key = KSYM_UNDEFINED;
861 static float motion_x1, motion_y1;
862 static float button_x1, button_y1;
863 static SDL_FingerID motion_id = -1;
864 static SDL_FingerID button_id = -1;
865 int move_trigger_distance_percent = setup.touch.move_distance;
866 int drop_trigger_distance_percent = setup.touch.drop_distance;
867 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
868 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
869 float event_x = event->x;
870 float event_y = event->y;
872 if (event->type == EVENT_FINGERPRESS)
874 if (event_x > 1.0 / 3.0)
878 motion_id = event->fingerId;
883 motion_key_x = KSYM_UNDEFINED;
884 motion_key_y = KSYM_UNDEFINED;
886 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
892 button_id = event->fingerId;
897 button_key = setup.input[0].key.snap;
899 HandleKey(button_key, KEY_PRESSED);
901 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
904 else if (event->type == EVENT_FINGERRELEASE)
906 if (event->fingerId == motion_id)
910 if (motion_key_x != KSYM_UNDEFINED)
911 HandleKey(motion_key_x, KEY_RELEASED);
912 if (motion_key_y != KSYM_UNDEFINED)
913 HandleKey(motion_key_y, KEY_RELEASED);
915 motion_key_x = KSYM_UNDEFINED;
916 motion_key_y = KSYM_UNDEFINED;
918 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
920 else if (event->fingerId == button_id)
924 if (button_key != KSYM_UNDEFINED)
925 HandleKey(button_key, KEY_RELEASED);
927 button_key = KSYM_UNDEFINED;
929 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
932 else if (event->type == EVENT_FINGERMOTION)
934 if (event->fingerId == motion_id)
936 float distance_x = ABS(event_x - motion_x1);
937 float distance_y = ABS(event_y - motion_y1);
938 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
939 event_x > motion_x1 ? setup.input[0].key.right :
941 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
942 event_y > motion_y1 ? setup.input[0].key.down :
945 if (distance_x < move_trigger_distance / 2 ||
946 distance_x < distance_y)
947 new_motion_key_x = KSYM_UNDEFINED;
949 if (distance_y < move_trigger_distance / 2 ||
950 distance_y < distance_x)
951 new_motion_key_y = KSYM_UNDEFINED;
953 if (distance_x > move_trigger_distance ||
954 distance_y > move_trigger_distance)
956 if (new_motion_key_x != motion_key_x)
958 if (motion_key_x != KSYM_UNDEFINED)
959 HandleKey(motion_key_x, KEY_RELEASED);
960 if (new_motion_key_x != KSYM_UNDEFINED)
961 HandleKey(new_motion_key_x, KEY_PRESSED);
964 if (new_motion_key_y != motion_key_y)
966 if (motion_key_y != KSYM_UNDEFINED)
967 HandleKey(motion_key_y, KEY_RELEASED);
968 if (new_motion_key_y != KSYM_UNDEFINED)
969 HandleKey(new_motion_key_y, KEY_PRESSED);
975 motion_key_x = new_motion_key_x;
976 motion_key_y = new_motion_key_y;
978 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
981 else if (event->fingerId == button_id)
983 float distance_x = ABS(event_x - button_x1);
984 float distance_y = ABS(event_y - button_y1);
986 if (distance_x < drop_trigger_distance / 2 &&
987 distance_y > drop_trigger_distance)
989 if (button_key == setup.input[0].key.snap)
990 HandleKey(button_key, KEY_RELEASED);
995 button_key = setup.input[0].key.drop;
997 HandleKey(button_key, KEY_PRESSED);
999 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1005 void HandleFingerEvent(FingerEvent *event)
1007 #if DEBUG_EVENTS_FINGER
1008 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1009 event->type == EVENT_FINGERPRESS ? "pressed" :
1010 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1014 event->dx, event->dy,
1018 if (game_status != GAME_MODE_PLAYING)
1021 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1023 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1024 local_player->mouse_action.button_hint =
1025 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1026 event->x < 0.5 ? MB_LEFTBUTTON :
1027 event->x > 0.5 ? MB_RIGHTBUTTON :
1033 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1034 HandleFingerEvent_VirtualButtons(event);
1035 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1036 HandleFingerEvent_WipeGestures(event);
1041 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1043 static int old_mx = 0, old_my = 0;
1044 static int last_button = MB_LEFTBUTTON;
1045 static boolean touched = FALSE;
1046 static boolean tapped = FALSE;
1048 // screen tile was tapped (but finger not touching the screen anymore)
1049 // (this point will also be reached without receiving a touch event)
1050 if (tapped && !touched)
1052 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1057 // stop here if this function was not triggered by a touch event
1061 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1063 // finger started touching the screen
1073 ClearPlayerMouseAction();
1075 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1078 else if (button == MB_RELEASED && touched)
1080 // finger stopped touching the screen
1085 SetPlayerMouseAction(old_mx, old_my, last_button);
1087 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1089 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1094 // finger moved while touching the screen
1096 int old_x = getLevelFromScreenX(old_mx);
1097 int old_y = getLevelFromScreenY(old_my);
1098 int new_x = getLevelFromScreenX(mx);
1099 int new_y = getLevelFromScreenY(my);
1101 if (new_x != old_x || new_y != old_y)
1106 // finger moved left or right from (horizontal) starting position
1108 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1110 SetPlayerMouseAction(old_mx, old_my, button_nr);
1112 last_button = button_nr;
1114 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1118 // finger stays at or returned to (horizontal) starting position
1120 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1122 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1127 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1129 static int old_mx = 0, old_my = 0;
1130 static int last_button = MB_LEFTBUTTON;
1131 static boolean touched = FALSE;
1132 static boolean tapped = FALSE;
1134 // screen tile was tapped (but finger not touching the screen anymore)
1135 // (this point will also be reached without receiving a touch event)
1136 if (tapped && !touched)
1138 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1143 // stop here if this function was not triggered by a touch event
1147 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1149 // finger started touching the screen
1159 ClearPlayerMouseAction();
1161 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1164 else if (button == MB_RELEASED && touched)
1166 // finger stopped touching the screen
1171 SetPlayerMouseAction(old_mx, old_my, last_button);
1173 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1175 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1180 // finger moved while touching the screen
1182 int old_x = getLevelFromScreenX(old_mx);
1183 int old_y = getLevelFromScreenY(old_my);
1184 int new_x = getLevelFromScreenX(mx);
1185 int new_y = getLevelFromScreenY(my);
1187 if (new_x != old_x || new_y != old_y)
1189 // finger moved away from starting position
1191 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1193 // quickly alternate between clicking and releasing for maximum speed
1194 if (FrameCounter % 2 == 0)
1195 button_nr = MB_RELEASED;
1197 SetPlayerMouseAction(old_mx, old_my, button_nr);
1200 last_button = button_nr;
1204 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1208 // finger stays at or returned to starting position
1210 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1212 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1217 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1219 static int old_mx = 0, old_my = 0;
1220 static Key motion_key_x = KSYM_UNDEFINED;
1221 static Key motion_key_y = KSYM_UNDEFINED;
1222 static boolean touched = FALSE;
1223 static boolean started_on_player = FALSE;
1224 static boolean player_is_dropping = FALSE;
1225 static int player_drop_count = 0;
1226 static int last_player_x = -1;
1227 static int last_player_y = -1;
1229 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1238 started_on_player = FALSE;
1239 player_is_dropping = FALSE;
1240 player_drop_count = 0;
1244 motion_key_x = KSYM_UNDEFINED;
1245 motion_key_y = KSYM_UNDEFINED;
1247 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1250 else if (button == MB_RELEASED && touched)
1257 if (motion_key_x != KSYM_UNDEFINED)
1258 HandleKey(motion_key_x, KEY_RELEASED);
1259 if (motion_key_y != KSYM_UNDEFINED)
1260 HandleKey(motion_key_y, KEY_RELEASED);
1262 if (started_on_player)
1264 if (player_is_dropping)
1266 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1268 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1272 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1274 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1278 motion_key_x = KSYM_UNDEFINED;
1279 motion_key_y = KSYM_UNDEFINED;
1281 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1286 int src_x = local_player->jx;
1287 int src_y = local_player->jy;
1288 int dst_x = getLevelFromScreenX(old_mx);
1289 int dst_y = getLevelFromScreenY(old_my);
1290 int dx = dst_x - src_x;
1291 int dy = dst_y - src_y;
1292 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1293 dx > 0 ? setup.input[0].key.right :
1295 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1296 dy > 0 ? setup.input[0].key.down :
1299 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1300 (last_player_x != local_player->jx ||
1301 last_player_y != local_player->jy))
1303 // in case of asymmetric diagonal movement, use "preferred" direction
1305 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1307 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1308 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1310 local_player->last_move_dir = last_move_dir;
1312 // (required to prevent accidentally forcing direction for next movement)
1313 last_player_x = local_player->jx;
1314 last_player_y = local_player->jy;
1317 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1319 started_on_player = TRUE;
1320 player_drop_count = getPlayerInventorySize(0);
1321 player_is_dropping = (player_drop_count > 0);
1323 if (player_is_dropping)
1325 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1327 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1331 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1333 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1336 else if (dx != 0 || dy != 0)
1338 if (player_is_dropping &&
1339 player_drop_count == getPlayerInventorySize(0))
1341 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1343 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1344 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1346 player_is_dropping = FALSE;
1350 if (new_motion_key_x != motion_key_x)
1352 Error(ERR_DEBUG, "---------- %s %s ----------",
1353 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1354 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1356 if (motion_key_x != KSYM_UNDEFINED)
1357 HandleKey(motion_key_x, KEY_RELEASED);
1358 if (new_motion_key_x != KSYM_UNDEFINED)
1359 HandleKey(new_motion_key_x, KEY_PRESSED);
1362 if (new_motion_key_y != motion_key_y)
1364 Error(ERR_DEBUG, "---------- %s %s ----------",
1365 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1366 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1368 if (motion_key_y != KSYM_UNDEFINED)
1369 HandleKey(motion_key_y, KEY_RELEASED);
1370 if (new_motion_key_y != KSYM_UNDEFINED)
1371 HandleKey(new_motion_key_y, KEY_PRESSED);
1374 motion_key_x = new_motion_key_x;
1375 motion_key_y = new_motion_key_y;
1379 static void HandleButtonOrFinger(int mx, int my, int button)
1381 if (game_status != GAME_MODE_PLAYING)
1384 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1386 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1387 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1388 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1389 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1390 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1391 SetPlayerMouseAction(mx, my, button); /* special case */
1395 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1396 HandleButtonOrFinger_FollowFinger(mx, my, button);
1400 #if defined(TARGET_SDL2)
1402 static boolean checkTextInputKeyModState(void)
1404 // when playing, only handle raw key events and ignore text input
1405 if (game_status == GAME_MODE_PLAYING)
1408 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1411 void HandleTextEvent(TextEvent *event)
1413 char *text = event->text;
1414 Key key = getKeyFromKeyName(text);
1416 #if DEBUG_EVENTS_TEXT
1417 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1420 text[0], (int)(text[0]),
1422 getKeyNameFromKey(key),
1426 #if !defined(HAS_SCREEN_KEYBOARD)
1427 // non-mobile devices: only handle key input with modifier keys pressed here
1428 // (every other key input is handled directly as physical key input event)
1429 if (!checkTextInputKeyModState())
1433 // process text input as "classic" (with uppercase etc.) key input event
1434 HandleKey(key, KEY_PRESSED);
1435 HandleKey(key, KEY_RELEASED);
1438 void HandlePauseResumeEvent(PauseResumeEvent *event)
1440 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1444 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1452 void HandleKeyEvent(KeyEvent *event)
1454 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1455 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1456 Key key = GetEventKey(event, with_modifiers);
1457 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1459 #if DEBUG_EVENTS_KEY
1460 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1461 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1462 event->keysym.scancode,
1467 getKeyNameFromKey(key));
1470 #if defined(PLATFORM_ANDROID)
1471 if (key == KSYM_Back)
1473 // always map the "back" button to the "escape" key on Android devices
1478 // for any key event other than "back" button, disable overlay buttons
1479 SetOverlayEnabled(FALSE);
1483 HandleKeyModState(keymod, key_status);
1485 #if defined(TARGET_SDL2)
1486 // only handle raw key input without text modifier keys pressed
1487 if (!checkTextInputKeyModState())
1488 HandleKey(key, key_status);
1490 HandleKey(key, key_status);
1494 void HandleFocusEvent(FocusChangeEvent *event)
1496 static int old_joystick_status = -1;
1498 if (event->type == EVENT_FOCUSOUT)
1500 KeyboardAutoRepeatOn();
1501 old_joystick_status = joystick.status;
1502 joystick.status = JOYSTICK_NOT_AVAILABLE;
1504 ClearPlayerAction();
1506 else if (event->type == EVENT_FOCUSIN)
1508 /* When there are two Rocks'n'Diamonds windows which overlap and
1509 the player moves the pointer from one game window to the other,
1510 a 'FocusOut' event is generated for the window the pointer is
1511 leaving and a 'FocusIn' event is generated for the window the
1512 pointer is entering. In some cases, it can happen that the
1513 'FocusIn' event is handled by the one game process before the
1514 'FocusOut' event by the other game process. In this case the
1515 X11 environment would end up with activated keyboard auto repeat,
1516 because unfortunately this is a global setting and not (which
1517 would be far better) set for each X11 window individually.
1518 The effect would be keyboard auto repeat while playing the game
1519 (game_status == GAME_MODE_PLAYING), which is not desired.
1520 To avoid this special case, we just wait 1/10 second before
1521 processing the 'FocusIn' event.
1524 if (game_status == GAME_MODE_PLAYING)
1527 KeyboardAutoRepeatOffUnlessAutoplay();
1530 if (old_joystick_status != -1)
1531 joystick.status = old_joystick_status;
1535 void HandleClientMessageEvent(ClientMessageEvent *event)
1537 if (CheckCloseWindowEvent(event))
1541 void HandleWindowManagerEvent(Event *event)
1543 #if defined(TARGET_SDL)
1544 SDLHandleWindowManagerEvent(event);
1548 void HandleButton(int mx, int my, int button, int button_nr)
1550 static int old_mx = 0, old_my = 0;
1551 boolean button_hold = FALSE;
1552 boolean handle_gadgets = TRUE;
1558 button_nr = -button_nr;
1567 #if defined(PLATFORM_ANDROID)
1568 // when playing, only handle gadgets when using "follow finger" controls
1569 // or when using touch controls in combination with the MM game engine
1571 (game_status != GAME_MODE_PLAYING ||
1572 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1573 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1576 if (HandleGlobalAnimClicks(mx, my, button))
1578 /* do not handle this button event anymore */
1579 return; /* force mouse event not to be handled at all */
1582 if (handle_gadgets && HandleGadgets(mx, my, button))
1584 /* do not handle this button event anymore */
1585 mx = my = -32; /* force mouse event to be outside screen tiles */
1588 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1591 /* do not use scroll wheel button events for anything other than gadgets */
1592 if (IS_WHEEL_BUTTON(button_nr))
1595 switch (game_status)
1597 case GAME_MODE_TITLE:
1598 HandleTitleScreen(mx, my, 0, 0, button);
1601 case GAME_MODE_MAIN:
1602 HandleMainMenu(mx, my, 0, 0, button);
1605 case GAME_MODE_PSEUDO_TYPENAME:
1606 HandleTypeName(0, KSYM_Return);
1609 case GAME_MODE_LEVELS:
1610 HandleChooseLevelSet(mx, my, 0, 0, button);
1613 case GAME_MODE_LEVELNR:
1614 HandleChooseLevelNr(mx, my, 0, 0, button);
1617 case GAME_MODE_SCORES:
1618 HandleHallOfFame(0, 0, 0, 0, button);
1621 case GAME_MODE_EDITOR:
1622 HandleLevelEditorIdle();
1625 case GAME_MODE_INFO:
1626 HandleInfoScreen(mx, my, 0, 0, button);
1629 case GAME_MODE_SETUP:
1630 HandleSetupScreen(mx, my, 0, 0, button);
1633 case GAME_MODE_PLAYING:
1634 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1635 HandleButtonOrFinger(mx, my, button);
1637 SetPlayerMouseAction(mx, my, button);
1640 if (button == MB_PRESSED && !motion_status && !button_hold &&
1641 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1642 DumpTileFromScreen(mx, my);
1652 static boolean is_string_suffix(char *string, char *suffix)
1654 int string_len = strlen(string);
1655 int suffix_len = strlen(suffix);
1657 if (suffix_len > string_len)
1660 return (strEqual(&string[string_len - suffix_len], suffix));
1663 #define MAX_CHEAT_INPUT_LEN 32
1665 static void HandleKeysSpecial(Key key)
1667 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1668 char letter = getCharFromKey(key);
1669 int cheat_input_len = strlen(cheat_input);
1675 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1677 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1678 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1680 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1683 cheat_input[cheat_input_len++] = letter;
1684 cheat_input[cheat_input_len] = '\0';
1686 #if DEBUG_EVENTS_KEY
1687 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1690 if (game_status == GAME_MODE_MAIN)
1692 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1693 is_string_suffix(cheat_input, ":ist"))
1695 InsertSolutionTape();
1697 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1698 is_string_suffix(cheat_input, ":pst"))
1702 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1703 is_string_suffix(cheat_input, ":rg"))
1705 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1708 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1709 is_string_suffix(cheat_input, ":rs"))
1711 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1714 else if (is_string_suffix(cheat_input, ":reload-music") ||
1715 is_string_suffix(cheat_input, ":rm"))
1717 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1720 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1721 is_string_suffix(cheat_input, ":ra"))
1723 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1724 1 << ARTWORK_TYPE_SOUNDS |
1725 1 << ARTWORK_TYPE_MUSIC);
1728 else if (is_string_suffix(cheat_input, ":dump-level") ||
1729 is_string_suffix(cheat_input, ":dl"))
1733 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1734 is_string_suffix(cheat_input, ":dt"))
1738 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1739 is_string_suffix(cheat_input, ":ft"))
1741 /* fix single-player tapes that contain player input for more than one
1742 player (due to a bug in 3.3.1.2 and earlier versions), which results
1743 in playing levels with more than one player in multi-player mode,
1744 even though the tape was originally recorded in single-player mode */
1746 /* remove player input actions for all players but the first one */
1747 for (i = 1; i < MAX_PLAYERS; i++)
1748 tape.player_participates[i] = FALSE;
1750 tape.changed = TRUE;
1752 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1753 is_string_suffix(cheat_input, ":snl"))
1755 SaveNativeLevel(&level);
1757 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1758 is_string_suffix(cheat_input, ":fps"))
1760 global.show_frames_per_second = !global.show_frames_per_second;
1763 else if (game_status == GAME_MODE_PLAYING)
1766 if (is_string_suffix(cheat_input, ".q"))
1767 DEBUG_SetMaximumDynamite();
1770 else if (game_status == GAME_MODE_EDITOR)
1772 if (is_string_suffix(cheat_input, ":dump-brush") ||
1773 is_string_suffix(cheat_input, ":DB"))
1777 else if (is_string_suffix(cheat_input, ":DDB"))
1783 /* special key shortcuts for all game modes */
1784 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1785 is_string_suffix(cheat_input, ":dea") ||
1786 is_string_suffix(cheat_input, ":DEA"))
1788 DumpGadgetIdentifiers();
1789 DumpScreenIdentifiers();
1793 void HandleKeysDebug(Key key)
1798 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1800 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1802 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1804 if (key == setup.debug.frame_delay_key[i] &&
1805 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1807 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1808 setup.debug.frame_delay[i] : setup.game_frame_delay);
1810 if (!setup.debug.frame_delay_game_only)
1811 MenuFrameDelay = GameFrameDelay;
1813 SetVideoFrameDelay(GameFrameDelay);
1815 if (GameFrameDelay > ONE_SECOND_DELAY)
1816 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1817 else if (GameFrameDelay != 0)
1818 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1819 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1820 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1822 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1829 if (game_status == GAME_MODE_PLAYING)
1833 options.debug = !options.debug;
1835 Error(ERR_DEBUG, "debug mode %s",
1836 (options.debug ? "enabled" : "disabled"));
1838 else if (key == KSYM_v)
1840 Error(ERR_DEBUG, "currently using game engine version %d",
1841 game.engine_version);
1847 void HandleKey(Key key, int key_status)
1849 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1850 static boolean ignore_repeated_key = FALSE;
1851 static struct SetupKeyboardInfo ski;
1852 static struct SetupShortcutInfo ssi;
1861 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1862 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1863 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1864 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1865 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1866 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1871 #if defined(TARGET_SDL2)
1872 /* map special keys (media keys / remote control buttons) to default keys */
1873 if (key == KSYM_PlayPause)
1875 else if (key == KSYM_Select)
1879 HandleSpecialGameControllerKeys(key, key_status);
1881 if (game_status == GAME_MODE_PLAYING)
1883 /* only needed for single-step tape recording mode */
1884 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1887 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1889 byte key_action = 0;
1891 if (setup.input[pnr].use_joystick)
1894 ski = setup.input[pnr].key;
1896 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1897 if (key == *key_info[i].key_custom)
1898 key_action |= key_info[i].action;
1900 /* use combined snap+direction keys for the first player only */
1903 ssi = setup.shortcut;
1905 for (i = 0; i < NUM_DIRECTIONS; i++)
1906 if (key == *key_info[i].key_snap)
1907 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1910 if (key_status == KEY_PRESSED)
1911 stored_player[pnr].action |= key_action;
1913 stored_player[pnr].action &= ~key_action;
1915 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1917 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1919 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1921 /* if snap key already pressed, keep pause mode when releasing */
1922 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1923 has_snapped[pnr] = TRUE;
1925 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1927 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1929 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1930 getRedDiskReleaseFlag_SP() == 0)
1932 /* add a single inactive frame before dropping starts */
1933 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1934 stored_player[pnr].force_dropping = TRUE;
1937 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1939 /* if snap key was pressed without direction, leave pause mode */
1940 if (!has_snapped[pnr])
1941 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1943 has_snapped[pnr] = FALSE;
1946 else if (tape.recording && tape.pausing && !tape.use_mouse)
1948 /* prevent key release events from un-pausing a paused game */
1949 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1950 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1953 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1954 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1960 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1961 if (key == key_info[i].key_default)
1962 joy |= key_info[i].action;
1967 if (key_status == KEY_PRESSED)
1968 key_joystick_mapping |= joy;
1970 key_joystick_mapping &= ~joy;
1975 if (game_status != GAME_MODE_PLAYING)
1976 key_joystick_mapping = 0;
1978 if (key_status == KEY_RELEASED)
1980 // reset flag to ignore repeated "key pressed" events after key release
1981 ignore_repeated_key = FALSE;
1986 if ((key == KSYM_F11 ||
1987 ((key == KSYM_Return ||
1988 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1989 video.fullscreen_available &&
1990 !ignore_repeated_key)
1992 setup.fullscreen = !setup.fullscreen;
1994 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1996 if (game_status == GAME_MODE_SETUP)
1997 RedrawSetupScreenAfterFullscreenToggle();
1999 // set flag to ignore repeated "key pressed" events
2000 ignore_repeated_key = TRUE;
2005 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2006 key == KSYM_minus || key == KSYM_KP_Subtract ||
2007 key == KSYM_plus || key == KSYM_KP_Add ||
2008 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2009 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2010 video.window_scaling_available &&
2011 !video.fullscreen_enabled)
2013 if (key == KSYM_0 || key == KSYM_KP_0)
2014 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2015 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2016 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2018 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2020 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2021 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2022 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2023 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2025 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2027 if (game_status == GAME_MODE_SETUP)
2028 RedrawSetupScreenAfterFullscreenToggle();
2033 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2034 key == KSYM_Return ||
2035 key == KSYM_Escape)))
2037 /* do not handle this key event anymore */
2038 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2042 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2043 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2050 if (game_status == GAME_MODE_MAIN &&
2051 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2053 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2058 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2060 if (key == setup.shortcut.save_game)
2062 else if (key == setup.shortcut.load_game)
2064 else if (key == setup.shortcut.toggle_pause)
2065 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2067 HandleTapeButtonKeys(key);
2068 HandleSoundButtonKeys(key);
2071 if (game_status == GAME_MODE_PLAYING && !network_playing)
2073 int centered_player_nr_next = -999;
2075 if (key == setup.shortcut.focus_player_all)
2076 centered_player_nr_next = -1;
2078 for (i = 0; i < MAX_PLAYERS; i++)
2079 if (key == setup.shortcut.focus_player[i])
2080 centered_player_nr_next = i;
2082 if (centered_player_nr_next != -999)
2084 game.centered_player_nr_next = centered_player_nr_next;
2085 game.set_centered_player = TRUE;
2089 tape.centered_player_nr_next = game.centered_player_nr_next;
2090 tape.set_centered_player = TRUE;
2095 HandleKeysSpecial(key);
2097 if (HandleGadgetsKeyInput(key))
2099 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2100 key = KSYM_UNDEFINED;
2103 switch (game_status)
2105 case GAME_MODE_PSEUDO_TYPENAME:
2106 HandleTypeName(0, key);
2109 case GAME_MODE_TITLE:
2110 case GAME_MODE_MAIN:
2111 case GAME_MODE_LEVELS:
2112 case GAME_MODE_LEVELNR:
2113 case GAME_MODE_SETUP:
2114 case GAME_MODE_INFO:
2115 case GAME_MODE_SCORES:
2120 if (game_status == GAME_MODE_TITLE)
2121 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2122 else if (game_status == GAME_MODE_MAIN)
2123 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2124 else if (game_status == GAME_MODE_LEVELS)
2125 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2126 else if (game_status == GAME_MODE_LEVELNR)
2127 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2128 else if (game_status == GAME_MODE_SETUP)
2129 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2130 else if (game_status == GAME_MODE_INFO)
2131 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2132 else if (game_status == GAME_MODE_SCORES)
2133 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2137 if (game_status != GAME_MODE_MAIN)
2138 FadeSkipNextFadeIn();
2140 if (game_status == GAME_MODE_TITLE)
2141 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2142 else if (game_status == GAME_MODE_LEVELS)
2143 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2144 else if (game_status == GAME_MODE_LEVELNR)
2145 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2146 else if (game_status == GAME_MODE_SETUP)
2147 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2148 else if (game_status == GAME_MODE_INFO)
2149 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2150 else if (game_status == GAME_MODE_SCORES)
2151 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2155 if (game_status == GAME_MODE_LEVELS)
2156 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2157 else if (game_status == GAME_MODE_LEVELNR)
2158 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2159 else if (game_status == GAME_MODE_SETUP)
2160 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2161 else if (game_status == GAME_MODE_INFO)
2162 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2163 else if (game_status == GAME_MODE_SCORES)
2164 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2167 case KSYM_Page_Down:
2168 if (game_status == GAME_MODE_LEVELS)
2169 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2170 else if (game_status == GAME_MODE_LEVELNR)
2171 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2172 else if (game_status == GAME_MODE_SETUP)
2173 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2174 else if (game_status == GAME_MODE_INFO)
2175 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2176 else if (game_status == GAME_MODE_SCORES)
2177 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2185 case GAME_MODE_EDITOR:
2186 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2187 HandleLevelEditorKeyInput(key);
2190 case GAME_MODE_PLAYING:
2195 RequestQuitGame(setup.ask_on_escape);
2205 if (key == KSYM_Escape)
2207 SetGameStatus(GAME_MODE_MAIN);
2215 HandleKeysDebug(key);
2218 void HandleNoEvent(void)
2220 HandleMouseCursor();
2222 switch (game_status)
2224 case GAME_MODE_PLAYING:
2225 HandleButtonOrFinger(-1, -1, -1);
2230 void HandleEventActions(void)
2232 // if (button_status && game_status != GAME_MODE_PLAYING)
2233 if (button_status && (game_status != GAME_MODE_PLAYING ||
2235 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2237 HandleButton(0, 0, button_status, -button_status);
2244 if (network.enabled)
2247 switch (game_status)
2249 case GAME_MODE_MAIN:
2250 DrawPreviewLevelAnimation();
2253 case GAME_MODE_EDITOR:
2254 HandleLevelEditorIdle();
2262 static void HandleTileCursor(int dx, int dy, int button)
2265 ClearPlayerMouseAction();
2272 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2273 (dx < 0 ? MB_LEFTBUTTON :
2274 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2276 else if (!tile_cursor.moving)
2278 int old_xpos = tile_cursor.xpos;
2279 int old_ypos = tile_cursor.ypos;
2280 int new_xpos = old_xpos;
2281 int new_ypos = old_ypos;
2283 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2284 new_xpos = old_xpos + dx;
2286 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2287 new_ypos = old_ypos + dy;
2289 SetTileCursorTargetXY(new_xpos, new_ypos);
2293 static int HandleJoystickForAllPlayers(void)
2297 boolean no_joysticks_configured = TRUE;
2298 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2299 static byte joy_action_last[MAX_PLAYERS];
2301 for (i = 0; i < MAX_PLAYERS; i++)
2302 if (setup.input[i].use_joystick)
2303 no_joysticks_configured = FALSE;
2305 /* if no joysticks configured, map connected joysticks to players */
2306 if (no_joysticks_configured)
2307 use_as_joystick_nr = TRUE;
2309 for (i = 0; i < MAX_PLAYERS; i++)
2311 byte joy_action = 0;
2313 joy_action = JoystickExt(i, use_as_joystick_nr);
2314 result |= joy_action;
2316 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2317 joy_action != joy_action_last[i])
2318 stored_player[i].action = joy_action;
2320 joy_action_last[i] = joy_action;
2326 void HandleJoystick(void)
2328 static unsigned int joytest_delay = 0;
2329 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2330 static int joytest_last = 0;
2331 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2332 int delay_value = GADGET_FRAME_DELAY;
2333 int joystick = HandleJoystickForAllPlayers();
2334 int keyboard = key_joystick_mapping;
2335 int joy = (joystick | keyboard);
2336 int joytest = joystick;
2337 int left = joy & JOY_LEFT;
2338 int right = joy & JOY_RIGHT;
2339 int up = joy & JOY_UP;
2340 int down = joy & JOY_DOWN;
2341 int button = joy & JOY_BUTTON;
2342 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2343 int dx = (left ? -1 : right ? 1 : 0);
2344 int dy = (up ? -1 : down ? 1 : 0);
2345 boolean use_delay_value_first = (joytest != joytest_last);
2347 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2349 /* do not handle this button event anymore */
2353 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2355 if (game_status == GAME_MODE_PLAYING)
2357 // when playing MM style levels, also use delay for keyboard events
2358 joytest |= keyboard;
2360 // only use first delay value for new events, but not for changed events
2361 use_delay_value_first = (!joytest != !joytest_last);
2363 // only use delay after the initial keyboard event
2367 // for any joystick or keyboard event, enable playfield tile cursor
2368 if (dx || dy || button)
2369 SetTileCursorEnabled(TRUE);
2372 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2374 /* delay joystick/keyboard actions if axes/keys continually pressed */
2375 newbutton = dx = dy = 0;
2379 /* first start with longer delay, then continue with shorter delay */
2380 joytest_delay_value =
2381 (use_delay_value_first ? delay_value_first : delay_value);
2384 joytest_last = joytest;
2386 switch (game_status)
2388 case GAME_MODE_TITLE:
2389 case GAME_MODE_MAIN:
2390 case GAME_MODE_LEVELS:
2391 case GAME_MODE_LEVELNR:
2392 case GAME_MODE_SETUP:
2393 case GAME_MODE_INFO:
2394 case GAME_MODE_SCORES:
2396 if (game_status == GAME_MODE_TITLE)
2397 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2398 else if (game_status == GAME_MODE_MAIN)
2399 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2400 else if (game_status == GAME_MODE_LEVELS)
2401 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2402 else if (game_status == GAME_MODE_LEVELNR)
2403 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2404 else if (game_status == GAME_MODE_SETUP)
2405 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2406 else if (game_status == GAME_MODE_INFO)
2407 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2408 else if (game_status == GAME_MODE_SCORES)
2409 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2414 case GAME_MODE_PLAYING:
2416 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2417 if (tape.playing || keyboard)
2418 newbutton = ((joy & JOY_BUTTON) != 0);
2421 if (newbutton && AllPlayersGone)
2428 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2430 if (joystick & JOY_ACTION)
2431 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2433 else if (tape.recording && tape.pausing && !tape.use_mouse)
2435 if (joystick & JOY_ACTION)
2436 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2439 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2440 HandleTileCursor(dx, dy, button);
2449 void HandleSpecialGameControllerButtons(Event *event)
2451 #if defined(TARGET_SDL2)
2455 switch (event->type)
2457 case SDL_CONTROLLERBUTTONDOWN:
2458 key_status = KEY_PRESSED;
2461 case SDL_CONTROLLERBUTTONUP:
2462 key_status = KEY_RELEASED;
2469 switch (event->cbutton.button)
2471 case SDL_CONTROLLER_BUTTON_START:
2475 case SDL_CONTROLLER_BUTTON_BACK:
2483 HandleKey(key, key_status);
2487 void HandleSpecialGameControllerKeys(Key key, int key_status)
2489 #if defined(TARGET_SDL2)
2490 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2491 int button = SDL_CONTROLLER_BUTTON_INVALID;
2493 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2494 if (key == KSYM_Rewind)
2495 button = SDL_CONTROLLER_BUTTON_A;
2496 else if (key == KSYM_FastForward || key == KSYM_Menu)
2497 button = SDL_CONTROLLER_BUTTON_B;
2499 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2503 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2504 SDL_CONTROLLERBUTTONUP);
2506 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2507 event.cbutton.button = button;
2508 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2511 HandleJoystickEvent(&event);
2517 boolean DoKeysymAction(int keysym)
2521 Key key = (Key)(-keysym);
2523 HandleKey(key, KEY_PRESSED);
2524 HandleKey(key, KEY_RELEASED);