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 #if defined(TARGET_SDL2)
353 while (PendingEvent())
357 PeekEvent(&next_event);
359 /* if event is repeated key press event, remove it from event queue */
360 if (next_event.type == EVENT_KEYPRESS &&
361 next_event.key.repeat)
362 WaitEvent(&next_event);
369 void ClearEventQueue(void)
373 while (NextValidEvent(&event))
377 case EVENT_BUTTONRELEASE:
378 button_status = MB_RELEASED;
381 case EVENT_KEYRELEASE:
385 #if defined(TARGET_SDL2)
386 case SDL_CONTROLLERBUTTONUP:
387 HandleJoystickEvent(&event);
393 HandleOtherEvents(&event);
399 static void ClearPlayerMouseAction(void)
401 local_player->mouse_action.lx = 0;
402 local_player->mouse_action.ly = 0;
403 local_player->mouse_action.button = 0;
406 void ClearPlayerAction(void)
410 /* simulate key release events for still pressed keys */
411 key_joystick_mapping = 0;
412 for (i = 0; i < MAX_PLAYERS; i++)
413 stored_player[i].action = 0;
415 ClearJoystickState();
416 ClearPlayerMouseAction();
419 static void SetPlayerMouseAction(int mx, int my, int button)
421 int lx = getLevelFromScreenX(mx);
422 int ly = getLevelFromScreenY(my);
423 int new_button = (!local_player->mouse_action.button && button);
425 if (local_player->mouse_action.button_hint)
426 button = local_player->mouse_action.button_hint;
428 ClearPlayerMouseAction();
430 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
433 local_player->mouse_action.lx = lx;
434 local_player->mouse_action.ly = ly;
435 local_player->mouse_action.button = button;
437 if (tape.recording && tape.pausing && tape.use_mouse)
439 /* un-pause a paused game only if mouse button was newly pressed down */
441 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
444 SetTileCursorXY(lx, ly);
447 void SleepWhileUnmapped(void)
449 boolean window_unmapped = TRUE;
451 KeyboardAutoRepeatOn();
453 while (window_unmapped)
457 if (!WaitValidEvent(&event))
462 case EVENT_BUTTONRELEASE:
463 button_status = MB_RELEASED;
466 case EVENT_KEYRELEASE:
467 key_joystick_mapping = 0;
470 #if defined(TARGET_SDL2)
471 case SDL_CONTROLLERBUTTONUP:
472 HandleJoystickEvent(&event);
473 key_joystick_mapping = 0;
477 case EVENT_MAPNOTIFY:
478 window_unmapped = FALSE;
481 case EVENT_UNMAPNOTIFY:
482 /* this is only to surely prevent the 'should not happen' case
483 * of recursively looping between 'SleepWhileUnmapped()' and
484 * 'HandleOtherEvents()' which usually calls this funtion.
489 HandleOtherEvents(&event);
494 if (game_status == GAME_MODE_PLAYING)
495 KeyboardAutoRepeatOffUnlessAutoplay();
498 void HandleExposeEvent(ExposeEvent *event)
502 void HandleButtonEvent(ButtonEvent *event)
504 #if DEBUG_EVENTS_BUTTON
505 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
507 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
511 // for any mouse button event, disable playfield tile cursor
512 SetTileCursorEnabled(FALSE);
514 #if defined(HAS_SCREEN_KEYBOARD)
515 if (video.shifted_up)
516 event->y += video.shifted_up_pos;
519 motion_status = FALSE;
521 if (event->type == EVENT_BUTTONPRESS)
522 button_status = event->button;
524 button_status = MB_RELEASED;
526 HandleButton(event->x, event->y, button_status, event->button);
529 void HandleMotionEvent(MotionEvent *event)
531 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
534 motion_status = TRUE;
536 #if DEBUG_EVENTS_MOTION
537 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
538 button_status, event->x, event->y);
541 HandleButton(event->x, event->y, button_status, button_status);
544 #if defined(TARGET_SDL2)
546 void HandleWheelEvent(WheelEvent *event)
550 #if DEBUG_EVENTS_WHEEL
552 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
553 event->which, event->x, event->y);
555 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
556 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
557 event->which, event->x, event->y,
558 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
559 "SDL_MOUSEWHEEL_FLIPPED"));
563 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
564 event->x > 0 ? MB_WHEEL_RIGHT :
565 event->y < 0 ? MB_WHEEL_DOWN :
566 event->y > 0 ? MB_WHEEL_UP : 0);
568 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
569 // accelerated mouse wheel available on Mac and Windows
570 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
572 // no accelerated mouse wheel available on Unix/Linux
573 wheel_steps = DEFAULT_WHEEL_STEPS;
576 motion_status = FALSE;
578 button_status = button_nr;
579 HandleButton(0, 0, button_status, -button_nr);
581 button_status = MB_RELEASED;
582 HandleButton(0, 0, button_status, -button_nr);
585 void HandleWindowEvent(WindowEvent *event)
587 #if DEBUG_EVENTS_WINDOW
588 int subtype = event->event;
591 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
592 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
593 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
594 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
595 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
596 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
597 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
598 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
599 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
600 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
601 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
602 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
603 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
604 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
607 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
608 event_name, event->data1, event->data2);
612 // (not needed, as the screen gets redrawn every 20 ms anyway)
613 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
614 event->event == SDL_WINDOWEVENT_RESIZED ||
615 event->event == SDL_WINDOWEVENT_EXPOSED)
619 if (event->event == SDL_WINDOWEVENT_RESIZED)
621 if (!video.fullscreen_enabled)
623 int new_window_width = event->data1;
624 int new_window_height = event->data2;
626 // if window size has changed after resizing, calculate new scaling factor
627 if (new_window_width != video.window_width ||
628 new_window_height != video.window_height)
630 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
631 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
633 // (extreme window scaling allowed, but cannot be saved permanently)
634 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
635 setup.window_scaling_percent =
636 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
637 MAX_WINDOW_SCALING_PERCENT);
639 video.window_width = new_window_width;
640 video.window_height = new_window_height;
642 if (game_status == GAME_MODE_SETUP)
643 RedrawSetupScreenAfterFullscreenToggle();
648 #if defined(PLATFORM_ANDROID)
651 int new_display_width = event->data1;
652 int new_display_height = event->data2;
654 // if fullscreen display size has changed, device has been rotated
655 if (new_display_width != video.display_width ||
656 new_display_height != video.display_height)
658 int nr = GRID_ACTIVE_NR(); // previous screen orientation
660 video.display_width = new_display_width;
661 video.display_height = new_display_height;
663 SDLSetScreenProperties();
665 // check if screen orientation has changed (should always be true here)
666 if (nr != GRID_ACTIVE_NR())
670 if (game_status == GAME_MODE_SETUP)
671 RedrawSetupScreenAfterScreenRotation(nr);
673 nr = GRID_ACTIVE_NR();
675 overlay.grid_xsize = setup.touch.grid_xsize[nr];
676 overlay.grid_ysize = setup.touch.grid_ysize[nr];
678 for (x = 0; x < MAX_GRID_XSIZE; x++)
679 for (y = 0; y < MAX_GRID_YSIZE; y++)
680 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
688 #define NUM_TOUCH_FINGERS 3
693 SDL_FingerID finger_id;
696 } touch_info[NUM_TOUCH_FINGERS];
698 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
701 int x = event->x * overlay.grid_xsize;
702 int y = event->y * overlay.grid_ysize;
703 int grid_button = overlay.grid_button[x][y];
704 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
705 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
706 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
707 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
708 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
709 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
710 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
713 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
714 float event_x = (event->x);
715 float event_y = (event->y - ypos) / (1 - ypos);
716 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
717 event_y > 2.0 / 3.0 && event_y < 1 ?
718 setup.input[0].key.snap :
719 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
720 event_y > 2.0 / 3.0 && event_y < 1 ?
721 setup.input[0].key.drop :
722 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
723 event_y > 0 && event_y < 1.0 / 3.0 ?
724 setup.input[0].key.up :
725 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
726 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
727 setup.input[0].key.left :
728 event_x > 8.0 / 9.0 && event_x < 1 &&
729 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
730 setup.input[0].key.right :
731 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
732 event_y > 2.0 / 3.0 && event_y < 1 ?
733 setup.input[0].key.down :
736 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
738 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
742 // for any touch input event, enable overlay buttons (if activated)
743 SetOverlayEnabled(TRUE);
745 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
746 getKeyNameFromKey(key), key_status_name, event->fingerId);
748 if (key_status == KEY_PRESSED)
749 overlay.grid_button_action |= grid_button_action;
751 overlay.grid_button_action &= ~grid_button_action;
753 // check if we already know this touch event's finger id
754 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
756 if (touch_info[i].touched &&
757 touch_info[i].finger_id == event->fingerId)
759 // Error(ERR_DEBUG, "MARK 1: %d", i);
765 if (i >= NUM_TOUCH_FINGERS)
767 if (key_status == KEY_PRESSED)
769 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
771 // unknown finger id -- get new, empty slot, if available
772 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
774 if (touch_info[i].counter < oldest_counter)
777 oldest_counter = touch_info[i].counter;
779 // Error(ERR_DEBUG, "MARK 2: %d", i);
782 if (!touch_info[i].touched)
784 // Error(ERR_DEBUG, "MARK 3: %d", i);
790 if (i >= NUM_TOUCH_FINGERS)
792 // all slots allocated -- use oldest slot
795 // Error(ERR_DEBUG, "MARK 4: %d", i);
800 // release of previously unknown key (should not happen)
802 if (key != KSYM_UNDEFINED)
804 HandleKey(key, KEY_RELEASED);
806 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
807 getKeyNameFromKey(key), "KEY_RELEASED", i);
812 if (i < NUM_TOUCH_FINGERS)
814 if (key_status == KEY_PRESSED)
816 if (touch_info[i].key != key)
818 if (touch_info[i].key != KSYM_UNDEFINED)
820 HandleKey(touch_info[i].key, KEY_RELEASED);
822 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
823 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
826 if (key != KSYM_UNDEFINED)
828 HandleKey(key, KEY_PRESSED);
830 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
831 getKeyNameFromKey(key), "KEY_PRESSED", i);
835 touch_info[i].touched = TRUE;
836 touch_info[i].finger_id = event->fingerId;
837 touch_info[i].counter = Counter();
838 touch_info[i].key = key;
842 if (touch_info[i].key != KSYM_UNDEFINED)
844 HandleKey(touch_info[i].key, KEY_RELEASED);
846 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
847 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
850 touch_info[i].touched = FALSE;
851 touch_info[i].finger_id = 0;
852 touch_info[i].counter = 0;
853 touch_info[i].key = 0;
858 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
860 static Key motion_key_x = KSYM_UNDEFINED;
861 static Key motion_key_y = KSYM_UNDEFINED;
862 static Key button_key = KSYM_UNDEFINED;
863 static float motion_x1, motion_y1;
864 static float button_x1, button_y1;
865 static SDL_FingerID motion_id = -1;
866 static SDL_FingerID button_id = -1;
867 int move_trigger_distance_percent = setup.touch.move_distance;
868 int drop_trigger_distance_percent = setup.touch.drop_distance;
869 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
870 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
871 float event_x = event->x;
872 float event_y = event->y;
874 if (event->type == EVENT_FINGERPRESS)
876 if (event_x > 1.0 / 3.0)
880 motion_id = event->fingerId;
885 motion_key_x = KSYM_UNDEFINED;
886 motion_key_y = KSYM_UNDEFINED;
888 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
894 button_id = event->fingerId;
899 button_key = setup.input[0].key.snap;
901 HandleKey(button_key, KEY_PRESSED);
903 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
906 else if (event->type == EVENT_FINGERRELEASE)
908 if (event->fingerId == motion_id)
912 if (motion_key_x != KSYM_UNDEFINED)
913 HandleKey(motion_key_x, KEY_RELEASED);
914 if (motion_key_y != KSYM_UNDEFINED)
915 HandleKey(motion_key_y, KEY_RELEASED);
917 motion_key_x = KSYM_UNDEFINED;
918 motion_key_y = KSYM_UNDEFINED;
920 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
922 else if (event->fingerId == button_id)
926 if (button_key != KSYM_UNDEFINED)
927 HandleKey(button_key, KEY_RELEASED);
929 button_key = KSYM_UNDEFINED;
931 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
934 else if (event->type == EVENT_FINGERMOTION)
936 if (event->fingerId == motion_id)
938 float distance_x = ABS(event_x - motion_x1);
939 float distance_y = ABS(event_y - motion_y1);
940 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
941 event_x > motion_x1 ? setup.input[0].key.right :
943 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
944 event_y > motion_y1 ? setup.input[0].key.down :
947 if (distance_x < move_trigger_distance / 2 ||
948 distance_x < distance_y)
949 new_motion_key_x = KSYM_UNDEFINED;
951 if (distance_y < move_trigger_distance / 2 ||
952 distance_y < distance_x)
953 new_motion_key_y = KSYM_UNDEFINED;
955 if (distance_x > move_trigger_distance ||
956 distance_y > move_trigger_distance)
958 if (new_motion_key_x != motion_key_x)
960 if (motion_key_x != KSYM_UNDEFINED)
961 HandleKey(motion_key_x, KEY_RELEASED);
962 if (new_motion_key_x != KSYM_UNDEFINED)
963 HandleKey(new_motion_key_x, KEY_PRESSED);
966 if (new_motion_key_y != motion_key_y)
968 if (motion_key_y != KSYM_UNDEFINED)
969 HandleKey(motion_key_y, KEY_RELEASED);
970 if (new_motion_key_y != KSYM_UNDEFINED)
971 HandleKey(new_motion_key_y, KEY_PRESSED);
977 motion_key_x = new_motion_key_x;
978 motion_key_y = new_motion_key_y;
980 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
983 else if (event->fingerId == button_id)
985 float distance_x = ABS(event_x - button_x1);
986 float distance_y = ABS(event_y - button_y1);
988 if (distance_x < drop_trigger_distance / 2 &&
989 distance_y > drop_trigger_distance)
991 if (button_key == setup.input[0].key.snap)
992 HandleKey(button_key, KEY_RELEASED);
997 button_key = setup.input[0].key.drop;
999 HandleKey(button_key, KEY_PRESSED);
1001 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1007 void HandleFingerEvent(FingerEvent *event)
1009 #if DEBUG_EVENTS_FINGER
1010 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1011 event->type == EVENT_FINGERPRESS ? "pressed" :
1012 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1016 event->dx, event->dy,
1020 if (game_status != GAME_MODE_PLAYING)
1023 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1025 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1026 local_player->mouse_action.button_hint =
1027 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1028 event->x < 0.5 ? MB_LEFTBUTTON :
1029 event->x > 0.5 ? MB_RIGHTBUTTON :
1035 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1036 HandleFingerEvent_VirtualButtons(event);
1037 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1038 HandleFingerEvent_WipeGestures(event);
1043 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1045 static int old_mx = 0, old_my = 0;
1046 static int last_button = MB_LEFTBUTTON;
1047 static boolean touched = FALSE;
1048 static boolean tapped = FALSE;
1050 // screen tile was tapped (but finger not touching the screen anymore)
1051 // (this point will also be reached without receiving a touch event)
1052 if (tapped && !touched)
1054 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1059 // stop here if this function was not triggered by a touch event
1063 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1065 // finger started touching the screen
1075 ClearPlayerMouseAction();
1077 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1080 else if (button == MB_RELEASED && touched)
1082 // finger stopped touching the screen
1087 SetPlayerMouseAction(old_mx, old_my, last_button);
1089 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1091 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1096 // finger moved while touching the screen
1098 int old_x = getLevelFromScreenX(old_mx);
1099 int old_y = getLevelFromScreenY(old_my);
1100 int new_x = getLevelFromScreenX(mx);
1101 int new_y = getLevelFromScreenY(my);
1103 if (new_x != old_x || new_y != old_y)
1108 // finger moved left or right from (horizontal) starting position
1110 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1112 SetPlayerMouseAction(old_mx, old_my, button_nr);
1114 last_button = button_nr;
1116 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1120 // finger stays at or returned to (horizontal) starting position
1122 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1124 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1129 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1131 static int old_mx = 0, old_my = 0;
1132 static int last_button = MB_LEFTBUTTON;
1133 static boolean touched = FALSE;
1134 static boolean tapped = FALSE;
1136 // screen tile was tapped (but finger not touching the screen anymore)
1137 // (this point will also be reached without receiving a touch event)
1138 if (tapped && !touched)
1140 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1145 // stop here if this function was not triggered by a touch event
1149 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1151 // finger started touching the screen
1161 ClearPlayerMouseAction();
1163 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1166 else if (button == MB_RELEASED && touched)
1168 // finger stopped touching the screen
1173 SetPlayerMouseAction(old_mx, old_my, last_button);
1175 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1177 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1182 // finger moved while touching the screen
1184 int old_x = getLevelFromScreenX(old_mx);
1185 int old_y = getLevelFromScreenY(old_my);
1186 int new_x = getLevelFromScreenX(mx);
1187 int new_y = getLevelFromScreenY(my);
1189 if (new_x != old_x || new_y != old_y)
1191 // finger moved away from starting position
1193 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1195 // quickly alternate between clicking and releasing for maximum speed
1196 if (FrameCounter % 2 == 0)
1197 button_nr = MB_RELEASED;
1199 SetPlayerMouseAction(old_mx, old_my, button_nr);
1202 last_button = button_nr;
1206 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1210 // finger stays at or returned to starting position
1212 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1214 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1219 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1221 static int old_mx = 0, old_my = 0;
1222 static Key motion_key_x = KSYM_UNDEFINED;
1223 static Key motion_key_y = KSYM_UNDEFINED;
1224 static boolean touched = FALSE;
1225 static boolean started_on_player = FALSE;
1226 static boolean player_is_dropping = FALSE;
1227 static int player_drop_count = 0;
1228 static int last_player_x = -1;
1229 static int last_player_y = -1;
1231 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1240 started_on_player = FALSE;
1241 player_is_dropping = FALSE;
1242 player_drop_count = 0;
1246 motion_key_x = KSYM_UNDEFINED;
1247 motion_key_y = KSYM_UNDEFINED;
1249 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1252 else if (button == MB_RELEASED && touched)
1259 if (motion_key_x != KSYM_UNDEFINED)
1260 HandleKey(motion_key_x, KEY_RELEASED);
1261 if (motion_key_y != KSYM_UNDEFINED)
1262 HandleKey(motion_key_y, KEY_RELEASED);
1264 if (started_on_player)
1266 if (player_is_dropping)
1268 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1270 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1274 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1276 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1280 motion_key_x = KSYM_UNDEFINED;
1281 motion_key_y = KSYM_UNDEFINED;
1283 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1288 int src_x = local_player->jx;
1289 int src_y = local_player->jy;
1290 int dst_x = getLevelFromScreenX(old_mx);
1291 int dst_y = getLevelFromScreenY(old_my);
1292 int dx = dst_x - src_x;
1293 int dy = dst_y - src_y;
1294 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1295 dx > 0 ? setup.input[0].key.right :
1297 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1298 dy > 0 ? setup.input[0].key.down :
1301 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1302 (last_player_x != local_player->jx ||
1303 last_player_y != local_player->jy))
1305 // in case of asymmetric diagonal movement, use "preferred" direction
1307 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1309 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1310 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1312 local_player->last_move_dir = last_move_dir;
1314 // (required to prevent accidentally forcing direction for next movement)
1315 last_player_x = local_player->jx;
1316 last_player_y = local_player->jy;
1319 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1321 started_on_player = TRUE;
1322 player_drop_count = getPlayerInventorySize(0);
1323 player_is_dropping = (player_drop_count > 0);
1325 if (player_is_dropping)
1327 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1329 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1333 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1335 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1338 else if (dx != 0 || dy != 0)
1340 if (player_is_dropping &&
1341 player_drop_count == getPlayerInventorySize(0))
1343 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1345 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1346 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1348 player_is_dropping = FALSE;
1352 if (new_motion_key_x != motion_key_x)
1354 Error(ERR_DEBUG, "---------- %s %s ----------",
1355 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1356 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1358 if (motion_key_x != KSYM_UNDEFINED)
1359 HandleKey(motion_key_x, KEY_RELEASED);
1360 if (new_motion_key_x != KSYM_UNDEFINED)
1361 HandleKey(new_motion_key_x, KEY_PRESSED);
1364 if (new_motion_key_y != motion_key_y)
1366 Error(ERR_DEBUG, "---------- %s %s ----------",
1367 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1368 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1370 if (motion_key_y != KSYM_UNDEFINED)
1371 HandleKey(motion_key_y, KEY_RELEASED);
1372 if (new_motion_key_y != KSYM_UNDEFINED)
1373 HandleKey(new_motion_key_y, KEY_PRESSED);
1376 motion_key_x = new_motion_key_x;
1377 motion_key_y = new_motion_key_y;
1381 static void HandleButtonOrFinger(int mx, int my, int button)
1383 if (game_status != GAME_MODE_PLAYING)
1386 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1388 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1389 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1390 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1391 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1392 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1393 SetPlayerMouseAction(mx, my, button); /* special case */
1397 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1398 HandleButtonOrFinger_FollowFinger(mx, my, button);
1402 #if defined(TARGET_SDL2)
1404 static boolean checkTextInputKeyModState(void)
1406 // when playing, only handle raw key events and ignore text input
1407 if (game_status == GAME_MODE_PLAYING)
1410 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1413 void HandleTextEvent(TextEvent *event)
1415 char *text = event->text;
1416 Key key = getKeyFromKeyName(text);
1418 #if DEBUG_EVENTS_TEXT
1419 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1422 text[0], (int)(text[0]),
1424 getKeyNameFromKey(key),
1428 #if !defined(HAS_SCREEN_KEYBOARD)
1429 // non-mobile devices: only handle key input with modifier keys pressed here
1430 // (every other key input is handled directly as physical key input event)
1431 if (!checkTextInputKeyModState())
1435 // process text input as "classic" (with uppercase etc.) key input event
1436 HandleKey(key, KEY_PRESSED);
1437 HandleKey(key, KEY_RELEASED);
1440 void HandlePauseResumeEvent(PauseResumeEvent *event)
1442 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1446 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1454 void HandleKeyEvent(KeyEvent *event)
1456 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1457 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1458 Key key = GetEventKey(event, with_modifiers);
1459 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1461 #if DEBUG_EVENTS_KEY
1462 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1463 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1464 event->keysym.scancode,
1469 getKeyNameFromKey(key));
1472 #if defined(PLATFORM_ANDROID)
1473 if (key == KSYM_Back)
1475 // always map the "back" button to the "escape" key on Android devices
1480 // for any key event other than "back" button, disable overlay buttons
1481 SetOverlayEnabled(FALSE);
1485 HandleKeyModState(keymod, key_status);
1487 #if defined(TARGET_SDL2)
1488 // only handle raw key input without text modifier keys pressed
1489 if (!checkTextInputKeyModState())
1490 HandleKey(key, key_status);
1492 HandleKey(key, key_status);
1496 void HandleFocusEvent(FocusChangeEvent *event)
1498 static int old_joystick_status = -1;
1500 if (event->type == EVENT_FOCUSOUT)
1502 KeyboardAutoRepeatOn();
1503 old_joystick_status = joystick.status;
1504 joystick.status = JOYSTICK_NOT_AVAILABLE;
1506 ClearPlayerAction();
1508 else if (event->type == EVENT_FOCUSIN)
1510 /* When there are two Rocks'n'Diamonds windows which overlap and
1511 the player moves the pointer from one game window to the other,
1512 a 'FocusOut' event is generated for the window the pointer is
1513 leaving and a 'FocusIn' event is generated for the window the
1514 pointer is entering. In some cases, it can happen that the
1515 'FocusIn' event is handled by the one game process before the
1516 'FocusOut' event by the other game process. In this case the
1517 X11 environment would end up with activated keyboard auto repeat,
1518 because unfortunately this is a global setting and not (which
1519 would be far better) set for each X11 window individually.
1520 The effect would be keyboard auto repeat while playing the game
1521 (game_status == GAME_MODE_PLAYING), which is not desired.
1522 To avoid this special case, we just wait 1/10 second before
1523 processing the 'FocusIn' event.
1526 if (game_status == GAME_MODE_PLAYING)
1529 KeyboardAutoRepeatOffUnlessAutoplay();
1532 if (old_joystick_status != -1)
1533 joystick.status = old_joystick_status;
1537 void HandleClientMessageEvent(ClientMessageEvent *event)
1539 if (CheckCloseWindowEvent(event))
1543 void HandleWindowManagerEvent(Event *event)
1545 #if defined(TARGET_SDL)
1546 SDLHandleWindowManagerEvent(event);
1550 void HandleButton(int mx, int my, int button, int button_nr)
1552 static int old_mx = 0, old_my = 0;
1553 boolean button_hold = FALSE;
1554 boolean handle_gadgets = TRUE;
1560 button_nr = -button_nr;
1569 #if defined(PLATFORM_ANDROID)
1570 // when playing, only handle gadgets when using "follow finger" controls
1571 // or when using touch controls in combination with the MM game engine
1573 (game_status != GAME_MODE_PLAYING ||
1574 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1575 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1578 if (HandleGlobalAnimClicks(mx, my, button))
1580 /* do not handle this button event anymore */
1581 return; /* force mouse event not to be handled at all */
1584 if (handle_gadgets && HandleGadgets(mx, my, button))
1586 /* do not handle this button event anymore */
1587 mx = my = -32; /* force mouse event to be outside screen tiles */
1590 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1593 /* do not use scroll wheel button events for anything other than gadgets */
1594 if (IS_WHEEL_BUTTON(button_nr))
1597 switch (game_status)
1599 case GAME_MODE_TITLE:
1600 HandleTitleScreen(mx, my, 0, 0, button);
1603 case GAME_MODE_MAIN:
1604 HandleMainMenu(mx, my, 0, 0, button);
1607 case GAME_MODE_PSEUDO_TYPENAME:
1608 HandleTypeName(0, KSYM_Return);
1611 case GAME_MODE_LEVELS:
1612 HandleChooseLevelSet(mx, my, 0, 0, button);
1615 case GAME_MODE_LEVELNR:
1616 HandleChooseLevelNr(mx, my, 0, 0, button);
1619 case GAME_MODE_SCORES:
1620 HandleHallOfFame(0, 0, 0, 0, button);
1623 case GAME_MODE_EDITOR:
1624 HandleLevelEditorIdle();
1627 case GAME_MODE_INFO:
1628 HandleInfoScreen(mx, my, 0, 0, button);
1631 case GAME_MODE_SETUP:
1632 HandleSetupScreen(mx, my, 0, 0, button);
1635 case GAME_MODE_PLAYING:
1636 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1637 HandleButtonOrFinger(mx, my, button);
1639 SetPlayerMouseAction(mx, my, button);
1642 if (button == MB_PRESSED && !motion_status && !button_hold &&
1643 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1644 DumpTileFromScreen(mx, my);
1654 static boolean is_string_suffix(char *string, char *suffix)
1656 int string_len = strlen(string);
1657 int suffix_len = strlen(suffix);
1659 if (suffix_len > string_len)
1662 return (strEqual(&string[string_len - suffix_len], suffix));
1665 #define MAX_CHEAT_INPUT_LEN 32
1667 static void HandleKeysSpecial(Key key)
1669 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1670 char letter = getCharFromKey(key);
1671 int cheat_input_len = strlen(cheat_input);
1677 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1679 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1680 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1682 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1685 cheat_input[cheat_input_len++] = letter;
1686 cheat_input[cheat_input_len] = '\0';
1688 #if DEBUG_EVENTS_KEY
1689 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1692 if (game_status == GAME_MODE_MAIN)
1694 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1695 is_string_suffix(cheat_input, ":ist"))
1697 InsertSolutionTape();
1699 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1700 is_string_suffix(cheat_input, ":pst"))
1704 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1705 is_string_suffix(cheat_input, ":rg"))
1707 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1710 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1711 is_string_suffix(cheat_input, ":rs"))
1713 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1716 else if (is_string_suffix(cheat_input, ":reload-music") ||
1717 is_string_suffix(cheat_input, ":rm"))
1719 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1722 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1723 is_string_suffix(cheat_input, ":ra"))
1725 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1726 1 << ARTWORK_TYPE_SOUNDS |
1727 1 << ARTWORK_TYPE_MUSIC);
1730 else if (is_string_suffix(cheat_input, ":dump-level") ||
1731 is_string_suffix(cheat_input, ":dl"))
1735 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1736 is_string_suffix(cheat_input, ":dt"))
1740 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1741 is_string_suffix(cheat_input, ":ft"))
1743 /* fix single-player tapes that contain player input for more than one
1744 player (due to a bug in 3.3.1.2 and earlier versions), which results
1745 in playing levels with more than one player in multi-player mode,
1746 even though the tape was originally recorded in single-player mode */
1748 /* remove player input actions for all players but the first one */
1749 for (i = 1; i < MAX_PLAYERS; i++)
1750 tape.player_participates[i] = FALSE;
1752 tape.changed = TRUE;
1754 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1755 is_string_suffix(cheat_input, ":snl"))
1757 SaveNativeLevel(&level);
1759 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1760 is_string_suffix(cheat_input, ":fps"))
1762 global.show_frames_per_second = !global.show_frames_per_second;
1765 else if (game_status == GAME_MODE_PLAYING)
1768 if (is_string_suffix(cheat_input, ".q"))
1769 DEBUG_SetMaximumDynamite();
1772 else if (game_status == GAME_MODE_EDITOR)
1774 if (is_string_suffix(cheat_input, ":dump-brush") ||
1775 is_string_suffix(cheat_input, ":DB"))
1779 else if (is_string_suffix(cheat_input, ":DDB"))
1785 /* special key shortcuts for all game modes */
1786 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1787 is_string_suffix(cheat_input, ":dea") ||
1788 is_string_suffix(cheat_input, ":DEA"))
1790 DumpGadgetIdentifiers();
1791 DumpScreenIdentifiers();
1795 void HandleKeysDebug(Key key)
1800 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1802 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1804 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1806 if (key == setup.debug.frame_delay_key[i] &&
1807 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1809 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1810 setup.debug.frame_delay[i] : setup.game_frame_delay);
1812 if (!setup.debug.frame_delay_game_only)
1813 MenuFrameDelay = GameFrameDelay;
1815 SetVideoFrameDelay(GameFrameDelay);
1817 if (GameFrameDelay > ONE_SECOND_DELAY)
1818 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1819 else if (GameFrameDelay != 0)
1820 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1821 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1822 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1824 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1831 if (game_status == GAME_MODE_PLAYING)
1835 options.debug = !options.debug;
1837 Error(ERR_DEBUG, "debug mode %s",
1838 (options.debug ? "enabled" : "disabled"));
1840 else if (key == KSYM_v)
1842 Error(ERR_DEBUG, "currently using game engine version %d",
1843 game.engine_version);
1849 void HandleKey(Key key, int key_status)
1851 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1852 static boolean ignore_repeated_key = FALSE;
1853 static struct SetupKeyboardInfo ski;
1854 static struct SetupShortcutInfo ssi;
1863 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1864 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1865 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1866 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1867 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1868 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1873 #if defined(TARGET_SDL2)
1874 /* map special keys (media keys / remote control buttons) to default keys */
1875 if (key == KSYM_PlayPause)
1877 else if (key == KSYM_Select)
1881 HandleSpecialGameControllerKeys(key, key_status);
1883 if (game_status == GAME_MODE_PLAYING)
1885 /* only needed for single-step tape recording mode */
1886 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1889 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1891 byte key_action = 0;
1893 if (setup.input[pnr].use_joystick)
1896 ski = setup.input[pnr].key;
1898 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1899 if (key == *key_info[i].key_custom)
1900 key_action |= key_info[i].action;
1902 /* use combined snap+direction keys for the first player only */
1905 ssi = setup.shortcut;
1907 for (i = 0; i < NUM_DIRECTIONS; i++)
1908 if (key == *key_info[i].key_snap)
1909 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1912 if (key_status == KEY_PRESSED)
1913 stored_player[pnr].action |= key_action;
1915 stored_player[pnr].action &= ~key_action;
1917 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1919 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1921 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1923 /* if snap key already pressed, keep pause mode when releasing */
1924 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1925 has_snapped[pnr] = TRUE;
1927 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1929 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1931 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1932 getRedDiskReleaseFlag_SP() == 0)
1934 /* add a single inactive frame before dropping starts */
1935 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1936 stored_player[pnr].force_dropping = TRUE;
1939 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1941 /* if snap key was pressed without direction, leave pause mode */
1942 if (!has_snapped[pnr])
1943 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1945 has_snapped[pnr] = FALSE;
1948 else if (tape.recording && tape.pausing && !tape.use_mouse)
1950 /* prevent key release events from un-pausing a paused game */
1951 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1952 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1955 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1956 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1962 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1963 if (key == key_info[i].key_default)
1964 joy |= key_info[i].action;
1969 if (key_status == KEY_PRESSED)
1970 key_joystick_mapping |= joy;
1972 key_joystick_mapping &= ~joy;
1977 if (game_status != GAME_MODE_PLAYING)
1978 key_joystick_mapping = 0;
1980 if (key_status == KEY_RELEASED)
1982 // reset flag to ignore repeated "key pressed" events after key release
1983 ignore_repeated_key = FALSE;
1988 if ((key == KSYM_F11 ||
1989 ((key == KSYM_Return ||
1990 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1991 video.fullscreen_available &&
1992 !ignore_repeated_key)
1994 setup.fullscreen = !setup.fullscreen;
1996 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1998 if (game_status == GAME_MODE_SETUP)
1999 RedrawSetupScreenAfterFullscreenToggle();
2001 // set flag to ignore repeated "key pressed" events
2002 ignore_repeated_key = TRUE;
2007 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2008 key == KSYM_minus || key == KSYM_KP_Subtract ||
2009 key == KSYM_plus || key == KSYM_KP_Add ||
2010 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2011 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2012 video.window_scaling_available &&
2013 !video.fullscreen_enabled)
2015 if (key == KSYM_0 || key == KSYM_KP_0)
2016 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2017 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2018 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2020 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2022 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2023 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2024 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2025 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2027 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2029 if (game_status == GAME_MODE_SETUP)
2030 RedrawSetupScreenAfterFullscreenToggle();
2035 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2036 key == KSYM_Return ||
2037 key == KSYM_Escape)))
2039 /* do not handle this key event anymore */
2040 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2044 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2045 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2052 if (game_status == GAME_MODE_MAIN &&
2053 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2055 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2060 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2062 if (key == setup.shortcut.save_game)
2064 else if (key == setup.shortcut.load_game)
2066 else if (key == setup.shortcut.toggle_pause)
2067 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2069 HandleTapeButtonKeys(key);
2070 HandleSoundButtonKeys(key);
2073 if (game_status == GAME_MODE_PLAYING && !network_playing)
2075 int centered_player_nr_next = -999;
2077 if (key == setup.shortcut.focus_player_all)
2078 centered_player_nr_next = -1;
2080 for (i = 0; i < MAX_PLAYERS; i++)
2081 if (key == setup.shortcut.focus_player[i])
2082 centered_player_nr_next = i;
2084 if (centered_player_nr_next != -999)
2086 game.centered_player_nr_next = centered_player_nr_next;
2087 game.set_centered_player = TRUE;
2091 tape.centered_player_nr_next = game.centered_player_nr_next;
2092 tape.set_centered_player = TRUE;
2097 HandleKeysSpecial(key);
2099 if (HandleGadgetsKeyInput(key))
2101 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2102 key = KSYM_UNDEFINED;
2105 switch (game_status)
2107 case GAME_MODE_PSEUDO_TYPENAME:
2108 HandleTypeName(0, key);
2111 case GAME_MODE_TITLE:
2112 case GAME_MODE_MAIN:
2113 case GAME_MODE_LEVELS:
2114 case GAME_MODE_LEVELNR:
2115 case GAME_MODE_SETUP:
2116 case GAME_MODE_INFO:
2117 case GAME_MODE_SCORES:
2119 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2126 if (game_status == GAME_MODE_TITLE)
2127 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2128 else if (game_status == GAME_MODE_MAIN)
2129 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2130 else if (game_status == GAME_MODE_LEVELS)
2131 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2132 else if (game_status == GAME_MODE_LEVELNR)
2133 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2134 else if (game_status == GAME_MODE_SETUP)
2135 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2136 else if (game_status == GAME_MODE_INFO)
2137 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2138 else if (game_status == GAME_MODE_SCORES)
2139 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2143 if (game_status != GAME_MODE_MAIN)
2144 FadeSkipNextFadeIn();
2146 if (game_status == GAME_MODE_TITLE)
2147 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2148 else if (game_status == GAME_MODE_LEVELS)
2149 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2150 else if (game_status == GAME_MODE_LEVELNR)
2151 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2152 else if (game_status == GAME_MODE_SETUP)
2153 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2154 else if (game_status == GAME_MODE_INFO)
2155 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2156 else if (game_status == GAME_MODE_SCORES)
2157 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2161 if (game_status == GAME_MODE_LEVELS)
2162 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2163 else if (game_status == GAME_MODE_LEVELNR)
2164 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2165 else if (game_status == GAME_MODE_SETUP)
2166 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2167 else if (game_status == GAME_MODE_INFO)
2168 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2169 else if (game_status == GAME_MODE_SCORES)
2170 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2173 case KSYM_Page_Down:
2174 if (game_status == GAME_MODE_LEVELS)
2175 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2176 else if (game_status == GAME_MODE_LEVELNR)
2177 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2178 else if (game_status == GAME_MODE_SETUP)
2179 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2180 else if (game_status == GAME_MODE_INFO)
2181 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2182 else if (game_status == GAME_MODE_SCORES)
2183 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2191 case GAME_MODE_EDITOR:
2192 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2193 HandleLevelEditorKeyInput(key);
2196 case GAME_MODE_PLAYING:
2201 RequestQuitGame(setup.ask_on_escape);
2211 if (key == KSYM_Escape)
2213 SetGameStatus(GAME_MODE_MAIN);
2221 HandleKeysDebug(key);
2224 void HandleNoEvent(void)
2226 HandleMouseCursor();
2228 switch (game_status)
2230 case GAME_MODE_PLAYING:
2231 HandleButtonOrFinger(-1, -1, -1);
2236 void HandleEventActions(void)
2238 // if (button_status && game_status != GAME_MODE_PLAYING)
2239 if (button_status && (game_status != GAME_MODE_PLAYING ||
2241 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2243 HandleButton(0, 0, button_status, -button_status);
2250 if (network.enabled)
2253 switch (game_status)
2255 case GAME_MODE_MAIN:
2256 DrawPreviewLevelAnimation();
2259 case GAME_MODE_EDITOR:
2260 HandleLevelEditorIdle();
2268 static void HandleTileCursor(int dx, int dy, int button)
2271 ClearPlayerMouseAction();
2278 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2279 (dx < 0 ? MB_LEFTBUTTON :
2280 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2282 else if (!tile_cursor.moving)
2284 int old_xpos = tile_cursor.xpos;
2285 int old_ypos = tile_cursor.ypos;
2286 int new_xpos = old_xpos;
2287 int new_ypos = old_ypos;
2289 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2290 new_xpos = old_xpos + dx;
2292 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2293 new_ypos = old_ypos + dy;
2295 SetTileCursorTargetXY(new_xpos, new_ypos);
2299 static int HandleJoystickForAllPlayers(void)
2303 boolean no_joysticks_configured = TRUE;
2304 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2305 static byte joy_action_last[MAX_PLAYERS];
2307 for (i = 0; i < MAX_PLAYERS; i++)
2308 if (setup.input[i].use_joystick)
2309 no_joysticks_configured = FALSE;
2311 /* if no joysticks configured, map connected joysticks to players */
2312 if (no_joysticks_configured)
2313 use_as_joystick_nr = TRUE;
2315 for (i = 0; i < MAX_PLAYERS; i++)
2317 byte joy_action = 0;
2319 joy_action = JoystickExt(i, use_as_joystick_nr);
2320 result |= joy_action;
2322 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2323 joy_action != joy_action_last[i])
2324 stored_player[i].action = joy_action;
2326 joy_action_last[i] = joy_action;
2332 void HandleJoystick(void)
2334 static unsigned int joytest_delay = 0;
2335 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2336 static int joytest_last = 0;
2337 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2338 int delay_value = GADGET_FRAME_DELAY;
2339 int joystick = HandleJoystickForAllPlayers();
2340 int keyboard = key_joystick_mapping;
2341 int joy = (joystick | keyboard);
2342 int joytest = joystick;
2343 int left = joy & JOY_LEFT;
2344 int right = joy & JOY_RIGHT;
2345 int up = joy & JOY_UP;
2346 int down = joy & JOY_DOWN;
2347 int button = joy & JOY_BUTTON;
2348 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2349 int dx = (left ? -1 : right ? 1 : 0);
2350 int dy = (up ? -1 : down ? 1 : 0);
2351 boolean use_delay_value_first = (joytest != joytest_last);
2353 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2355 /* do not handle this button event anymore */
2359 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2361 if (game_status == GAME_MODE_PLAYING)
2363 // when playing MM style levels, also use delay for keyboard events
2364 joytest |= keyboard;
2366 // only use first delay value for new events, but not for changed events
2367 use_delay_value_first = (!joytest != !joytest_last);
2369 // only use delay after the initial keyboard event
2373 // for any joystick or keyboard event, enable playfield tile cursor
2374 if (dx || dy || button)
2375 SetTileCursorEnabled(TRUE);
2378 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2380 /* delay joystick/keyboard actions if axes/keys continually pressed */
2381 newbutton = dx = dy = 0;
2385 /* first start with longer delay, then continue with shorter delay */
2386 joytest_delay_value =
2387 (use_delay_value_first ? delay_value_first : delay_value);
2390 joytest_last = joytest;
2392 switch (game_status)
2394 case GAME_MODE_TITLE:
2395 case GAME_MODE_MAIN:
2396 case GAME_MODE_LEVELS:
2397 case GAME_MODE_LEVELNR:
2398 case GAME_MODE_SETUP:
2399 case GAME_MODE_INFO:
2400 case GAME_MODE_SCORES:
2402 if (anyTextGadgetActive())
2405 if (game_status == GAME_MODE_TITLE)
2406 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2407 else if (game_status == GAME_MODE_MAIN)
2408 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2409 else if (game_status == GAME_MODE_LEVELS)
2410 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2411 else if (game_status == GAME_MODE_LEVELNR)
2412 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2413 else if (game_status == GAME_MODE_SETUP)
2414 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2415 else if (game_status == GAME_MODE_INFO)
2416 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2417 else if (game_status == GAME_MODE_SCORES)
2418 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2423 case GAME_MODE_PLAYING:
2425 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2426 if (tape.playing || keyboard)
2427 newbutton = ((joy & JOY_BUTTON) != 0);
2430 if (newbutton && AllPlayersGone)
2437 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2439 if (joystick & JOY_ACTION)
2440 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2442 else if (tape.recording && tape.pausing && !tape.use_mouse)
2444 if (joystick & JOY_ACTION)
2445 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2448 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2449 HandleTileCursor(dx, dy, button);
2458 void HandleSpecialGameControllerButtons(Event *event)
2460 #if defined(TARGET_SDL2)
2464 switch (event->type)
2466 case SDL_CONTROLLERBUTTONDOWN:
2467 key_status = KEY_PRESSED;
2470 case SDL_CONTROLLERBUTTONUP:
2471 key_status = KEY_RELEASED;
2478 switch (event->cbutton.button)
2480 case SDL_CONTROLLER_BUTTON_START:
2484 case SDL_CONTROLLER_BUTTON_BACK:
2492 HandleKey(key, key_status);
2496 void HandleSpecialGameControllerKeys(Key key, int key_status)
2498 #if defined(TARGET_SDL2)
2499 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2500 int button = SDL_CONTROLLER_BUTTON_INVALID;
2502 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2503 if (key == KSYM_Rewind)
2504 button = SDL_CONTROLLER_BUTTON_A;
2505 else if (key == KSYM_FastForward || key == KSYM_Menu)
2506 button = SDL_CONTROLLER_BUTTON_B;
2508 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2512 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2513 SDL_CONTROLLERBUTTONUP);
2515 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2516 event.cbutton.button = button;
2517 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2520 HandleJoystickEvent(&event);
2526 boolean DoKeysymAction(int keysym)
2530 Key key = (Key)(-keysym);
2532 HandleKey(key, KEY_PRESSED);
2533 HandleKey(key, KEY_RELEASED);