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