1 // ============================================================================
2 // Rocks'n'Diamonds - McDuffin Strikes Back!
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include "libgame/libgame.h"
26 #define DEBUG_EVENTS 0
28 #define DEBUG_EVENTS_BUTTON (DEBUG_EVENTS * 0)
29 #define DEBUG_EVENTS_MOTION (DEBUG_EVENTS * 0)
30 #define DEBUG_EVENTS_WHEEL (DEBUG_EVENTS * 1)
31 #define DEBUG_EVENTS_WINDOW (DEBUG_EVENTS * 0)
32 #define DEBUG_EVENTS_FINGER (DEBUG_EVENTS * 0)
33 #define DEBUG_EVENTS_TEXT (DEBUG_EVENTS * 1)
34 #define DEBUG_EVENTS_KEY (DEBUG_EVENTS * 1)
37 static boolean cursor_inside_playfield = FALSE;
38 static int cursor_mode_last = CURSOR_DEFAULT;
39 static unsigned int special_cursor_delay = 0;
40 static unsigned int special_cursor_delay_value = 1000;
43 // forward declarations for internal use
44 static void HandleNoEvent(void);
45 static void HandleEventActions(void);
48 // event filter especially needed for SDL event filtering due to
49 // delay problems with lots of mouse motion events when mouse button
50 // not pressed (X11 can handle this with 'PointerMotionHintMask')
52 // event filter addition for SDL2: as SDL2 does not have a function to enable
53 // or disable keyboard auto-repeat, filter repeated keyboard events instead
55 static int FilterEvents(const Event *event)
59 #if defined(TARGET_SDL2)
60 // skip repeated key press events if keyboard auto-repeat is disabled
61 if (event->type == EVENT_KEYPRESS &&
67 if (event->type == EVENT_BUTTONPRESS ||
68 event->type == EVENT_BUTTONRELEASE)
70 ((ButtonEvent *)event)->x -= video.screen_xoffset;
71 ((ButtonEvent *)event)->y -= video.screen_yoffset;
73 else if (event->type == EVENT_MOTIONNOTIFY)
75 ((MotionEvent *)event)->x -= video.screen_xoffset;
76 ((MotionEvent *)event)->y -= video.screen_yoffset;
79 // non-motion events are directly passed to event handler functions
80 if (event->type != EVENT_MOTIONNOTIFY)
83 motion = (MotionEvent *)event;
84 cursor_inside_playfield = (motion->x >= SX && motion->x < SX + SXSIZE &&
85 motion->y >= SY && motion->y < SY + SYSIZE);
87 // do no reset mouse cursor before all pending events have been processed
88 if (gfx.cursor_mode == cursor_mode_last &&
89 ((game_status == GAME_MODE_TITLE &&
90 gfx.cursor_mode == CURSOR_NONE) ||
91 (game_status == GAME_MODE_PLAYING &&
92 gfx.cursor_mode == CURSOR_PLAYFIELD)))
94 SetMouseCursor(CURSOR_DEFAULT);
96 DelayReached(&special_cursor_delay, 0);
98 cursor_mode_last = CURSOR_DEFAULT;
101 // skip mouse motion events without pressed button outside level editor
102 if (button_status == MB_RELEASED &&
103 game_status != GAME_MODE_EDITOR && game_status != GAME_MODE_PLAYING)
109 // to prevent delay problems, skip mouse motion events if the very next
110 // event is also a mouse motion event (and therefore effectively only
111 // handling the last of a row of mouse motion events in the event queue)
113 static boolean SkipPressedMouseMotionEvent(const Event *event)
115 // nothing to do if the current event is not a mouse motion event
116 if (event->type != EVENT_MOTIONNOTIFY)
119 // only skip motion events with pressed button outside the game
120 if (button_status == MB_RELEASED || game_status == GAME_MODE_PLAYING)
127 PeekEvent(&next_event);
129 // if next event is also a mouse motion event, skip the current one
130 if (next_event.type == EVENT_MOTIONNOTIFY)
137 static boolean WaitValidEvent(Event *event)
141 if (!FilterEvents(event))
144 if (SkipPressedMouseMotionEvent(event))
150 /* this is especially needed for event modifications for the Android target:
151 if mouse coordinates should be modified in the event filter function,
152 using a properly installed SDL event filter does not work, because in
153 the event filter, mouse coordinates in the event structure are still
154 physical pixel positions, not logical (scaled) screen positions, so this
155 has to be handled at a later stage in the event processing functions
156 (when device pixel positions are already converted to screen positions) */
158 boolean NextValidEvent(Event *event)
160 while (PendingEvent())
161 if (WaitValidEvent(event))
167 static void HandleEvents(void)
170 unsigned int event_frame_delay = 0;
171 unsigned int event_frame_delay_value = GAME_FRAME_DELAY;
173 ResetDelayCounter(&event_frame_delay);
175 while (NextValidEvent(&event))
179 case EVENT_BUTTONPRESS:
180 case EVENT_BUTTONRELEASE:
181 HandleButtonEvent((ButtonEvent *) &event);
184 case EVENT_MOTIONNOTIFY:
185 HandleMotionEvent((MotionEvent *) &event);
188 #if defined(TARGET_SDL2)
189 case EVENT_WHEELMOTION:
190 HandleWheelEvent((WheelEvent *) &event);
193 case SDL_WINDOWEVENT:
194 HandleWindowEvent((WindowEvent *) &event);
197 case EVENT_FINGERPRESS:
198 case EVENT_FINGERRELEASE:
199 case EVENT_FINGERMOTION:
200 HandleFingerEvent((FingerEvent *) &event);
203 case EVENT_TEXTINPUT:
204 HandleTextEvent((TextEvent *) &event);
207 case SDL_APP_WILLENTERBACKGROUND:
208 case SDL_APP_DIDENTERBACKGROUND:
209 case SDL_APP_WILLENTERFOREGROUND:
210 case SDL_APP_DIDENTERFOREGROUND:
211 HandlePauseResumeEvent((PauseResumeEvent *) &event);
216 case EVENT_KEYRELEASE:
217 HandleKeyEvent((KeyEvent *) &event);
221 HandleOtherEvents(&event);
225 // do not handle events for longer than standard frame delay period
226 if (DelayReached(&event_frame_delay, event_frame_delay_value))
231 void HandleOtherEvents(Event *event)
236 HandleExposeEvent((ExposeEvent *) event);
239 case EVENT_UNMAPNOTIFY:
241 // This causes the game to stop not only when iconified, but also
242 // when on another virtual desktop, which might be not desired.
243 SleepWhileUnmapped();
249 HandleFocusEvent((FocusChangeEvent *) event);
252 case EVENT_CLIENTMESSAGE:
253 HandleClientMessageEvent((ClientMessageEvent *) event);
256 #if defined(TARGET_SDL)
257 #if defined(TARGET_SDL2)
258 case SDL_CONTROLLERBUTTONDOWN:
259 case SDL_CONTROLLERBUTTONUP:
260 // for any game controller button event, disable overlay buttons
261 SetOverlayEnabled(FALSE);
263 HandleSpecialGameControllerButtons(event);
266 case SDL_CONTROLLERDEVICEADDED:
267 case SDL_CONTROLLERDEVICEREMOVED:
268 case SDL_CONTROLLERAXISMOTION:
270 case SDL_JOYAXISMOTION:
271 case SDL_JOYBUTTONDOWN:
272 case SDL_JOYBUTTONUP:
273 HandleJoystickEvent(event);
277 HandleWindowManagerEvent(event);
286 static void HandleMouseCursor(void)
288 if (game_status == GAME_MODE_TITLE)
290 // when showing title screens, hide mouse pointer (if not moved)
292 if (gfx.cursor_mode != CURSOR_NONE &&
293 DelayReached(&special_cursor_delay, special_cursor_delay_value))
295 SetMouseCursor(CURSOR_NONE);
298 else if (game_status == GAME_MODE_PLAYING && (!tape.pausing ||
301 // when playing, display a special mouse pointer inside the playfield
303 if (gfx.cursor_mode != CURSOR_PLAYFIELD &&
304 cursor_inside_playfield &&
305 DelayReached(&special_cursor_delay, special_cursor_delay_value))
307 if (level.game_engine_type != GAME_ENGINE_TYPE_MM ||
309 SetMouseCursor(CURSOR_PLAYFIELD);
312 else if (gfx.cursor_mode != CURSOR_DEFAULT)
314 SetMouseCursor(CURSOR_DEFAULT);
317 // this is set after all pending events have been processed
318 cursor_mode_last = gfx.cursor_mode;
330 // execute event related actions after pending events have been processed
331 HandleEventActions();
333 // don't use all CPU time when idle; the main loop while playing
334 // has its own synchronization and is CPU friendly, too
336 if (game_status == GAME_MODE_PLAYING)
339 // always copy backbuffer to visible screen for every video frame
342 // reset video frame delay to default (may change again while playing)
343 SetVideoFrameDelay(MenuFrameDelay);
345 if (game_status == GAME_MODE_QUIT)
350 void ClearAutoRepeatKeyEvents(void)
352 #if defined(TARGET_SDL2)
353 while (PendingEvent())
357 PeekEvent(&next_event);
359 // if event is repeated key press event, remove it from event queue
360 if (next_event.type == EVENT_KEYPRESS &&
361 next_event.key.repeat)
362 WaitEvent(&next_event);
369 void ClearEventQueue(void)
373 while (NextValidEvent(&event))
377 case EVENT_BUTTONRELEASE:
378 button_status = MB_RELEASED;
381 case EVENT_KEYRELEASE:
385 #if defined(TARGET_SDL2)
386 case SDL_CONTROLLERBUTTONUP:
387 HandleJoystickEvent(&event);
393 HandleOtherEvents(&event);
399 static void ClearPlayerMouseAction(void)
401 local_player->mouse_action.lx = 0;
402 local_player->mouse_action.ly = 0;
403 local_player->mouse_action.button = 0;
406 void ClearPlayerAction(void)
410 // simulate key release events for still pressed keys
411 key_joystick_mapping = 0;
412 for (i = 0; i < MAX_PLAYERS; i++)
413 stored_player[i].action = 0;
415 ClearJoystickState();
416 ClearPlayerMouseAction();
419 static void SetPlayerMouseAction(int mx, int my, int button)
421 int lx = getLevelFromScreenX(mx);
422 int ly = getLevelFromScreenY(my);
423 int new_button = (!local_player->mouse_action.button && button);
425 if (local_player->mouse_action.button_hint)
426 button = local_player->mouse_action.button_hint;
428 ClearPlayerMouseAction();
430 if (!IN_GFX_FIELD_PLAY(mx, my) || !IN_LEV_FIELD(lx, ly))
433 local_player->mouse_action.lx = lx;
434 local_player->mouse_action.ly = ly;
435 local_player->mouse_action.button = button;
437 if (tape.recording && tape.pausing && tape.use_mouse)
439 // un-pause a paused game only if mouse button was newly pressed down
441 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
444 SetTileCursorXY(lx, ly);
447 void SleepWhileUnmapped(void)
449 boolean window_unmapped = TRUE;
451 KeyboardAutoRepeatOn();
453 while (window_unmapped)
457 if (!WaitValidEvent(&event))
462 case EVENT_BUTTONRELEASE:
463 button_status = MB_RELEASED;
466 case EVENT_KEYRELEASE:
467 key_joystick_mapping = 0;
470 #if defined(TARGET_SDL2)
471 case SDL_CONTROLLERBUTTONUP:
472 HandleJoystickEvent(&event);
473 key_joystick_mapping = 0;
477 case EVENT_MAPNOTIFY:
478 window_unmapped = FALSE;
481 case EVENT_UNMAPNOTIFY:
482 // this is only to surely prevent the 'should not happen' case
483 // of recursively looping between 'SleepWhileUnmapped()' and
484 // 'HandleOtherEvents()' which usually calls this funtion.
488 HandleOtherEvents(&event);
493 if (game_status == GAME_MODE_PLAYING)
494 KeyboardAutoRepeatOffUnlessAutoplay();
497 void HandleExposeEvent(ExposeEvent *event)
501 void HandleButtonEvent(ButtonEvent *event)
503 #if DEBUG_EVENTS_BUTTON
504 Error(ERR_DEBUG, "BUTTON EVENT: button %d %s, x/y %d/%d\n",
506 event->type == EVENT_BUTTONPRESS ? "pressed" : "released",
510 // for any mouse button event, disable playfield tile cursor
511 SetTileCursorEnabled(FALSE);
513 #if defined(HAS_SCREEN_KEYBOARD)
514 if (video.shifted_up)
515 event->y += video.shifted_up_pos;
518 motion_status = FALSE;
520 if (event->type == EVENT_BUTTONPRESS)
521 button_status = event->button;
523 button_status = MB_RELEASED;
525 HandleButton(event->x, event->y, button_status, event->button);
528 void HandleMotionEvent(MotionEvent *event)
530 if (button_status == MB_RELEASED && game_status != GAME_MODE_EDITOR)
533 motion_status = TRUE;
535 #if DEBUG_EVENTS_MOTION
536 Error(ERR_DEBUG, "MOTION EVENT: button %d moved, x/y %d/%d\n",
537 button_status, event->x, event->y);
540 HandleButton(event->x, event->y, button_status, button_status);
543 #if defined(TARGET_SDL2)
545 void HandleWheelEvent(WheelEvent *event)
549 #if DEBUG_EVENTS_WHEEL
551 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d\n",
552 event->which, event->x, event->y);
554 // (SDL_MOUSEWHEEL_NORMAL/SDL_MOUSEWHEEL_FLIPPED needs SDL 2.0.4 or newer)
555 Error(ERR_DEBUG, "WHEEL EVENT: mouse == %d, x/y == %d/%d, direction == %s\n",
556 event->which, event->x, event->y,
557 (event->direction == SDL_MOUSEWHEEL_NORMAL ? "SDL_MOUSEWHEEL_NORMAL" :
558 "SDL_MOUSEWHEEL_FLIPPED"));
562 button_nr = (event->x < 0 ? MB_WHEEL_LEFT :
563 event->x > 0 ? MB_WHEEL_RIGHT :
564 event->y < 0 ? MB_WHEEL_DOWN :
565 event->y > 0 ? MB_WHEEL_UP : 0);
567 #if defined(PLATFORM_WIN32) || defined(PLATFORM_MACOSX)
568 // accelerated mouse wheel available on Mac and Windows
569 wheel_steps = (event->x ? ABS(event->x) : ABS(event->y));
571 // no accelerated mouse wheel available on Unix/Linux
572 wheel_steps = DEFAULT_WHEEL_STEPS;
575 motion_status = FALSE;
577 button_status = button_nr;
578 HandleButton(0, 0, button_status, -button_nr);
580 button_status = MB_RELEASED;
581 HandleButton(0, 0, button_status, -button_nr);
584 void HandleWindowEvent(WindowEvent *event)
586 #if DEBUG_EVENTS_WINDOW
587 int subtype = event->event;
590 (subtype == SDL_WINDOWEVENT_SHOWN ? "SDL_WINDOWEVENT_SHOWN" :
591 subtype == SDL_WINDOWEVENT_HIDDEN ? "SDL_WINDOWEVENT_HIDDEN" :
592 subtype == SDL_WINDOWEVENT_EXPOSED ? "SDL_WINDOWEVENT_EXPOSED" :
593 subtype == SDL_WINDOWEVENT_MOVED ? "SDL_WINDOWEVENT_MOVED" :
594 subtype == SDL_WINDOWEVENT_SIZE_CHANGED ? "SDL_WINDOWEVENT_SIZE_CHANGED" :
595 subtype == SDL_WINDOWEVENT_RESIZED ? "SDL_WINDOWEVENT_RESIZED" :
596 subtype == SDL_WINDOWEVENT_MINIMIZED ? "SDL_WINDOWEVENT_MINIMIZED" :
597 subtype == SDL_WINDOWEVENT_MAXIMIZED ? "SDL_WINDOWEVENT_MAXIMIZED" :
598 subtype == SDL_WINDOWEVENT_RESTORED ? "SDL_WINDOWEVENT_RESTORED" :
599 subtype == SDL_WINDOWEVENT_ENTER ? "SDL_WINDOWEVENT_ENTER" :
600 subtype == SDL_WINDOWEVENT_LEAVE ? "SDL_WINDOWEVENT_LEAVE" :
601 subtype == SDL_WINDOWEVENT_FOCUS_GAINED ? "SDL_WINDOWEVENT_FOCUS_GAINED" :
602 subtype == SDL_WINDOWEVENT_FOCUS_LOST ? "SDL_WINDOWEVENT_FOCUS_LOST" :
603 subtype == SDL_WINDOWEVENT_CLOSE ? "SDL_WINDOWEVENT_CLOSE" :
606 Error(ERR_DEBUG, "WINDOW EVENT: '%s', %ld, %ld",
607 event_name, event->data1, event->data2);
611 // (not needed, as the screen gets redrawn every 20 ms anyway)
612 if (event->event == SDL_WINDOWEVENT_SIZE_CHANGED ||
613 event->event == SDL_WINDOWEVENT_RESIZED ||
614 event->event == SDL_WINDOWEVENT_EXPOSED)
618 if (event->event == SDL_WINDOWEVENT_RESIZED)
620 if (!video.fullscreen_enabled)
622 int new_window_width = event->data1;
623 int new_window_height = event->data2;
625 // if window size has changed after resizing, calculate new scaling factor
626 if (new_window_width != video.window_width ||
627 new_window_height != video.window_height)
629 int new_xpercent = 100.0 * new_window_width / video.screen_width + .5;
630 int new_ypercent = 100.0 * new_window_height / video.screen_height + .5;
632 // (extreme window scaling allowed, but cannot be saved permanently)
633 video.window_scaling_percent = MIN(new_xpercent, new_ypercent);
634 setup.window_scaling_percent =
635 MIN(MAX(MIN_WINDOW_SCALING_PERCENT, video.window_scaling_percent),
636 MAX_WINDOW_SCALING_PERCENT);
638 video.window_width = new_window_width;
639 video.window_height = new_window_height;
641 if (game_status == GAME_MODE_SETUP)
642 RedrawSetupScreenAfterFullscreenToggle();
647 #if defined(PLATFORM_ANDROID)
650 int new_display_width = event->data1;
651 int new_display_height = event->data2;
653 // if fullscreen display size has changed, device has been rotated
654 if (new_display_width != video.display_width ||
655 new_display_height != video.display_height)
657 int nr = GRID_ACTIVE_NR(); // previous screen orientation
659 video.display_width = new_display_width;
660 video.display_height = new_display_height;
662 SDLSetScreenProperties();
664 // check if screen orientation has changed (should always be true here)
665 if (nr != GRID_ACTIVE_NR())
669 if (game_status == GAME_MODE_SETUP)
670 RedrawSetupScreenAfterScreenRotation(nr);
672 nr = GRID_ACTIVE_NR();
674 overlay.grid_xsize = setup.touch.grid_xsize[nr];
675 overlay.grid_ysize = setup.touch.grid_ysize[nr];
677 for (x = 0; x < MAX_GRID_XSIZE; x++)
678 for (y = 0; y < MAX_GRID_YSIZE; y++)
679 overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
687 #define NUM_TOUCH_FINGERS 3
692 SDL_FingerID finger_id;
695 } touch_info[NUM_TOUCH_FINGERS];
697 static void HandleFingerEvent_VirtualButtons(FingerEvent *event)
700 int x = event->x * overlay.grid_xsize;
701 int y = event->y * overlay.grid_ysize;
702 int grid_button = overlay.grid_button[x][y];
703 int grid_button_action = GET_ACTION_FROM_GRID_BUTTON(grid_button);
704 Key key = (grid_button == CHAR_GRID_BUTTON_LEFT ? setup.input[0].key.left :
705 grid_button == CHAR_GRID_BUTTON_RIGHT ? setup.input[0].key.right :
706 grid_button == CHAR_GRID_BUTTON_UP ? setup.input[0].key.up :
707 grid_button == CHAR_GRID_BUTTON_DOWN ? setup.input[0].key.down :
708 grid_button == CHAR_GRID_BUTTON_SNAP ? setup.input[0].key.snap :
709 grid_button == CHAR_GRID_BUTTON_DROP ? setup.input[0].key.drop :
712 float ypos = 1.0 - 1.0 / 3.0 * video.display_width / video.display_height;
713 float event_x = (event->x);
714 float event_y = (event->y - ypos) / (1 - ypos);
715 Key key = (event_x > 0 && event_x < 1.0 / 6.0 &&
716 event_y > 2.0 / 3.0 && event_y < 1 ?
717 setup.input[0].key.snap :
718 event_x > 1.0 / 6.0 && event_x < 1.0 / 3.0 &&
719 event_y > 2.0 / 3.0 && event_y < 1 ?
720 setup.input[0].key.drop :
721 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
722 event_y > 0 && event_y < 1.0 / 3.0 ?
723 setup.input[0].key.up :
724 event_x > 6.0 / 9.0 && event_x < 7.0 / 9.0 &&
725 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
726 setup.input[0].key.left :
727 event_x > 8.0 / 9.0 && event_x < 1 &&
728 event_y > 1.0 / 3.0 && event_y < 2.0 / 3.0 ?
729 setup.input[0].key.right :
730 event_x > 7.0 / 9.0 && event_x < 8.0 / 9.0 &&
731 event_y > 2.0 / 3.0 && event_y < 1 ?
732 setup.input[0].key.down :
735 int key_status = (event->type == EVENT_FINGERRELEASE ? KEY_RELEASED :
737 char *key_status_name = (key_status == KEY_RELEASED ? "KEY_RELEASED" :
741 // for any touch input event, enable overlay buttons (if activated)
742 SetOverlayEnabled(TRUE);
744 Error(ERR_DEBUG, "::: key '%s' was '%s' [fingerId: %lld]",
745 getKeyNameFromKey(key), key_status_name, event->fingerId);
747 if (key_status == KEY_PRESSED)
748 overlay.grid_button_action |= grid_button_action;
750 overlay.grid_button_action &= ~grid_button_action;
752 // check if we already know this touch event's finger id
753 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
755 if (touch_info[i].touched &&
756 touch_info[i].finger_id == event->fingerId)
758 // Error(ERR_DEBUG, "MARK 1: %d", i);
764 if (i >= NUM_TOUCH_FINGERS)
766 if (key_status == KEY_PRESSED)
768 int oldest_pos = 0, oldest_counter = touch_info[0].counter;
770 // unknown finger id -- get new, empty slot, if available
771 for (i = 0; i < NUM_TOUCH_FINGERS; i++)
773 if (touch_info[i].counter < oldest_counter)
776 oldest_counter = touch_info[i].counter;
778 // Error(ERR_DEBUG, "MARK 2: %d", i);
781 if (!touch_info[i].touched)
783 // Error(ERR_DEBUG, "MARK 3: %d", i);
789 if (i >= NUM_TOUCH_FINGERS)
791 // all slots allocated -- use oldest slot
794 // Error(ERR_DEBUG, "MARK 4: %d", i);
799 // release of previously unknown key (should not happen)
801 if (key != KSYM_UNDEFINED)
803 HandleKey(key, KEY_RELEASED);
805 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [1]",
806 getKeyNameFromKey(key), "KEY_RELEASED", i);
811 if (i < NUM_TOUCH_FINGERS)
813 if (key_status == KEY_PRESSED)
815 if (touch_info[i].key != key)
817 if (touch_info[i].key != KSYM_UNDEFINED)
819 HandleKey(touch_info[i].key, KEY_RELEASED);
821 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [2]",
822 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
825 if (key != KSYM_UNDEFINED)
827 HandleKey(key, KEY_PRESSED);
829 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [3]",
830 getKeyNameFromKey(key), "KEY_PRESSED", i);
834 touch_info[i].touched = TRUE;
835 touch_info[i].finger_id = event->fingerId;
836 touch_info[i].counter = Counter();
837 touch_info[i].key = key;
841 if (touch_info[i].key != KSYM_UNDEFINED)
843 HandleKey(touch_info[i].key, KEY_RELEASED);
845 Error(ERR_DEBUG, "=> key == '%s', key_status == '%s' [slot %d] [4]",
846 getKeyNameFromKey(touch_info[i].key), "KEY_RELEASED", i);
849 touch_info[i].touched = FALSE;
850 touch_info[i].finger_id = 0;
851 touch_info[i].counter = 0;
852 touch_info[i].key = 0;
857 static void HandleFingerEvent_WipeGestures(FingerEvent *event)
859 static Key motion_key_x = KSYM_UNDEFINED;
860 static Key motion_key_y = KSYM_UNDEFINED;
861 static Key button_key = KSYM_UNDEFINED;
862 static float motion_x1, motion_y1;
863 static float button_x1, button_y1;
864 static SDL_FingerID motion_id = -1;
865 static SDL_FingerID button_id = -1;
866 int move_trigger_distance_percent = setup.touch.move_distance;
867 int drop_trigger_distance_percent = setup.touch.drop_distance;
868 float move_trigger_distance = (float)move_trigger_distance_percent / 100;
869 float drop_trigger_distance = (float)drop_trigger_distance_percent / 100;
870 float event_x = event->x;
871 float event_y = event->y;
873 if (event->type == EVENT_FINGERPRESS)
875 if (event_x > 1.0 / 3.0)
879 motion_id = event->fingerId;
884 motion_key_x = KSYM_UNDEFINED;
885 motion_key_y = KSYM_UNDEFINED;
887 Error(ERR_DEBUG, "---------- MOVE STARTED (WAIT) ----------");
893 button_id = event->fingerId;
898 button_key = setup.input[0].key.snap;
900 HandleKey(button_key, KEY_PRESSED);
902 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
905 else if (event->type == EVENT_FINGERRELEASE)
907 if (event->fingerId == motion_id)
911 if (motion_key_x != KSYM_UNDEFINED)
912 HandleKey(motion_key_x, KEY_RELEASED);
913 if (motion_key_y != KSYM_UNDEFINED)
914 HandleKey(motion_key_y, KEY_RELEASED);
916 motion_key_x = KSYM_UNDEFINED;
917 motion_key_y = KSYM_UNDEFINED;
919 Error(ERR_DEBUG, "---------- MOVE STOPPED ----------");
921 else if (event->fingerId == button_id)
925 if (button_key != KSYM_UNDEFINED)
926 HandleKey(button_key, KEY_RELEASED);
928 button_key = KSYM_UNDEFINED;
930 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
933 else if (event->type == EVENT_FINGERMOTION)
935 if (event->fingerId == motion_id)
937 float distance_x = ABS(event_x - motion_x1);
938 float distance_y = ABS(event_y - motion_y1);
939 Key new_motion_key_x = (event_x < motion_x1 ? setup.input[0].key.left :
940 event_x > motion_x1 ? setup.input[0].key.right :
942 Key new_motion_key_y = (event_y < motion_y1 ? setup.input[0].key.up :
943 event_y > motion_y1 ? setup.input[0].key.down :
946 if (distance_x < move_trigger_distance / 2 ||
947 distance_x < distance_y)
948 new_motion_key_x = KSYM_UNDEFINED;
950 if (distance_y < move_trigger_distance / 2 ||
951 distance_y < distance_x)
952 new_motion_key_y = KSYM_UNDEFINED;
954 if (distance_x > move_trigger_distance ||
955 distance_y > move_trigger_distance)
957 if (new_motion_key_x != motion_key_x)
959 if (motion_key_x != KSYM_UNDEFINED)
960 HandleKey(motion_key_x, KEY_RELEASED);
961 if (new_motion_key_x != KSYM_UNDEFINED)
962 HandleKey(new_motion_key_x, KEY_PRESSED);
965 if (new_motion_key_y != motion_key_y)
967 if (motion_key_y != KSYM_UNDEFINED)
968 HandleKey(motion_key_y, KEY_RELEASED);
969 if (new_motion_key_y != KSYM_UNDEFINED)
970 HandleKey(new_motion_key_y, KEY_PRESSED);
976 motion_key_x = new_motion_key_x;
977 motion_key_y = new_motion_key_y;
979 Error(ERR_DEBUG, "---------- MOVE STARTED (MOVE) ----------");
982 else if (event->fingerId == button_id)
984 float distance_x = ABS(event_x - button_x1);
985 float distance_y = ABS(event_y - button_y1);
987 if (distance_x < drop_trigger_distance / 2 &&
988 distance_y > drop_trigger_distance)
990 if (button_key == setup.input[0].key.snap)
991 HandleKey(button_key, KEY_RELEASED);
996 button_key = setup.input[0].key.drop;
998 HandleKey(button_key, KEY_PRESSED);
1000 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1006 void HandleFingerEvent(FingerEvent *event)
1008 #if DEBUG_EVENTS_FINGER
1009 Error(ERR_DEBUG, "FINGER EVENT: finger was %s, touch ID %lld, finger ID %lld, x/y %f/%f, dx/dy %f/%f, pressure %f",
1010 event->type == EVENT_FINGERPRESS ? "pressed" :
1011 event->type == EVENT_FINGERRELEASE ? "released" : "moved",
1015 event->dx, event->dy,
1019 if (game_status != GAME_MODE_PLAYING)
1022 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1024 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1025 local_player->mouse_action.button_hint =
1026 (event->type == EVENT_FINGERRELEASE ? MB_NOT_PRESSED :
1027 event->x < 0.5 ? MB_LEFTBUTTON :
1028 event->x > 0.5 ? MB_RIGHTBUTTON :
1034 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1035 HandleFingerEvent_VirtualButtons(event);
1036 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1037 HandleFingerEvent_WipeGestures(event);
1042 static void HandleButtonOrFinger_WipeGestures_MM(int mx, int my, int button)
1044 static int old_mx = 0, old_my = 0;
1045 static int last_button = MB_LEFTBUTTON;
1046 static boolean touched = FALSE;
1047 static boolean tapped = FALSE;
1049 // screen tile was tapped (but finger not touching the screen anymore)
1050 // (this point will also be reached without receiving a touch event)
1051 if (tapped && !touched)
1053 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1058 // stop here if this function was not triggered by a touch event
1062 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1064 // finger started touching the screen
1074 ClearPlayerMouseAction();
1076 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1079 else if (button == MB_RELEASED && touched)
1081 // finger stopped touching the screen
1086 SetPlayerMouseAction(old_mx, old_my, last_button);
1088 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1090 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1095 // finger moved while touching the screen
1097 int old_x = getLevelFromScreenX(old_mx);
1098 int old_y = getLevelFromScreenY(old_my);
1099 int new_x = getLevelFromScreenX(mx);
1100 int new_y = getLevelFromScreenY(my);
1102 if (new_x != old_x || new_y != old_y)
1107 // finger moved left or right from (horizontal) starting position
1109 int button_nr = (new_x < old_x ? MB_LEFTBUTTON : MB_RIGHTBUTTON);
1111 SetPlayerMouseAction(old_mx, old_my, button_nr);
1113 last_button = button_nr;
1115 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1119 // finger stays at or returned to (horizontal) starting position
1121 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1123 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1128 static void HandleButtonOrFinger_FollowFinger_MM(int mx, int my, int button)
1130 static int old_mx = 0, old_my = 0;
1131 static int last_button = MB_LEFTBUTTON;
1132 static boolean touched = FALSE;
1133 static boolean tapped = FALSE;
1135 // screen tile was tapped (but finger not touching the screen anymore)
1136 // (this point will also be reached without receiving a touch event)
1137 if (tapped && !touched)
1139 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1144 // stop here if this function was not triggered by a touch event
1148 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1150 // finger started touching the screen
1160 ClearPlayerMouseAction();
1162 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1165 else if (button == MB_RELEASED && touched)
1167 // finger stopped touching the screen
1172 SetPlayerMouseAction(old_mx, old_my, last_button);
1174 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1176 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1181 // finger moved while touching the screen
1183 int old_x = getLevelFromScreenX(old_mx);
1184 int old_y = getLevelFromScreenY(old_my);
1185 int new_x = getLevelFromScreenX(mx);
1186 int new_y = getLevelFromScreenY(my);
1188 if (new_x != old_x || new_y != old_y)
1190 // finger moved away from starting position
1192 int button_nr = getButtonFromTouchPosition(old_x, old_y, mx, my);
1194 // quickly alternate between clicking and releasing for maximum speed
1195 if (FrameCounter % 2 == 0)
1196 button_nr = MB_RELEASED;
1198 SetPlayerMouseAction(old_mx, old_my, button_nr);
1201 last_button = button_nr;
1205 Error(ERR_DEBUG, "---------- TOUCH ACTION: ROTATING ----------");
1209 // finger stays at or returned to starting position
1211 SetPlayerMouseAction(old_mx, old_my, MB_RELEASED);
1213 Error(ERR_DEBUG, "---------- TOUCH ACTION PAUSED ----------");
1218 static void HandleButtonOrFinger_FollowFinger(int mx, int my, int button)
1220 static int old_mx = 0, old_my = 0;
1221 static Key motion_key_x = KSYM_UNDEFINED;
1222 static Key motion_key_y = KSYM_UNDEFINED;
1223 static boolean touched = FALSE;
1224 static boolean started_on_player = FALSE;
1225 static boolean player_is_dropping = FALSE;
1226 static int player_drop_count = 0;
1227 static int last_player_x = -1;
1228 static int last_player_y = -1;
1230 if (button == MB_PRESSED && IN_GFX_FIELD_PLAY(mx, my))
1239 started_on_player = FALSE;
1240 player_is_dropping = FALSE;
1241 player_drop_count = 0;
1245 motion_key_x = KSYM_UNDEFINED;
1246 motion_key_y = KSYM_UNDEFINED;
1248 Error(ERR_DEBUG, "---------- TOUCH ACTION STARTED ----------");
1251 else if (button == MB_RELEASED && touched)
1258 if (motion_key_x != KSYM_UNDEFINED)
1259 HandleKey(motion_key_x, KEY_RELEASED);
1260 if (motion_key_y != KSYM_UNDEFINED)
1261 HandleKey(motion_key_y, KEY_RELEASED);
1263 if (started_on_player)
1265 if (player_is_dropping)
1267 Error(ERR_DEBUG, "---------- DROP STOPPED ----------");
1269 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1273 Error(ERR_DEBUG, "---------- SNAP STOPPED ----------");
1275 HandleKey(setup.input[0].key.snap, KEY_RELEASED);
1279 motion_key_x = KSYM_UNDEFINED;
1280 motion_key_y = KSYM_UNDEFINED;
1282 Error(ERR_DEBUG, "---------- TOUCH ACTION STOPPED ----------");
1287 int src_x = local_player->jx;
1288 int src_y = local_player->jy;
1289 int dst_x = getLevelFromScreenX(old_mx);
1290 int dst_y = getLevelFromScreenY(old_my);
1291 int dx = dst_x - src_x;
1292 int dy = dst_y - src_y;
1293 Key new_motion_key_x = (dx < 0 ? setup.input[0].key.left :
1294 dx > 0 ? setup.input[0].key.right :
1296 Key new_motion_key_y = (dy < 0 ? setup.input[0].key.up :
1297 dy > 0 ? setup.input[0].key.down :
1300 if (dx != 0 && dy != 0 && ABS(dx) != ABS(dy) &&
1301 (last_player_x != local_player->jx ||
1302 last_player_y != local_player->jy))
1304 // in case of asymmetric diagonal movement, use "preferred" direction
1306 int last_move_dir = (ABS(dx) > ABS(dy) ? MV_VERTICAL : MV_HORIZONTAL);
1308 if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
1309 level.native_em_level->ply[0]->last_move_dir = last_move_dir;
1311 local_player->last_move_dir = last_move_dir;
1313 // (required to prevent accidentally forcing direction for next movement)
1314 last_player_x = local_player->jx;
1315 last_player_y = local_player->jy;
1318 if (button == MB_PRESSED && !motion_status && dx == 0 && dy == 0)
1320 started_on_player = TRUE;
1321 player_drop_count = getPlayerInventorySize(0);
1322 player_is_dropping = (player_drop_count > 0);
1324 if (player_is_dropping)
1326 Error(ERR_DEBUG, "---------- DROP STARTED ----------");
1328 HandleKey(setup.input[0].key.drop, KEY_PRESSED);
1332 Error(ERR_DEBUG, "---------- SNAP STARTED ----------");
1334 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1337 else if (dx != 0 || dy != 0)
1339 if (player_is_dropping &&
1340 player_drop_count == getPlayerInventorySize(0))
1342 Error(ERR_DEBUG, "---------- DROP -> SNAP ----------");
1344 HandleKey(setup.input[0].key.drop, KEY_RELEASED);
1345 HandleKey(setup.input[0].key.snap, KEY_PRESSED);
1347 player_is_dropping = FALSE;
1351 if (new_motion_key_x != motion_key_x)
1353 Error(ERR_DEBUG, "---------- %s %s ----------",
1354 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1355 dx < 0 ? "LEFT" : dx > 0 ? "RIGHT" : "PAUSED");
1357 if (motion_key_x != KSYM_UNDEFINED)
1358 HandleKey(motion_key_x, KEY_RELEASED);
1359 if (new_motion_key_x != KSYM_UNDEFINED)
1360 HandleKey(new_motion_key_x, KEY_PRESSED);
1363 if (new_motion_key_y != motion_key_y)
1365 Error(ERR_DEBUG, "---------- %s %s ----------",
1366 started_on_player && !player_is_dropping ? "SNAPPING" : "MOVING",
1367 dy < 0 ? "UP" : dy > 0 ? "DOWN" : "PAUSED");
1369 if (motion_key_y != KSYM_UNDEFINED)
1370 HandleKey(motion_key_y, KEY_RELEASED);
1371 if (new_motion_key_y != KSYM_UNDEFINED)
1372 HandleKey(new_motion_key_y, KEY_PRESSED);
1375 motion_key_x = new_motion_key_x;
1376 motion_key_y = new_motion_key_y;
1380 static void HandleButtonOrFinger(int mx, int my, int button)
1382 if (game_status != GAME_MODE_PLAYING)
1385 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1387 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_WIPE_GESTURES))
1388 HandleButtonOrFinger_WipeGestures_MM(mx, my, button);
1389 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1390 HandleButtonOrFinger_FollowFinger_MM(mx, my, button);
1391 else if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
1392 SetPlayerMouseAction(mx, my, button); // special case
1396 if (strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER))
1397 HandleButtonOrFinger_FollowFinger(mx, my, button);
1401 #if defined(TARGET_SDL2)
1403 static boolean checkTextInputKeyModState(void)
1405 // when playing, only handle raw key events and ignore text input
1406 if (game_status == GAME_MODE_PLAYING)
1409 return ((GetKeyModState() & KMOD_TextInput) != KMOD_None);
1412 void HandleTextEvent(TextEvent *event)
1414 char *text = event->text;
1415 Key key = getKeyFromKeyName(text);
1417 #if DEBUG_EVENTS_TEXT
1418 Error(ERR_DEBUG, "TEXT EVENT: text == '%s' [%d byte(s), '%c'/%d], resulting key == %d (%s) [%04x]",
1421 text[0], (int)(text[0]),
1423 getKeyNameFromKey(key),
1427 #if !defined(HAS_SCREEN_KEYBOARD)
1428 // non-mobile devices: only handle key input with modifier keys pressed here
1429 // (every other key input is handled directly as physical key input event)
1430 if (!checkTextInputKeyModState())
1434 // process text input as "classic" (with uppercase etc.) key input event
1435 HandleKey(key, KEY_PRESSED);
1436 HandleKey(key, KEY_RELEASED);
1439 void HandlePauseResumeEvent(PauseResumeEvent *event)
1441 if (event->type == SDL_APP_WILLENTERBACKGROUND)
1445 else if (event->type == SDL_APP_DIDENTERFOREGROUND)
1453 void HandleKeyEvent(KeyEvent *event)
1455 int key_status = (event->type == EVENT_KEYPRESS ? KEY_PRESSED : KEY_RELEASED);
1456 boolean with_modifiers = (game_status == GAME_MODE_PLAYING ? FALSE : TRUE);
1457 Key key = GetEventKey(event, with_modifiers);
1458 Key keymod = (with_modifiers ? GetEventKey(event, FALSE) : key);
1460 #if DEBUG_EVENTS_KEY
1461 Error(ERR_DEBUG, "KEY EVENT: key was %s, keysym.scancode == %d, keysym.sym == %d, keymod = %d, GetKeyModState() = 0x%04x, resulting key == %d (%s)",
1462 event->type == EVENT_KEYPRESS ? "pressed" : "released",
1463 event->keysym.scancode,
1468 getKeyNameFromKey(key));
1471 #if defined(PLATFORM_ANDROID)
1472 if (key == KSYM_Back)
1474 // always map the "back" button to the "escape" key on Android devices
1479 // for any key event other than "back" button, disable overlay buttons
1480 SetOverlayEnabled(FALSE);
1484 HandleKeyModState(keymod, key_status);
1486 #if defined(TARGET_SDL2)
1487 // only handle raw key input without text modifier keys pressed
1488 if (!checkTextInputKeyModState())
1489 HandleKey(key, key_status);
1491 HandleKey(key, key_status);
1495 void HandleFocusEvent(FocusChangeEvent *event)
1497 static int old_joystick_status = -1;
1499 if (event->type == EVENT_FOCUSOUT)
1501 KeyboardAutoRepeatOn();
1502 old_joystick_status = joystick.status;
1503 joystick.status = JOYSTICK_NOT_AVAILABLE;
1505 ClearPlayerAction();
1507 else if (event->type == EVENT_FOCUSIN)
1509 /* When there are two Rocks'n'Diamonds windows which overlap and
1510 the player moves the pointer from one game window to the other,
1511 a 'FocusOut' event is generated for the window the pointer is
1512 leaving and a 'FocusIn' event is generated for the window the
1513 pointer is entering. In some cases, it can happen that the
1514 'FocusIn' event is handled by the one game process before the
1515 'FocusOut' event by the other game process. In this case the
1516 X11 environment would end up with activated keyboard auto repeat,
1517 because unfortunately this is a global setting and not (which
1518 would be far better) set for each X11 window individually.
1519 The effect would be keyboard auto repeat while playing the game
1520 (game_status == GAME_MODE_PLAYING), which is not desired.
1521 To avoid this special case, we just wait 1/10 second before
1522 processing the 'FocusIn' event. */
1524 if (game_status == GAME_MODE_PLAYING)
1527 KeyboardAutoRepeatOffUnlessAutoplay();
1530 if (old_joystick_status != -1)
1531 joystick.status = old_joystick_status;
1535 void HandleClientMessageEvent(ClientMessageEvent *event)
1537 if (CheckCloseWindowEvent(event))
1541 void HandleWindowManagerEvent(Event *event)
1543 #if defined(TARGET_SDL)
1544 SDLHandleWindowManagerEvent(event);
1548 void HandleButton(int mx, int my, int button, int button_nr)
1550 static int old_mx = 0, old_my = 0;
1551 boolean button_hold = FALSE;
1552 boolean handle_gadgets = TRUE;
1558 button_nr = -button_nr;
1567 #if defined(PLATFORM_ANDROID)
1568 // when playing, only handle gadgets when using "follow finger" controls
1569 // or when using touch controls in combination with the MM game engine
1571 (game_status != GAME_MODE_PLAYING ||
1572 level.game_engine_type == GAME_ENGINE_TYPE_MM ||
1573 strEqual(setup.touch.control_type, TOUCH_CONTROL_FOLLOW_FINGER));
1576 if (HandleGlobalAnimClicks(mx, my, button))
1578 // do not handle this button event anymore
1579 return; // force mouse event not to be handled at all
1582 if (handle_gadgets && HandleGadgets(mx, my, button))
1584 // do not handle this button event anymore
1585 mx = my = -32; // force mouse event to be outside screen tiles
1588 if (button_hold && game_status == GAME_MODE_PLAYING && tape.pausing)
1591 // do not use scroll wheel button events for anything other than gadgets
1592 if (IS_WHEEL_BUTTON(button_nr))
1595 switch (game_status)
1597 case GAME_MODE_TITLE:
1598 HandleTitleScreen(mx, my, 0, 0, button);
1601 case GAME_MODE_MAIN:
1602 HandleMainMenu(mx, my, 0, 0, button);
1605 case GAME_MODE_PSEUDO_TYPENAME:
1606 HandleTypeName(0, KSYM_Return);
1609 case GAME_MODE_LEVELS:
1610 HandleChooseLevelSet(mx, my, 0, 0, button);
1613 case GAME_MODE_LEVELNR:
1614 HandleChooseLevelNr(mx, my, 0, 0, button);
1617 case GAME_MODE_SCORES:
1618 HandleHallOfFame(0, 0, 0, 0, button);
1621 case GAME_MODE_EDITOR:
1622 HandleLevelEditorIdle();
1625 case GAME_MODE_INFO:
1626 HandleInfoScreen(mx, my, 0, 0, button);
1629 case GAME_MODE_SETUP:
1630 HandleSetupScreen(mx, my, 0, 0, button);
1633 case GAME_MODE_PLAYING:
1634 if (!strEqual(setup.touch.control_type, TOUCH_CONTROL_OFF))
1635 HandleButtonOrFinger(mx, my, button);
1637 SetPlayerMouseAction(mx, my, button);
1640 if (button == MB_PRESSED && !motion_status && !button_hold &&
1641 IN_GFX_FIELD_PLAY(mx, my) && GetKeyModState() & KMOD_Control)
1642 DumpTileFromScreen(mx, my);
1652 static boolean is_string_suffix(char *string, char *suffix)
1654 int string_len = strlen(string);
1655 int suffix_len = strlen(suffix);
1657 if (suffix_len > string_len)
1660 return (strEqual(&string[string_len - suffix_len], suffix));
1663 #define MAX_CHEAT_INPUT_LEN 32
1665 static void HandleKeysSpecial(Key key)
1667 static char cheat_input[2 * MAX_CHEAT_INPUT_LEN + 1] = "";
1668 char letter = getCharFromKey(key);
1669 int cheat_input_len = strlen(cheat_input);
1675 if (cheat_input_len >= 2 * MAX_CHEAT_INPUT_LEN)
1677 for (i = 0; i < MAX_CHEAT_INPUT_LEN + 1; i++)
1678 cheat_input[i] = cheat_input[MAX_CHEAT_INPUT_LEN + i];
1680 cheat_input_len = MAX_CHEAT_INPUT_LEN;
1683 cheat_input[cheat_input_len++] = letter;
1684 cheat_input[cheat_input_len] = '\0';
1686 #if DEBUG_EVENTS_KEY
1687 Error(ERR_DEBUG, "SPECIAL KEY '%s' [%d]\n", cheat_input, cheat_input_len);
1690 if (game_status == GAME_MODE_MAIN)
1692 if (is_string_suffix(cheat_input, ":insert-solution-tape") ||
1693 is_string_suffix(cheat_input, ":ist"))
1695 InsertSolutionTape();
1697 else if (is_string_suffix(cheat_input, ":play-solution-tape") ||
1698 is_string_suffix(cheat_input, ":pst"))
1702 else if (is_string_suffix(cheat_input, ":reload-graphics") ||
1703 is_string_suffix(cheat_input, ":rg"))
1705 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS);
1708 else if (is_string_suffix(cheat_input, ":reload-sounds") ||
1709 is_string_suffix(cheat_input, ":rs"))
1711 ReloadCustomArtwork(1 << ARTWORK_TYPE_SOUNDS);
1714 else if (is_string_suffix(cheat_input, ":reload-music") ||
1715 is_string_suffix(cheat_input, ":rm"))
1717 ReloadCustomArtwork(1 << ARTWORK_TYPE_MUSIC);
1720 else if (is_string_suffix(cheat_input, ":reload-artwork") ||
1721 is_string_suffix(cheat_input, ":ra"))
1723 ReloadCustomArtwork(1 << ARTWORK_TYPE_GRAPHICS |
1724 1 << ARTWORK_TYPE_SOUNDS |
1725 1 << ARTWORK_TYPE_MUSIC);
1728 else if (is_string_suffix(cheat_input, ":dump-level") ||
1729 is_string_suffix(cheat_input, ":dl"))
1733 else if (is_string_suffix(cheat_input, ":dump-tape") ||
1734 is_string_suffix(cheat_input, ":dt"))
1738 else if (is_string_suffix(cheat_input, ":fix-tape") ||
1739 is_string_suffix(cheat_input, ":ft"))
1741 /* fix single-player tapes that contain player input for more than one
1742 player (due to a bug in 3.3.1.2 and earlier versions), which results
1743 in playing levels with more than one player in multi-player mode,
1744 even though the tape was originally recorded in single-player mode */
1746 // remove player input actions for all players but the first one
1747 for (i = 1; i < MAX_PLAYERS; i++)
1748 tape.player_participates[i] = FALSE;
1750 tape.changed = TRUE;
1752 else if (is_string_suffix(cheat_input, ":save-native-level") ||
1753 is_string_suffix(cheat_input, ":snl"))
1755 SaveNativeLevel(&level);
1757 else if (is_string_suffix(cheat_input, ":frames-per-second") ||
1758 is_string_suffix(cheat_input, ":fps"))
1760 global.show_frames_per_second = !global.show_frames_per_second;
1763 else if (game_status == GAME_MODE_PLAYING)
1766 if (is_string_suffix(cheat_input, ".q"))
1767 DEBUG_SetMaximumDynamite();
1770 else if (game_status == GAME_MODE_EDITOR)
1772 if (is_string_suffix(cheat_input, ":dump-brush") ||
1773 is_string_suffix(cheat_input, ":DB"))
1777 else if (is_string_suffix(cheat_input, ":DDB"))
1783 // special key shortcuts for all game modes
1784 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1785 is_string_suffix(cheat_input, ":dea") ||
1786 is_string_suffix(cheat_input, ":DEA"))
1788 DumpGadgetIdentifiers();
1789 DumpScreenIdentifiers();
1793 void HandleKeysDebug(Key key)
1798 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1800 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1802 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1804 if (key == setup.debug.frame_delay_key[i] &&
1805 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1807 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1808 setup.debug.frame_delay[i] : setup.game_frame_delay);
1810 if (!setup.debug.frame_delay_game_only)
1811 MenuFrameDelay = GameFrameDelay;
1813 SetVideoFrameDelay(GameFrameDelay);
1815 if (GameFrameDelay > ONE_SECOND_DELAY)
1816 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1817 else if (GameFrameDelay != 0)
1818 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1819 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1820 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1822 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1829 if (game_status == GAME_MODE_PLAYING)
1833 options.debug = !options.debug;
1835 Error(ERR_DEBUG, "debug mode %s",
1836 (options.debug ? "enabled" : "disabled"));
1838 else if (key == KSYM_v)
1840 Error(ERR_DEBUG, "currently using game engine version %d",
1841 game.engine_version);
1847 void HandleKey(Key key, int key_status)
1849 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1850 static boolean ignore_repeated_key = FALSE;
1851 static struct SetupKeyboardInfo ski;
1852 static struct SetupShortcutInfo ssi;
1861 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1862 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1863 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1864 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1865 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1866 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1871 #if defined(TARGET_SDL2)
1872 // map special keys (media keys / remote control buttons) to default keys
1873 if (key == KSYM_PlayPause)
1875 else if (key == KSYM_Select)
1879 HandleSpecialGameControllerKeys(key, key_status);
1881 if (game_status == GAME_MODE_PLAYING)
1883 // only needed for single-step tape recording mode
1884 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1887 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1889 byte key_action = 0;
1891 if (setup.input[pnr].use_joystick)
1894 ski = setup.input[pnr].key;
1896 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1897 if (key == *key_info[i].key_custom)
1898 key_action |= key_info[i].action;
1900 // use combined snap+direction keys for the first player only
1903 ssi = setup.shortcut;
1905 for (i = 0; i < NUM_DIRECTIONS; i++)
1906 if (key == *key_info[i].key_snap)
1907 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1910 if (key_status == KEY_PRESSED)
1911 stored_player[pnr].action |= key_action;
1913 stored_player[pnr].action &= ~key_action;
1915 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1917 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1919 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1921 // if snap key already pressed, keep pause mode when releasing
1922 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1923 has_snapped[pnr] = TRUE;
1925 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1927 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1929 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1930 getRedDiskReleaseFlag_SP() == 0)
1932 // add a single inactive frame before dropping starts
1933 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1934 stored_player[pnr].force_dropping = TRUE;
1937 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1939 // if snap key was pressed without direction, leave pause mode
1940 if (!has_snapped[pnr])
1941 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1943 has_snapped[pnr] = FALSE;
1946 else if (tape.recording && tape.pausing && !tape.use_mouse)
1948 // prevent key release events from un-pausing a paused game
1949 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1950 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1953 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1954 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1960 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1961 if (key == key_info[i].key_default)
1962 joy |= key_info[i].action;
1967 if (key_status == KEY_PRESSED)
1968 key_joystick_mapping |= joy;
1970 key_joystick_mapping &= ~joy;
1975 if (game_status != GAME_MODE_PLAYING)
1976 key_joystick_mapping = 0;
1978 if (key_status == KEY_RELEASED)
1980 // reset flag to ignore repeated "key pressed" events after key release
1981 ignore_repeated_key = FALSE;
1986 if ((key == KSYM_F11 ||
1987 ((key == KSYM_Return ||
1988 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
1989 video.fullscreen_available &&
1990 !ignore_repeated_key)
1992 setup.fullscreen = !setup.fullscreen;
1994 ToggleFullscreenOrChangeWindowScalingIfNeeded();
1996 if (game_status == GAME_MODE_SETUP)
1997 RedrawSetupScreenAfterFullscreenToggle();
1999 // set flag to ignore repeated "key pressed" events
2000 ignore_repeated_key = TRUE;
2005 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2006 key == KSYM_minus || key == KSYM_KP_Subtract ||
2007 key == KSYM_plus || key == KSYM_KP_Add ||
2008 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2009 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2010 video.window_scaling_available &&
2011 !video.fullscreen_enabled)
2013 if (key == KSYM_0 || key == KSYM_KP_0)
2014 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2015 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2016 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2018 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2020 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2021 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2022 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2023 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2025 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2027 if (game_status == GAME_MODE_SETUP)
2028 RedrawSetupScreenAfterFullscreenToggle();
2033 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2034 key == KSYM_Return ||
2035 key == KSYM_Escape)))
2037 // do not handle this key event anymore
2038 if (key != KSYM_Escape) // always allow ESC key to be handled
2042 if (game_status == GAME_MODE_PLAYING && AllPlayersGone &&
2043 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2050 if (game_status == GAME_MODE_MAIN &&
2051 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2053 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2058 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2060 if (key == setup.shortcut.save_game)
2062 else if (key == setup.shortcut.load_game)
2064 else if (key == setup.shortcut.toggle_pause)
2065 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2067 HandleTapeButtonKeys(key);
2068 HandleSoundButtonKeys(key);
2071 if (game_status == GAME_MODE_PLAYING && !network_playing)
2073 int centered_player_nr_next = -999;
2075 if (key == setup.shortcut.focus_player_all)
2076 centered_player_nr_next = -1;
2078 for (i = 0; i < MAX_PLAYERS; i++)
2079 if (key == setup.shortcut.focus_player[i])
2080 centered_player_nr_next = i;
2082 if (centered_player_nr_next != -999)
2084 game.centered_player_nr_next = centered_player_nr_next;
2085 game.set_centered_player = TRUE;
2089 tape.centered_player_nr_next = game.centered_player_nr_next;
2090 tape.set_centered_player = TRUE;
2095 HandleKeysSpecial(key);
2097 if (HandleGadgetsKeyInput(key))
2099 if (key != KSYM_Escape) // always allow ESC key to be handled
2100 key = KSYM_UNDEFINED;
2103 switch (game_status)
2105 case GAME_MODE_PSEUDO_TYPENAME:
2106 HandleTypeName(0, key);
2109 case GAME_MODE_TITLE:
2110 case GAME_MODE_MAIN:
2111 case GAME_MODE_LEVELS:
2112 case GAME_MODE_LEVELNR:
2113 case GAME_MODE_SETUP:
2114 case GAME_MODE_INFO:
2115 case GAME_MODE_SCORES:
2117 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2124 if (game_status == GAME_MODE_TITLE)
2125 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2126 else if (game_status == GAME_MODE_MAIN)
2127 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2128 else if (game_status == GAME_MODE_LEVELS)
2129 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2130 else if (game_status == GAME_MODE_LEVELNR)
2131 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2132 else if (game_status == GAME_MODE_SETUP)
2133 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2134 else if (game_status == GAME_MODE_INFO)
2135 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2136 else if (game_status == GAME_MODE_SCORES)
2137 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2141 if (game_status != GAME_MODE_MAIN)
2142 FadeSkipNextFadeIn();
2144 if (game_status == GAME_MODE_TITLE)
2145 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2146 else if (game_status == GAME_MODE_LEVELS)
2147 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2148 else if (game_status == GAME_MODE_LEVELNR)
2149 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2150 else if (game_status == GAME_MODE_SETUP)
2151 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2152 else if (game_status == GAME_MODE_INFO)
2153 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2154 else if (game_status == GAME_MODE_SCORES)
2155 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2159 if (game_status == GAME_MODE_LEVELS)
2160 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2161 else if (game_status == GAME_MODE_LEVELNR)
2162 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2163 else if (game_status == GAME_MODE_SETUP)
2164 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2165 else if (game_status == GAME_MODE_INFO)
2166 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2167 else if (game_status == GAME_MODE_SCORES)
2168 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2171 case KSYM_Page_Down:
2172 if (game_status == GAME_MODE_LEVELS)
2173 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2174 else if (game_status == GAME_MODE_LEVELNR)
2175 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2176 else if (game_status == GAME_MODE_SETUP)
2177 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2178 else if (game_status == GAME_MODE_INFO)
2179 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2180 else if (game_status == GAME_MODE_SCORES)
2181 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2189 case GAME_MODE_EDITOR:
2190 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2191 HandleLevelEditorKeyInput(key);
2194 case GAME_MODE_PLAYING:
2199 RequestQuitGame(setup.ask_on_escape);
2209 if (key == KSYM_Escape)
2211 SetGameStatus(GAME_MODE_MAIN);
2219 HandleKeysDebug(key);
2222 void HandleNoEvent(void)
2224 HandleMouseCursor();
2226 switch (game_status)
2228 case GAME_MODE_PLAYING:
2229 HandleButtonOrFinger(-1, -1, -1);
2234 void HandleEventActions(void)
2236 // if (button_status && game_status != GAME_MODE_PLAYING)
2237 if (button_status && (game_status != GAME_MODE_PLAYING ||
2239 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2241 HandleButton(0, 0, button_status, -button_status);
2248 if (network.enabled)
2251 switch (game_status)
2253 case GAME_MODE_MAIN:
2254 DrawPreviewLevelAnimation();
2257 case GAME_MODE_EDITOR:
2258 HandleLevelEditorIdle();
2266 static void HandleTileCursor(int dx, int dy, int button)
2269 ClearPlayerMouseAction();
2276 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2277 (dx < 0 ? MB_LEFTBUTTON :
2278 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2280 else if (!tile_cursor.moving)
2282 int old_xpos = tile_cursor.xpos;
2283 int old_ypos = tile_cursor.ypos;
2284 int new_xpos = old_xpos;
2285 int new_ypos = old_ypos;
2287 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2288 new_xpos = old_xpos + dx;
2290 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2291 new_ypos = old_ypos + dy;
2293 SetTileCursorTargetXY(new_xpos, new_ypos);
2297 static int HandleJoystickForAllPlayers(void)
2301 boolean no_joysticks_configured = TRUE;
2302 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2303 static byte joy_action_last[MAX_PLAYERS];
2305 for (i = 0; i < MAX_PLAYERS; i++)
2306 if (setup.input[i].use_joystick)
2307 no_joysticks_configured = FALSE;
2309 // if no joysticks configured, map connected joysticks to players
2310 if (no_joysticks_configured)
2311 use_as_joystick_nr = TRUE;
2313 for (i = 0; i < MAX_PLAYERS; i++)
2315 byte joy_action = 0;
2317 joy_action = JoystickExt(i, use_as_joystick_nr);
2318 result |= joy_action;
2320 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2321 joy_action != joy_action_last[i])
2322 stored_player[i].action = joy_action;
2324 joy_action_last[i] = joy_action;
2330 void HandleJoystick(void)
2332 static unsigned int joytest_delay = 0;
2333 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2334 static int joytest_last = 0;
2335 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2336 int delay_value = GADGET_FRAME_DELAY;
2337 int joystick = HandleJoystickForAllPlayers();
2338 int keyboard = key_joystick_mapping;
2339 int joy = (joystick | keyboard);
2340 int joytest = joystick;
2341 int left = joy & JOY_LEFT;
2342 int right = joy & JOY_RIGHT;
2343 int up = joy & JOY_UP;
2344 int down = joy & JOY_DOWN;
2345 int button = joy & JOY_BUTTON;
2346 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2347 int dx = (left ? -1 : right ? 1 : 0);
2348 int dy = (up ? -1 : down ? 1 : 0);
2349 boolean use_delay_value_first = (joytest != joytest_last);
2351 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2353 // do not handle this button event anymore
2357 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2359 if (game_status == GAME_MODE_PLAYING)
2361 // when playing MM style levels, also use delay for keyboard events
2362 joytest |= keyboard;
2364 // only use first delay value for new events, but not for changed events
2365 use_delay_value_first = (!joytest != !joytest_last);
2367 // only use delay after the initial keyboard event
2371 // for any joystick or keyboard event, enable playfield tile cursor
2372 if (dx || dy || button)
2373 SetTileCursorEnabled(TRUE);
2376 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2378 // delay joystick/keyboard actions if axes/keys continually pressed
2379 newbutton = dx = dy = 0;
2383 // first start with longer delay, then continue with shorter delay
2384 joytest_delay_value =
2385 (use_delay_value_first ? delay_value_first : delay_value);
2388 joytest_last = joytest;
2390 switch (game_status)
2392 case GAME_MODE_TITLE:
2393 case GAME_MODE_MAIN:
2394 case GAME_MODE_LEVELS:
2395 case GAME_MODE_LEVELNR:
2396 case GAME_MODE_SETUP:
2397 case GAME_MODE_INFO:
2398 case GAME_MODE_SCORES:
2400 if (anyTextGadgetActive())
2403 if (game_status == GAME_MODE_TITLE)
2404 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2405 else if (game_status == GAME_MODE_MAIN)
2406 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2407 else if (game_status == GAME_MODE_LEVELS)
2408 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2409 else if (game_status == GAME_MODE_LEVELNR)
2410 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2411 else if (game_status == GAME_MODE_SETUP)
2412 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2413 else if (game_status == GAME_MODE_INFO)
2414 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2415 else if (game_status == GAME_MODE_SCORES)
2416 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2421 case GAME_MODE_PLAYING:
2423 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2424 if (tape.playing || keyboard)
2425 newbutton = ((joy & JOY_BUTTON) != 0);
2428 if (newbutton && AllPlayersGone)
2435 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2437 if (joystick & JOY_ACTION)
2438 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2440 else if (tape.recording && tape.pausing && !tape.use_mouse)
2442 if (joystick & JOY_ACTION)
2443 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2446 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2447 HandleTileCursor(dx, dy, button);
2456 void HandleSpecialGameControllerButtons(Event *event)
2458 #if defined(TARGET_SDL2)
2462 switch (event->type)
2464 case SDL_CONTROLLERBUTTONDOWN:
2465 key_status = KEY_PRESSED;
2468 case SDL_CONTROLLERBUTTONUP:
2469 key_status = KEY_RELEASED;
2476 switch (event->cbutton.button)
2478 case SDL_CONTROLLER_BUTTON_START:
2482 case SDL_CONTROLLER_BUTTON_BACK:
2490 HandleKey(key, key_status);
2494 void HandleSpecialGameControllerKeys(Key key, int key_status)
2496 #if defined(TARGET_SDL2)
2497 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2498 int button = SDL_CONTROLLER_BUTTON_INVALID;
2500 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2501 if (key == KSYM_Rewind)
2502 button = SDL_CONTROLLER_BUTTON_A;
2503 else if (key == KSYM_FastForward || key == KSYM_Menu)
2504 button = SDL_CONTROLLER_BUTTON_B;
2506 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2510 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2511 SDL_CONTROLLERBUTTONUP);
2513 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2514 event.cbutton.button = button;
2515 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2518 HandleJoystickEvent(&event);
2524 boolean DoKeysymAction(int keysym)
2528 Key key = (Key)(-keysym);
2530 HandleKey(key, KEY_PRESSED);
2531 HandleKey(key, KEY_RELEASED);