1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
43 /* forward declarations for internal use */
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 /* event filter especially needed for SDL event filtering due to
49 delay problems with lots of mouse motion events when mouse button
50 not pressed (X11 can handle this with 'PointerMotionHintMask') */
52 /* event filter addition for SDL2: as SDL2 does not have a function to enable
53 or disable keyboard auto-repeat, filter repeated keyboard events instead */
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 /* skip repeated key press events if keyboard auto-repeat is disabled */
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 /* non-motion events are directly passed to event handler functions */
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 /* do no reset mouse cursor before all pending events have been processed */
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 /* skip mouse motion events without pressed button outside level editor */
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 /* to prevent delay problems, skip mouse motion events if the very next
110 event is also a mouse motion event (and therefore effectively only
111 handling the last of a row of mouse motion events in the event queue) */
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 /* nothing to do if the current event is not a mouse motion event */
116 if (event->type != EVENT_MOTIONNOTIFY)
119 /* only skip motion events with pressed button outside the game */
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 /* if next event is also a mouse motion event, skip the current one */
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 /* This causes the game to stop not only when iconified, but also
242 when on another virtual desktop, which might be not desired. */
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 void HandleMouseCursor()
288 if (game_status == GAME_MODE_TITLE)
290 /* when showing title screens, hide mouse pointer (if not moved) */
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 /* when playing, display a special mouse pointer inside the playfield */
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
309 SetMouseCursor(CURSOR_PLAYFIELD);
312 else if (gfx.cursor_mode != CURSOR_DEFAULT)
314 SetMouseCursor(CURSOR_DEFAULT);
317 /* this is set after all pending events have been processed */
318 cursor_mode_last = gfx.cursor_mode;
330 /* execute event related actions after pending events have been processed */
331 HandleEventActions();
333 /* don't use all CPU time when idle; the main loop while playing
334 has its own synchronization and is CPU friendly, too */
336 if (game_status == GAME_MODE_PLAYING)
339 /* always copy backbuffer to visible screen for every video frame */
342 /* reset video frame delay to default (may change again while playing) */
343 SetVideoFrameDelay(MenuFrameDelay);
345 if (game_status == GAME_MODE_QUIT)
350 void ClearEventQueue()
354 while (NextValidEvent(&event))
358 case EVENT_BUTTONRELEASE:
359 button_status = MB_RELEASED;
362 case EVENT_KEYRELEASE:
366 #if defined(TARGET_SDL2)
367 case SDL_CONTROLLERBUTTONUP:
368 HandleJoystickEvent(&event);
374 HandleOtherEvents(&event);
380 void ClearPlayerMouseAction()
382 local_player->mouse_action.lx = 0;
383 local_player->mouse_action.ly = 0;
384 local_player->mouse_action.button = 0;
387 void ClearPlayerAction()
391 /* simulate key release events for still pressed keys */
392 key_joystick_mapping = 0;
393 for (i = 0; i < MAX_PLAYERS; i++)
394 stored_player[i].action = 0;
396 ClearJoystickState();
397 ClearPlayerMouseAction();
400 void SetPlayerMouseAction(int mx, int my, int button)
402 int lx = getLevelFromScreenX(mx);
403 int ly = getLevelFromScreenY(my);
404 int new_button = (!local_player->mouse_action.button && button);
406 if (local_player->mouse_action.button_hint)
407 button = local_player->mouse_action.button_hint;
409 ClearPlayerMouseAction();
411 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
414 local_player->mouse_action.lx = lx;
415 local_player->mouse_action.ly = ly;
416 local_player->mouse_action.button = button;
418 if (tape.recording && tape.pausing && tape.use_mouse)
420 /* un-pause a paused game only if mouse button was newly pressed down */
422 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
425 SetTileCursorXY(lx, ly);
428 void SleepWhileUnmapped()
430 boolean window_unmapped = TRUE;
432 KeyboardAutoRepeatOn();
434 while (window_unmapped)
438 if (!WaitValidEvent(&event))
443 case EVENT_BUTTONRELEASE:
444 button_status = MB_RELEASED;
447 case EVENT_KEYRELEASE:
448 key_joystick_mapping = 0;
451 #if defined(TARGET_SDL2)
452 case SDL_CONTROLLERBUTTONUP:
453 HandleJoystickEvent(&event);
454 key_joystick_mapping = 0;
458 case EVENT_MAPNOTIFY:
459 window_unmapped = FALSE;
462 case EVENT_UNMAPNOTIFY:
463 /* this is only to surely prevent the 'should not happen' case
464 * of recursively looping between 'SleepWhileUnmapped()' and
465 * 'HandleOtherEvents()' which usually calls this funtion.
470 HandleOtherEvents(&event);
475 if (game_status == GAME_MODE_PLAYING)
476 KeyboardAutoRepeatOffUnlessAutoplay();
479 void HandleExposeEvent(ExposeEvent *event)
483 void HandleButtonEvent(ButtonEvent *event)
485 #if DEBUG_EVENTS_BUTTON
486 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
488 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
492 // for any mouse button event, disable playfield tile cursor
493 SetTileCursorEnabled(FALSE);
495 #if defined(HAS_SCREEN_KEYBOARD)
496 if (video.shifted_up)
497 event->y += video.shifted_up_pos;
500 motion_status = FALSE;
502 if (event->type == EVENT_BUTTONPRESS)
503 button_status = event->button;
505 button_status = MB_RELEASED;
507 HandleButton(event->x, event->y, button_status, event->button);
510 void HandleMotionEvent(MotionEvent *event)
512 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
515 motion_status = TRUE;
517 #if DEBUG_EVENTS_MOTION
518 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
519 button_status, event->x, event->y);
522 HandleButton(event->x, event->y, button_status, button_status);
525 #if defined(TARGET_SDL2)
527 void HandleWheelEvent(WheelEvent *event)
531 #if DEBUG_EVENTS_WHEEL
533 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
534 event->which, event->x, event->y);
536 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
537 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
538 event->which, event->x, event->y,
539 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
540 "SDL_MOUSEWHEEL_FLIPPED"));
544 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
545 event->x > 0 ? MB_WHEEL_RIGHT :
546 event->y < 0 ? MB_WHEEL_DOWN :
547 event->y > 0 ? MB_WHEEL_UP : 0);
549 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
550 // accelerated mouse wheel available on Mac and Windows
551 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
553 // no accelerated mouse wheel available on Unix/Linux
554 wheel_steps = DEFAULT_WHEEL_STEPS;
557 motion_status = FALSE;
559 button_status = button_nr;
560 HandleButton(0, 0, button_status, -button_nr);
562 button_status = MB_RELEASED;
563 HandleButton(0, 0, button_status, -button_nr);
566 void HandleWindowEvent(WindowEvent *event)
568 #if DEBUG_EVENTS_WINDOW
569 int subtype = event->event;
572 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
573 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
574 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
575 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
576 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
577 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
578 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
579 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
580 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
581 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
582 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
583 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
584 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
585 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
588 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
589 event_name, event->data1, event->data2);
593 // (not needed, as the screen gets redrawn every 20 ms anyway)
594 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
595 event->event == SDL_WINDOWEVENT_RESIZED ||
596 event->event == SDL_WINDOWEVENT_EXPOSED)
600 if (event->event == SDL_WINDOWEVENT_RESIZED)
602 if (!video.fullscreen_enabled)
604 int new_window_width = event->data1;
605 int new_window_height = event->data2;
607 // if window size has changed after resizing, calculate new scaling factor
608 if (new_window_width != video.window_width ||
609 new_window_height != video.window_height)
611 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
612 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
614 // (extreme window scaling allowed, but cannot be saved permanently)
615 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
616 setup.window_scaling_percent =
617 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
618 MAX_WINDOW_SCALING_PERCENT);
620 video.window_width = new_window_width;
621 video.window_height = new_window_height;
623 if (game_status == GAME_MODE_SETUP)
624 RedrawSetupScreenAfterFullscreenToggle();
629 #if defined(PLATFORM_ANDROID)
632 int new_display_width = event->data1;
633 int new_display_height = event->data2;
635 // if fullscreen display size has changed, device has been rotated
636 if (new_display_width != video.display_width ||
637 new_display_height != video.display_height)
639 int nr = GRID_ACTIVE_NR(); // previous screen orientation
641 video.display_width = new_display_width;
642 video.display_height = new_display_height;
644 SDLSetScreenProperties();
646 // check if screen orientation has changed (should always be true here)
647 if (nr != GRID_ACTIVE_NR())
651 if (game_status == GAME_MODE_SETUP)
652 RedrawSetupScreenAfterScreenRotation(nr);
654 nr = GRID_ACTIVE_NR();
656 overlay.grid_xsize = setup.touch.grid_xsize[nr];
657 overlay.grid_ysize = setup.touch.grid_ysize[nr];
659 for (x = 0; x < MAX_GRID_XSIZE; x++)
660 for (y = 0; y < MAX_GRID_YSIZE; y++)
661 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
669 #define NUM_TOUCH_FINGERS 3
674 SDL_FingerID finger_id;
677 } touch_info[NUM_TOUCH_FINGERS];
679 void HandleFingerEvent_VirtualButtons(FingerEvent *event)
682 int x = event->x * overlay.grid_xsize;
683 int y = event->y * overlay.grid_ysize;
684 int grid_button = overlay.grid_button[x][y];
685 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
686 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
687 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
688 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
689 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
690 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
691 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
694 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
695 float event_x = (event->x);
696 float event_y = (event->y - ypos) / (1 - ypos);
697 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
698 event_y > 2.0 / 3.0 && event_y < 1 ?
699 setup.input[0].key.snap :
700 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
701 event_y > 2.0 / 3.0 && event_y < 1 ?
702 setup.input[0].key.drop :
703 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
704 event_y > 0 && event_y < 1.0 / 3.0 ?
705 setup.input[0].key.up :
706 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
707 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
708 setup.input[0].key.left :
709 event_x > 8.0 / 9.0 && event_x < 1 &&
710 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
711 setup.input[0].key.right :
712 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
713 event_y > 2.0 / 3.0 && event_y < 1 ?
714 setup.input[0].key.down :
717 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
719 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
723 // for any touch input event, enable overlay buttons (if activated)
724 SetOverlayEnabled(TRUE);
726 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
727 getKeyNameFromKey(key), key_status_name, event->fingerId);
729 if (key_status == KEY_PRESSED)
730 overlay.grid_button_action |= grid_button_action;
732 overlay.grid_button_action &= ~grid_button_action;
734 // check if we already know this touch event's finger id
735 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
737 if (touch_info[i].touched &&
738 touch_info[i].finger_id == event->fingerId)
740 // Error(ERR_DEBUG, "MARK 1: %d", i);
746 if (i >= NUM_TOUCH_FINGERS)
748 if (key_status == KEY_PRESSED)
750 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
752 // unknown finger id -- get new, empty slot, if available
753 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755 if (touch_info[i].counter < oldest_counter)
758 oldest_counter = touch_info[i].counter;
760 // Error(ERR_DEBUG, "MARK 2: %d", i);
763 if (!touch_info[i].touched)
765 // Error(ERR_DEBUG, "MARK 3: %d", i);
771 if (i >= NUM_TOUCH_FINGERS)
773 // all slots allocated -- use oldest slot
776 // Error(ERR_DEBUG, "MARK 4: %d", i);
781 // release of previously unknown key (should not happen)
783 if (key != KSYM_UNDEFINED)
785 HandleKey(key, KEY_RELEASED);
787 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
788 getKeyNameFromKey(key), "KEY_RELEASED", i);
793 if (i < NUM_TOUCH_FINGERS)
795 if (key_status == KEY_PRESSED)
797 if (touch_info[i].key != key)
799 if (touch_info[i].key != KSYM_UNDEFINED)
801 HandleKey(touch_info[i].key, KEY_RELEASED);
803 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
804 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
807 if (key != KSYM_UNDEFINED)
809 HandleKey(key, KEY_PRESSED);
811 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
812 getKeyNameFromKey(key), "KEY_PRESSED", i);
816 touch_info[i].touched = TRUE;
817 touch_info[i].finger_id = event->fingerId;
818 touch_info[i].counter = Counter();
819 touch_info[i].key = key;
823 if (touch_info[i].key != KSYM_UNDEFINED)
825 HandleKey(touch_info[i].key, KEY_RELEASED);
827 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
828 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
831 touch_info[i].touched = FALSE;
832 touch_info[i].finger_id = 0;
833 touch_info[i].counter = 0;
834 touch_info[i].key = 0;
839 void HandleFingerEvent_WipeGestures(FingerEvent *event)
841 static Key motion_key_x = KSYM_UNDEFINED;
842 static Key motion_key_y = KSYM_UNDEFINED;
843 static Key button_key = KSYM_UNDEFINED;
844 static float motion_x1, motion_y1;
845 static float button_x1, button_y1;
846 static SDL_FingerID motion_id = -1;
847 static SDL_FingerID button_id = -1;
848 int move_trigger_distance_percent = setup.touch.move_distance;
849 int drop_trigger_distance_percent = setup.touch.drop_distance;
850 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
851 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
852 float event_x = event->x;
853 float event_y = event->y;
855 if (event->type == EVENT_FINGERPRESS)
857 if (event_x > 1.0 / 3.0)
861 motion_id = event->fingerId;
866 motion_key_x = KSYM_UNDEFINED;
867 motion_key_y = KSYM_UNDEFINED;
869 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
875 button_id = event->fingerId;
880 button_key = setup.input[0].key.snap;
882 HandleKey(button_key, KEY_PRESSED);
884 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
887 else if (event->type == EVENT_FINGERRELEASE)
889 if (event->fingerId == motion_id)
893 if (motion_key_x != KSYM_UNDEFINED)
894 HandleKey(motion_key_x, KEY_RELEASED);
895 if (motion_key_y != KSYM_UNDEFINED)
896 HandleKey(motion_key_y, KEY_RELEASED);
898 motion_key_x = KSYM_UNDEFINED;
899 motion_key_y = KSYM_UNDEFINED;
901 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
903 else if (event->fingerId == button_id)
907 if (button_key != KSYM_UNDEFINED)
908 HandleKey(button_key, KEY_RELEASED);
910 button_key = KSYM_UNDEFINED;
912 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
915 else if (event->type == EVENT_FINGERMOTION)
917 if (event->fingerId == motion_id)
919 float distance_x = ABS(event_x - motion_x1);
920 float distance_y = ABS(event_y - motion_y1);
921 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
922 event_x > motion_x1 ? setup.input[0].key.right :
924 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
925 event_y > motion_y1 ? setup.input[0].key.down :
928 if (distance_x < move_trigger_distance / 2 ||
929 distance_x < distance_y)
930 new_motion_key_x = KSYM_UNDEFINED;
932 if (distance_y < move_trigger_distance / 2 ||
933 distance_y < distance_x)
934 new_motion_key_y = KSYM_UNDEFINED;
936 if (distance_x > move_trigger_distance ||
937 distance_y > move_trigger_distance)
939 if (new_motion_key_x != motion_key_x)
941 if (motion_key_x != KSYM_UNDEFINED)
942 HandleKey(motion_key_x, KEY_RELEASED);
943 if (new_motion_key_x != KSYM_UNDEFINED)
944 HandleKey(new_motion_key_x, KEY_PRESSED);
947 if (new_motion_key_y != motion_key_y)
949 if (motion_key_y != KSYM_UNDEFINED)
950 HandleKey(motion_key_y, KEY_RELEASED);
951 if (new_motion_key_y != KSYM_UNDEFINED)
952 HandleKey(new_motion_key_y, KEY_PRESSED);
958 motion_key_x = new_motion_key_x;
959 motion_key_y = new_motion_key_y;
961 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
964 else if (event->fingerId == button_id)
966 float distance_x = ABS(event_x - button_x1);
967 float distance_y = ABS(event_y - button_y1);
969 if (distance_x < drop_trigger_distance / 2 &&
970 distance_y > drop_trigger_distance)
972 if (button_key == setup.input[0].key.snap)
973 HandleKey(button_key, KEY_RELEASED);
978 button_key = setup.input[0].key.drop;
980 HandleKey(button_key, KEY_PRESSED);
982 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
988 void HandleFingerEvent(FingerEvent *event)
990 #if DEBUG_EVENTS_FINGER
991 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
992 event->type == EVENT_FINGERPRESS ? "pressed" :
993 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
997 event->dx, event->dy,
1001 if (game_status != GAME_MODE_PLAYING)
1004 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1006 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1007 local_player->mouse_action.button_hint =
1008 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1009 event->x < 0.5 ? MB_LEFTBUTTON :
1010 event->x > 0.5 ? MB_RIGHTBUTTON :
1016 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1017 HandleFingerEvent_VirtualButtons(event);
1018 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1019 HandleFingerEvent_WipeGestures(event);
1024 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1026 static int old_mx = 0, old_my = 0;
1027 static int last_button = MB_LEFTBUTTON;
1028 static boolean touched = FALSE;
1029 static boolean tapped = FALSE;
1031 // screen tile was tapped (but finger not touching the screen anymore)
1032 // (this point will also be reached without receiving a touch event)
1033 if (tapped && !touched)
1035 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1040 // stop here if this function was not triggered by a touch event
1044 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1046 // finger started touching the screen
1056 ClearPlayerMouseAction();
1058 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1061 else if (button == MB_RELEASED && touched)
1063 // finger stopped touching the screen
1068 SetPlayerMouseAction(old_mx, old_my, last_button);
1070 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1072 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1077 // finger moved while touching the screen
1079 int old_x = getLevelFromScreenX(old_mx);
1080 int old_y = getLevelFromScreenY(old_my);
1081 int new_x = getLevelFromScreenX(mx);
1082 int new_y = getLevelFromScreenY(my);
1084 if (new_x != old_x || new_y != old_y)
1089 // finger moved left or right from (horizontal) starting position
1091 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1093 SetPlayerMouseAction(old_mx, old_my, button_nr);
1095 last_button = button_nr;
1097 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1101 // finger stays at or returned to (horizontal) starting position
1103 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1105 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1110 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1112 static int old_mx = 0, old_my = 0;
1113 static int last_button = MB_LEFTBUTTON;
1114 static boolean touched = FALSE;
1115 static boolean tapped = FALSE;
1117 // screen tile was tapped (but finger not touching the screen anymore)
1118 // (this point will also be reached without receiving a touch event)
1119 if (tapped && !touched)
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1126 // stop here if this function was not triggered by a touch event
1130 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1132 // finger started touching the screen
1142 ClearPlayerMouseAction();
1144 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1147 else if (button == MB_RELEASED && touched)
1149 // finger stopped touching the screen
1154 SetPlayerMouseAction(old_mx, old_my, last_button);
1156 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1158 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1163 // finger moved while touching the screen
1165 int old_x = getLevelFromScreenX(old_mx);
1166 int old_y = getLevelFromScreenY(old_my);
1167 int new_x = getLevelFromScreenX(mx);
1168 int new_y = getLevelFromScreenY(my);
1170 if (new_x != old_x || new_y != old_y)
1172 // finger moved away from starting position
1174 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1176 // quickly alternate between clicking and releasing for maximum speed
1177 if (FrameCounter % 2 == 0)
1178 button_nr = MB_RELEASED;
1180 SetPlayerMouseAction(old_mx, old_my, button_nr);
1183 last_button = button_nr;
1187 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1191 // finger stays at or returned to starting position
1193 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1195 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1200 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1202 static int old_mx = 0, old_my = 0;
1203 static Key motion_key_x = KSYM_UNDEFINED;
1204 static Key motion_key_y = KSYM_UNDEFINED;
1205 static boolean touched = FALSE;
1206 static boolean started_on_player = FALSE;
1207 static boolean player_is_dropping = FALSE;
1208 static int player_drop_count = 0;
1209 static int last_player_x = -1;
1210 static int last_player_y = -1;
1212 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1221 started_on_player = FALSE;
1222 player_is_dropping = FALSE;
1223 player_drop_count = 0;
1227 motion_key_x = KSYM_UNDEFINED;
1228 motion_key_y = KSYM_UNDEFINED;
1230 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1233 else if (button == MB_RELEASED && touched)
1240 if (motion_key_x != KSYM_UNDEFINED)
1241 HandleKey(motion_key_x, KEY_RELEASED);
1242 if (motion_key_y != KSYM_UNDEFINED)
1243 HandleKey(motion_key_y, KEY_RELEASED);
1245 if (started_on_player)
1247 if (player_is_dropping)
1249 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1251 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1255 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1257 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1261 motion_key_x = KSYM_UNDEFINED;
1262 motion_key_y = KSYM_UNDEFINED;
1264 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1269 int src_x = local_player->jx;
1270 int src_y = local_player->jy;
1271 int dst_x = getLevelFromScreenX(old_mx);
1272 int dst_y = getLevelFromScreenY(old_my);
1273 int dx = dst_x - src_x;
1274 int dy = dst_y - src_y;
1275 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1276 dx > 0 ? setup.input[0].key.right :
1278 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1279 dy > 0 ? setup.input[0].key.down :
1282 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1283 (last_player_x != local_player->jx ||
1284 last_player_y != local_player->jy))
1286 // in case of asymmetric diagonal movement, use "preferred" direction
1288 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1290 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1291 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1293 local_player->last_move_dir = last_move_dir;
1295 // (required to prevent accidentally forcing direction for next movement)
1296 last_player_x = local_player->jx;
1297 last_player_y = local_player->jy;
1300 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1302 started_on_player = TRUE;
1303 player_drop_count = getPlayerInventorySize(0);
1304 player_is_dropping = (player_drop_count > 0);
1306 if (player_is_dropping)
1308 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1310 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1314 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1316 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1319 else if (dx != 0 || dy != 0)
1321 if (player_is_dropping &&
1322 player_drop_count == getPlayerInventorySize(0))
1324 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1326 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1327 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1329 player_is_dropping = FALSE;
1333 if (new_motion_key_x != motion_key_x)
1335 Error(ERR_DEBUG, "---------- %s %s ----------",
1336 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1337 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1339 if (motion_key_x != KSYM_UNDEFINED)
1340 HandleKey(motion_key_x, KEY_RELEASED);
1341 if (new_motion_key_x != KSYM_UNDEFINED)
1342 HandleKey(new_motion_key_x, KEY_PRESSED);
1345 if (new_motion_key_y != motion_key_y)
1347 Error(ERR_DEBUG, "---------- %s %s ----------",
1348 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1349 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1351 if (motion_key_y != KSYM_UNDEFINED)
1352 HandleKey(motion_key_y, KEY_RELEASED);
1353 if (new_motion_key_y != KSYM_UNDEFINED)
1354 HandleKey(new_motion_key_y, KEY_PRESSED);
1357 motion_key_x = new_motion_key_x;
1358 motion_key_y = new_motion_key_y;
1362 static void HandleButtonOrFinger(int mx, int my, int button)
1364 if (game_status != GAME_MODE_PLAYING)
1367 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1369 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1370 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1371 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1372 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1376 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1377 HandleButtonOrFinger_FollowFinger(mx, my, button);
1381 #if defined(TARGET_SDL2)
1383 static boolean checkTextInputKeyModState()
1385 // when playing, only handle raw key events and ignore text input
1386 if (game_status == GAME_MODE_PLAYING)
1389 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1392 void HandleTextEvent(TextEvent *event)
1394 char *text = event->text;
1395 Key key = getKeyFromKeyName(text);
1397 #if DEBUG_EVENTS_TEXT
1398 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1401 text[0], (int)(text[0]),
1403 getKeyNameFromKey(key),
1407 #if !defined(HAS_SCREEN_KEYBOARD)
1408 // non-mobile devices: only handle key input with modifier keys pressed here
1409 // (every other key input is handled directly as physical key input event)
1410 if (!checkTextInputKeyModState())
1414 // process text input as "classic" (with uppercase etc.) key input event
1415 HandleKey(key, KEY_PRESSED);
1416 HandleKey(key, KEY_RELEASED);
1419 void HandlePauseResumeEvent(PauseResumeEvent *event)
1421 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1425 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1433 void HandleKeyEvent(KeyEvent *event)
1435 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1436 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1437 Key key = GetEventKey(event, with_modifiers);
1438 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1440 #if DEBUG_EVENTS_KEY
1441 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1442 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1443 event->keysym.scancode,
1448 getKeyNameFromKey(key));
1451 #if defined(PLATFORM_ANDROID)
1452 if (key == KSYM_Back)
1454 // always map the "back" button to the "escape" key on Android devices
1459 // for any key event other than "back" button, disable overlay buttons
1460 SetOverlayEnabled(FALSE);
1464 HandleKeyModState(keymod, key_status);
1466 #if defined(TARGET_SDL2)
1467 // only handle raw key input without text modifier keys pressed
1468 if (!checkTextInputKeyModState())
1469 HandleKey(key, key_status);
1471 HandleKey(key, key_status);
1475 void HandleFocusEvent(FocusChangeEvent *event)
1477 static int old_joystick_status = -1;
1479 if (event->type == EVENT_FOCUSOUT)
1481 KeyboardAutoRepeatOn();
1482 old_joystick_status = joystick.status;
1483 joystick.status = JOYSTICK_NOT_AVAILABLE;
1485 ClearPlayerAction();
1487 else if (event->type == EVENT_FOCUSIN)
1489 /* When there are two Rocks'n'Diamonds windows which overlap and
1490 the player moves the pointer from one game window to the other,
1491 a 'FocusOut' event is generated for the window the pointer is
1492 leaving and a 'FocusIn' event is generated for the window the
1493 pointer is entering. In some cases, it can happen that the
1494 'FocusIn' event is handled by the one game process before the
1495 'FocusOut' event by the other game process. In this case the
1496 X11 environment would end up with activated keyboard auto repeat,
1497 because unfortunately this is a global setting and not (which
1498 would be far better) set for each X11 window individually.
1499 The effect would be keyboard auto repeat while playing the game
1500 (game_status == GAME_MODE_PLAYING), which is not desired.
1501 To avoid this special case, we just wait 1/10 second before
1502 processing the 'FocusIn' event.
1505 if (game_status == GAME_MODE_PLAYING)
1508 KeyboardAutoRepeatOffUnlessAutoplay();
1511 if (old_joystick_status != -1)
1512 joystick.status = old_joystick_status;
1516 void HandleClientMessageEvent(ClientMessageEvent *event)
1518 if (CheckCloseWindowEvent(event))
1522 void HandleWindowManagerEvent(Event *event)
1524 #if defined(TARGET_SDL)
1525 SDLHandleWindowManagerEvent(event);
1529 void HandleButton(int mx, int my, int button, int button_nr)
1531 static int old_mx = 0, old_my = 0;
1532 boolean button_hold = FALSE;
1533 boolean handle_gadgets = TRUE;
1539 button_nr = -button_nr;
1548 #if defined(PLATFORM_ANDROID)
1549 // when playing, only handle gadgets when using "follow finger" controls
1550 // or when using touch controls in combination with the MM game engine
1552 (game_status != GAME_MODE_PLAYING ||
1553 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1554 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1557 if (HandleGlobalAnimClicks(mx, my, button))
1559 /* do not handle this button event anymore */
1560 return; /* force mouse event not to be handled at all */
1563 if (handle_gadgets && HandleGadgets(mx, my, button))
1565 /* do not handle this button event anymore */
1566 mx = my = -32; /* force mouse event to be outside screen tiles */
1569 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1572 /* do not use scroll wheel button events for anything other than gadgets */
1573 if (IS_WHEEL_BUTTON(button_nr))
1576 switch (game_status)
1578 case GAME_MODE_TITLE:
1579 HandleTitleScreen(mx, my, 0, 0, button);
1582 case GAME_MODE_MAIN:
1583 HandleMainMenu(mx, my, 0, 0, button);
1586 case GAME_MODE_PSEUDO_TYPENAME:
1587 HandleTypeName(0, KSYM_Return);
1590 case GAME_MODE_LEVELS:
1591 HandleChooseLevelSet(mx, my, 0, 0, button);
1594 case GAME_MODE_LEVELNR:
1595 HandleChooseLevelNr(mx, my, 0, 0, button);
1598 case GAME_MODE_SCORES:
1599 HandleHallOfFame(0, 0, 0, 0, button);
1602 case GAME_MODE_EDITOR:
1603 HandleLevelEditorIdle();
1606 case GAME_MODE_INFO:
1607 HandleInfoScreen(mx, my, 0, 0, button);
1610 case GAME_MODE_SETUP:
1611 HandleSetupScreen(mx, my, 0, 0, button);
1614 case GAME_MODE_PLAYING:
1615 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1616 HandleButtonOrFinger(mx, my, button);
1618 SetPlayerMouseAction(mx, my, button);
1621 if (button == MB_PRESSED && !motion_status && !button_hold &&
1622 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1623 DumpTileFromScreen(mx, my);
1633 static boolean is_string_suffix(char *string, char *suffix)
1635 int string_len = strlen(string);
1636 int suffix_len = strlen(suffix);
1638 if (suffix_len > string_len)
1641 return (strEqual(&string[string_len - suffix_len], suffix));
1644 #define MAX_CHEAT_INPUT_LEN 32
1646 static void HandleKeysSpecial(Key key)
1648 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1649 char letter = getCharFromKey(key);
1650 int cheat_input_len = strlen(cheat_input);
1656 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1658 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1659 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1661 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1664 cheat_input[cheat_input_len++] = letter;
1665 cheat_input[cheat_input_len] = '\0';
1667 #if DEBUG_EVENTS_KEY
1668 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1671 if (game_status == GAME_MODE_MAIN)
1673 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1674 is_string_suffix(cheat_input, ":ist"))
1676 InsertSolutionTape();
1678 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1679 is_string_suffix(cheat_input, ":pst"))
1683 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1684 is_string_suffix(cheat_input, ":rg"))
1686 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1689 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1690 is_string_suffix(cheat_input, ":rs"))
1692 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1695 else if (is_string_suffix(cheat_input, ":reload-music") ||
1696 is_string_suffix(cheat_input, ":rm"))
1698 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1701 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1702 is_string_suffix(cheat_input, ":ra"))
1704 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1705 1 << ARTWORK_TYPE_SOUNDS |
1706 1 << ARTWORK_TYPE_MUSIC);
1709 else if (is_string_suffix(cheat_input, ":dump-level") ||
1710 is_string_suffix(cheat_input, ":dl"))
1714 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1715 is_string_suffix(cheat_input, ":dt"))
1719 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1720 is_string_suffix(cheat_input, ":ft"))
1722 /* fix single-player tapes that contain player input for more than one
1723 player (due to a bug in 3.3.1.2 and earlier versions), which results
1724 in playing levels with more than one player in multi-player mode,
1725 even though the tape was originally recorded in single-player mode */
1727 /* remove player input actions for all players but the first one */
1728 for (i = 1; i < MAX_PLAYERS; i++)
1729 tape.player_participates[i] = FALSE;
1731 tape.changed = TRUE;
1733 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1734 is_string_suffix(cheat_input, ":snl"))
1736 SaveNativeLevel(&level);
1738 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1739 is_string_suffix(cheat_input, ":fps"))
1741 global.show_frames_per_second = !global.show_frames_per_second;
1744 else if (game_status == GAME_MODE_PLAYING)
1747 if (is_string_suffix(cheat_input, ".q"))
1748 DEBUG_SetMaximumDynamite();
1751 else if (game_status == GAME_MODE_EDITOR)
1753 if (is_string_suffix(cheat_input, ":dump-brush") ||
1754 is_string_suffix(cheat_input, ":DB"))
1758 else if (is_string_suffix(cheat_input, ":DDB"))
1764 /* special key shortcuts for all game modes */
1765 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1766 is_string_suffix(cheat_input, ":dea") ||
1767 is_string_suffix(cheat_input, ":DEA"))
1769 DumpGadgetIdentifiers();
1770 DumpScreenIdentifiers();
1774 void HandleKeysDebug(Key key)
1779 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1781 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1783 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1785 if (key == setup.debug.frame_delay_key[i] &&
1786 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1788 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1789 setup.debug.frame_delay[i] : setup.game_frame_delay);
1791 if (!setup.debug.frame_delay_game_only)
1792 MenuFrameDelay = GameFrameDelay;
1794 SetVideoFrameDelay(GameFrameDelay);
1796 if (GameFrameDelay > ONE_SECOND_DELAY)
1797 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1798 else if (GameFrameDelay != 0)
1799 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1800 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1801 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1803 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1810 if (game_status == GAME_MODE_PLAYING)
1814 options.debug = !options.debug;
1816 Error(ERR_DEBUG, "debug mode %s",
1817 (options.debug ? "enabled" : "disabled"));
1819 else if (key == KSYM_v)
1821 Error(ERR_DEBUG, "currently using game engine version %d",
1822 game.engine_version);
1828 void HandleKey(Key key, int key_status)
1830 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1831 static boolean ignore_repeated_key = FALSE;
1832 static struct SetupKeyboardInfo ski;
1833 static struct SetupShortcutInfo ssi;
1842 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1843 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1844 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1845 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1846 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1847 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1852 #if defined(TARGET_SDL2)
1853 /* map special keys (media keys / remote control buttons) to default keys */
1854 if (key == KSYM_PlayPause)
1856 else if (key == KSYM_Select)
1860 HandleSpecialGameControllerKeys(key, key_status);
1862 if (game_status == GAME_MODE_PLAYING)
1864 /* only needed for single-step tape recording mode */
1865 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1868 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1870 byte key_action = 0;
1872 if (setup.input[pnr].use_joystick)
1875 ski = setup.input[pnr].key;
1877 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1878 if (key == *key_info[i].key_custom)
1879 key_action |= key_info[i].action;
1881 /* use combined snap+direction keys for the first player only */
1884 ssi = setup.shortcut;
1886 for (i = 0; i < NUM_DIRECTIONS; i++)
1887 if (key == *key_info[i].key_snap)
1888 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1891 if (key_status == KEY_PRESSED)
1892 stored_player[pnr].action |= key_action;
1894 stored_player[pnr].action &= ~key_action;
1896 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1898 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1900 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1902 /* if snap key already pressed, keep pause mode when releasing */
1903 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1904 has_snapped[pnr] = TRUE;
1906 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1908 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1910 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1911 getRedDiskReleaseFlag_SP() == 0)
1913 /* add a single inactive frame before dropping starts */
1914 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1915 stored_player[pnr].force_dropping = TRUE;
1918 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1920 /* if snap key was pressed without direction, leave pause mode */
1921 if (!has_snapped[pnr])
1922 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1924 has_snapped[pnr] = FALSE;
1927 else if (tape.recording && tape.pausing && !tape.use_mouse)
1929 /* prevent key release events from un-pausing a paused game */
1930 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1931 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1934 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1935 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1941 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1942 if (key == key_info[i].key_default)
1943 joy |= key_info[i].action;
1948 if (key_status == KEY_PRESSED)
1949 key_joystick_mapping |= joy;
1951 key_joystick_mapping &= ~joy;
1956 if (game_status != GAME_MODE_PLAYING)
1957 key_joystick_mapping = 0;
1959 if (key_status == KEY_RELEASED)
1961 // reset flag to ignore repeated "key pressed" events after key release
1962 ignore_repeated_key = FALSE;
1967 if ((key == KSYM_F11 ||
1968 ((key == KSYM_Return ||
1969 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1970 video.fullscreen_available &&
1971 !ignore_repeated_key)
1973 setup.fullscreen = !setup.fullscreen;
1975 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1977 if (game_status == GAME_MODE_SETUP)
1978 RedrawSetupScreenAfterFullscreenToggle();
1980 // set flag to ignore repeated "key pressed" events
1981 ignore_repeated_key = TRUE;
1986 if ((key == KSYM_0 || key == KSYM_KP_0 ||
1987 key == KSYM_minus || key == KSYM_KP_Subtract ||
1988 key == KSYM_plus || key == KSYM_KP_Add ||
1989 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
1990 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
1991 video.window_scaling_available &&
1992 !video.fullscreen_enabled)
1994 if (key == KSYM_0 || key == KSYM_KP_0)
1995 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
1996 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
1997 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
1999 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2001 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2002 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2003 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2004 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2006 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2008 if (game_status == GAME_MODE_SETUP)
2009 RedrawSetupScreenAfterFullscreenToggle();
2014 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2015 key == KSYM_Return ||
2016 key == KSYM_Escape)))
2018 /* do not handle this key event anymore */
2019 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2023 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2024 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2031 if (game_status == GAME_MODE_MAIN &&
2032 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2034 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2039 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2041 if (key == setup.shortcut.save_game)
2043 else if (key == setup.shortcut.load_game)
2045 else if (key == setup.shortcut.toggle_pause)
2046 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2048 HandleTapeButtonKeys(key);
2049 HandleSoundButtonKeys(key);
2052 if (game_status == GAME_MODE_PLAYING && !network_playing)
2054 int centered_player_nr_next = -999;
2056 if (key == setup.shortcut.focus_player_all)
2057 centered_player_nr_next = -1;
2059 for (i = 0; i < MAX_PLAYERS; i++)
2060 if (key == setup.shortcut.focus_player[i])
2061 centered_player_nr_next = i;
2063 if (centered_player_nr_next != -999)
2065 game.centered_player_nr_next = centered_player_nr_next;
2066 game.set_centered_player = TRUE;
2070 tape.centered_player_nr_next = game.centered_player_nr_next;
2071 tape.set_centered_player = TRUE;
2076 HandleKeysSpecial(key);
2078 if (HandleGadgetsKeyInput(key))
2080 if (key != KSYM_Escape) /* always allow ESC key to be handled */
2081 key = KSYM_UNDEFINED;
2084 switch (game_status)
2086 case GAME_MODE_PSEUDO_TYPENAME:
2087 HandleTypeName(0, key);
2090 case GAME_MODE_TITLE:
2091 case GAME_MODE_MAIN:
2092 case GAME_MODE_LEVELS:
2093 case GAME_MODE_LEVELNR:
2094 case GAME_MODE_SETUP:
2095 case GAME_MODE_INFO:
2096 case GAME_MODE_SCORES:
2101 if (game_status == GAME_MODE_TITLE)
2102 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2103 else if (game_status == GAME_MODE_MAIN)
2104 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2105 else if (game_status == GAME_MODE_LEVELS)
2106 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2107 else if (game_status == GAME_MODE_LEVELNR)
2108 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2109 else if (game_status == GAME_MODE_SETUP)
2110 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2111 else if (game_status == GAME_MODE_INFO)
2112 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2113 else if (game_status == GAME_MODE_SCORES)
2114 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2118 if (game_status != GAME_MODE_MAIN)
2119 FadeSkipNextFadeIn();
2121 if (game_status == GAME_MODE_TITLE)
2122 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2123 else if (game_status == GAME_MODE_LEVELS)
2124 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2125 else if (game_status == GAME_MODE_LEVELNR)
2126 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2127 else if (game_status == GAME_MODE_SETUP)
2128 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2129 else if (game_status == GAME_MODE_INFO)
2130 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2131 else if (game_status == GAME_MODE_SCORES)
2132 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2136 if (game_status == GAME_MODE_LEVELS)
2137 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2138 else if (game_status == GAME_MODE_LEVELNR)
2139 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2140 else if (game_status == GAME_MODE_SETUP)
2141 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2142 else if (game_status == GAME_MODE_INFO)
2143 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2144 else if (game_status == GAME_MODE_SCORES)
2145 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2148 case KSYM_Page_Down:
2149 if (game_status == GAME_MODE_LEVELS)
2150 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2151 else if (game_status == GAME_MODE_LEVELNR)
2152 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2153 else if (game_status == GAME_MODE_SETUP)
2154 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2155 else if (game_status == GAME_MODE_INFO)
2156 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2157 else if (game_status == GAME_MODE_SCORES)
2158 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2166 case GAME_MODE_EDITOR:
2167 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2168 HandleLevelEditorKeyInput(key);
2171 case GAME_MODE_PLAYING:
2176 RequestQuitGame(setup.ask_on_escape);
2186 if (key == KSYM_Escape)
2188 SetGameStatus(GAME_MODE_MAIN);
2196 HandleKeysDebug(key);
2199 void HandleNoEvent()
2201 HandleMouseCursor();
2203 switch (game_status)
2205 case GAME_MODE_PLAYING:
2206 HandleButtonOrFinger(-1, -1, -1);
2211 void HandleEventActions()
2213 // if (button_status && game_status != GAME_MODE_PLAYING)
2214 if (button_status && (game_status != GAME_MODE_PLAYING ||
2216 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2218 HandleButton(0, 0, button_status, -button_status);
2225 if (network.enabled)
2228 switch (game_status)
2230 case GAME_MODE_MAIN:
2231 DrawPreviewLevelAnimation();
2234 case GAME_MODE_EDITOR:
2235 HandleLevelEditorIdle();
2243 static void HandleTileCursor(int dx, int dy, int button)
2246 ClearPlayerMouseAction();
2253 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2254 (dx < 0 ? MB_LEFTBUTTON :
2255 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2257 else if (!tile_cursor.moving)
2259 int old_xpos = tile_cursor.xpos;
2260 int old_ypos = tile_cursor.ypos;
2261 int new_xpos = old_xpos;
2262 int new_ypos = old_ypos;
2264 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2265 new_xpos = old_xpos + dx;
2267 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2268 new_ypos = old_ypos + dy;
2270 SetTileCursorTargetXY(new_xpos, new_ypos);
2274 static int HandleJoystickForAllPlayers()
2278 boolean no_joysticks_configured = TRUE;
2279 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2280 static byte joy_action_last[MAX_PLAYERS];
2282 for (i = 0; i < MAX_PLAYERS; i++)
2283 if (setup.input[i].use_joystick)
2284 no_joysticks_configured = FALSE;
2286 /* if no joysticks configured, map connected joysticks to players */
2287 if (no_joysticks_configured)
2288 use_as_joystick_nr = TRUE;
2290 for (i = 0; i < MAX_PLAYERS; i++)
2292 byte joy_action = 0;
2294 joy_action = JoystickExt(i, use_as_joystick_nr);
2295 result |= joy_action;
2297 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2298 joy_action != joy_action_last[i])
2299 stored_player[i].action = joy_action;
2301 joy_action_last[i] = joy_action;
2307 void HandleJoystick()
2309 static unsigned int joytest_delay = 0;
2310 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2311 static int joytest_last = 0;
2312 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2313 int delay_value = GADGET_FRAME_DELAY;
2314 int joystick = HandleJoystickForAllPlayers();
2315 int keyboard = key_joystick_mapping;
2316 int joy = (joystick | keyboard);
2317 int joytest = joystick;
2318 int left = joy & JOY_LEFT;
2319 int right = joy & JOY_RIGHT;
2320 int up = joy & JOY_UP;
2321 int down = joy & JOY_DOWN;
2322 int button = joy & JOY_BUTTON;
2323 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2324 int dx = (left ? -1 : right ? 1 : 0);
2325 int dy = (up ? -1 : down ? 1 : 0);
2326 boolean use_delay_value_first = (joytest != joytest_last);
2328 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2330 /* do not handle this button event anymore */
2334 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2336 if (game_status == GAME_MODE_PLAYING)
2338 // when playing MM style levels, also use delay for keyboard events
2339 joytest |= keyboard;
2341 // only use first delay value for new events, but not for changed events
2342 use_delay_value_first = (!joytest != !joytest_last);
2344 // only use delay after the initial keyboard event
2348 // for any joystick or keyboard event, enable playfield tile cursor
2349 if (dx || dy || button)
2350 SetTileCursorEnabled(TRUE);
2353 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2355 /* delay joystick/keyboard actions if axes/keys continually pressed */
2356 newbutton = dx = dy = 0;
2360 /* first start with longer delay, then continue with shorter delay */
2361 joytest_delay_value =
2362 (use_delay_value_first ? delay_value_first : delay_value);
2365 joytest_last = joytest;
2367 switch (game_status)
2369 case GAME_MODE_TITLE:
2370 case GAME_MODE_MAIN:
2371 case GAME_MODE_LEVELS:
2372 case GAME_MODE_LEVELNR:
2373 case GAME_MODE_SETUP:
2374 case GAME_MODE_INFO:
2375 case GAME_MODE_SCORES:
2377 if (game_status == GAME_MODE_TITLE)
2378 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2379 else if (game_status == GAME_MODE_MAIN)
2380 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2381 else if (game_status == GAME_MODE_LEVELS)
2382 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2383 else if (game_status == GAME_MODE_LEVELNR)
2384 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2385 else if (game_status == GAME_MODE_SETUP)
2386 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2387 else if (game_status == GAME_MODE_INFO)
2388 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2389 else if (game_status == GAME_MODE_SCORES)
2390 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2395 case GAME_MODE_PLAYING:
2397 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2398 if (tape.playing || keyboard)
2399 newbutton = ((joy & JOY_BUTTON) != 0);
2402 if (newbutton && AllPlayersGone)
2409 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2411 if (joystick & JOY_ACTION)
2412 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2414 else if (tape.recording && tape.pausing && !tape.use_mouse)
2416 if (joystick & JOY_ACTION)
2417 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2420 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2421 HandleTileCursor(dx, dy, button);
2430 void HandleSpecialGameControllerButtons(Event *event)
2432 #if defined(TARGET_SDL2)
2436 switch (event->type)
2438 case SDL_CONTROLLERBUTTONDOWN:
2439 key_status = KEY_PRESSED;
2442 case SDL_CONTROLLERBUTTONUP:
2443 key_status = KEY_RELEASED;
2450 switch (event->cbutton.button)
2452 case SDL_CONTROLLER_BUTTON_START:
2456 case SDL_CONTROLLER_BUTTON_BACK:
2464 HandleKey(key, key_status);
2468 void HandleSpecialGameControllerKeys(Key key, int key_status)
2470 #if defined(TARGET_SDL2)
2471 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2472 int button = SDL_CONTROLLER_BUTTON_INVALID;
2474 /* map keys to joystick buttons (special hack for Amazon Fire TV remote) */
2475 if (key == KSYM_Rewind)
2476 button = SDL_CONTROLLER_BUTTON_A;
2477 else if (key == KSYM_FastForward || key == KSYM_Menu)
2478 button = SDL_CONTROLLER_BUTTON_B;
2480 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2484 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2485 SDL_CONTROLLERBUTTONUP);
2487 event.cbutton.which = 0; /* first joystick (Amazon Fire TV remote) */
2488 event.cbutton.button = button;
2489 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2492 HandleJoystickEvent(&event);
2498 boolean DoKeysymAction(int keysym)
2502 Key key = (Key)(-keysym);
2504 HandleKey(key, KEY_PRESSED);
2505 HandleKey(key, KEY_RELEASED);