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"))
1782 if (GetKeyModState() & (KMOD_Control | KMOD_Meta))
1784 if (letter == 'x') // copy brush to clipboard (small size)
1786 CopyBrushToClipboard_Small();
1788 else if (letter == 'c') // copy brush to clipboard (normal size)
1790 CopyBrushToClipboard();
1792 else if (letter == 'v') // paste brush from Clipboard
1794 CopyClipboardToBrush();
1799 // special key shortcuts for all game modes
1800 if (is_string_suffix(cheat_input, ":dump-event-actions") ||
1801 is_string_suffix(cheat_input, ":dea") ||
1802 is_string_suffix(cheat_input, ":DEA"))
1804 DumpGadgetIdentifiers();
1805 DumpScreenIdentifiers();
1809 void HandleKeysDebug(Key key)
1814 if (game_status == GAME_MODE_PLAYING || !setup.debug.frame_delay_game_only)
1816 boolean mod_key_pressed = ((GetKeyModState() & KMOD_Valid) != KMOD_None);
1818 for (i = 0; i < NUM_DEBUG_FRAME_DELAY_KEYS; i++)
1820 if (key == setup.debug.frame_delay_key[i] &&
1821 (mod_key_pressed == setup.debug.frame_delay_use_mod_key))
1823 GameFrameDelay = (GameFrameDelay != setup.debug.frame_delay[i] ?
1824 setup.debug.frame_delay[i] : setup.game_frame_delay);
1826 if (!setup.debug.frame_delay_game_only)
1827 MenuFrameDelay = GameFrameDelay;
1829 SetVideoFrameDelay(GameFrameDelay);
1831 if (GameFrameDelay > ONE_SECOND_DELAY)
1832 Error(ERR_DEBUG, "frame delay == %d ms", GameFrameDelay);
1833 else if (GameFrameDelay != 0)
1834 Error(ERR_DEBUG, "frame delay == %d ms (max. %d fps / %d %%)",
1835 GameFrameDelay, ONE_SECOND_DELAY / GameFrameDelay,
1836 GAME_FRAME_DELAY * 100 / GameFrameDelay);
1838 Error(ERR_DEBUG, "frame delay == 0 ms (maximum speed)");
1845 if (game_status == GAME_MODE_PLAYING)
1849 options.debug = !options.debug;
1851 Error(ERR_DEBUG, "debug mode %s",
1852 (options.debug ? "enabled" : "disabled"));
1854 else if (key == KSYM_v)
1856 Error(ERR_DEBUG, "currently using game engine version %d",
1857 game.engine_version);
1863 void HandleKey(Key key, int key_status)
1865 boolean anyTextGadgetActiveOrJustFinished = anyTextGadgetActive();
1866 static boolean ignore_repeated_key = FALSE;
1867 static struct SetupKeyboardInfo ski;
1868 static struct SetupShortcutInfo ssi;
1877 { &ski.left, &ssi.snap_left, DEFAULT_KEY_LEFT, JOY_LEFT },
1878 { &ski.right, &ssi.snap_right, DEFAULT_KEY_RIGHT, JOY_RIGHT },
1879 { &ski.up, &ssi.snap_up, DEFAULT_KEY_UP, JOY_UP },
1880 { &ski.down, &ssi.snap_down, DEFAULT_KEY_DOWN, JOY_DOWN },
1881 { &ski.snap, NULL, DEFAULT_KEY_SNAP, JOY_BUTTON_SNAP },
1882 { &ski.drop, NULL, DEFAULT_KEY_DROP, JOY_BUTTON_DROP }
1887 #if defined(TARGET_SDL2)
1888 // map special keys (media keys / remote control buttons) to default keys
1889 if (key == KSYM_PlayPause)
1891 else if (key == KSYM_Select)
1895 HandleSpecialGameControllerKeys(key, key_status);
1897 if (game_status == GAME_MODE_PLAYING)
1899 // only needed for single-step tape recording mode
1900 static boolean has_snapped[MAX_PLAYERS] = { FALSE, FALSE, FALSE, FALSE };
1903 for (pnr = 0; pnr < MAX_PLAYERS; pnr++)
1905 byte key_action = 0;
1907 if (setup.input[pnr].use_joystick)
1910 ski = setup.input[pnr].key;
1912 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1913 if (key == *key_info[i].key_custom)
1914 key_action |= key_info[i].action;
1916 // use combined snap+direction keys for the first player only
1919 ssi = setup.shortcut;
1921 for (i = 0; i < NUM_DIRECTIONS; i++)
1922 if (key == *key_info[i].key_snap)
1923 key_action |= key_info[i].action | JOY_BUTTON_SNAP;
1926 if (key_status == KEY_PRESSED)
1927 stored_player[pnr].action |= key_action;
1929 stored_player[pnr].action &= ~key_action;
1931 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
1933 if (key_status == KEY_PRESSED && key_action & KEY_MOTION)
1935 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1937 // if snap key already pressed, keep pause mode when releasing
1938 if (stored_player[pnr].action & KEY_BUTTON_SNAP)
1939 has_snapped[pnr] = TRUE;
1941 else if (key_status == KEY_PRESSED && key_action & KEY_BUTTON_DROP)
1943 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1945 if (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
1946 getRedDiskReleaseFlag_SP() == 0)
1948 // add a single inactive frame before dropping starts
1949 stored_player[pnr].action &= ~KEY_BUTTON_DROP;
1950 stored_player[pnr].force_dropping = TRUE;
1953 else if (key_status == KEY_RELEASED && key_action & KEY_BUTTON_SNAP)
1955 // if snap key was pressed without direction, leave pause mode
1956 if (!has_snapped[pnr])
1957 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
1959 has_snapped[pnr] = FALSE;
1962 else if (tape.recording && tape.pausing && !tape.use_mouse)
1964 // prevent key release events from un-pausing a paused game
1965 if (key_status == KEY_PRESSED && key_action & KEY_ACTION)
1966 TapeTogglePause(TAPE_TOGGLE_MANUAL);
1969 // for MM style levels, handle in-game keyboard input in HandleJoystick()
1970 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
1976 for (i = 0; i < NUM_PLAYER_ACTIONS; i++)
1977 if (key == key_info[i].key_default)
1978 joy |= key_info[i].action;
1983 if (key_status == KEY_PRESSED)
1984 key_joystick_mapping |= joy;
1986 key_joystick_mapping &= ~joy;
1991 if (game_status != GAME_MODE_PLAYING)
1992 key_joystick_mapping = 0;
1994 if (key_status == KEY_RELEASED)
1996 // reset flag to ignore repeated "key pressed" events after key release
1997 ignore_repeated_key = FALSE;
2002 if ((key == KSYM_F11 ||
2003 ((key == KSYM_Return ||
2004 key == KSYM_KP_Enter) && (GetKeyModState() & KMOD_Alt))) &&
2005 video.fullscreen_available &&
2006 !ignore_repeated_key)
2008 setup.fullscreen = !setup.fullscreen;
2010 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2012 if (game_status == GAME_MODE_SETUP)
2013 RedrawSetupScreenAfterFullscreenToggle();
2015 // set flag to ignore repeated "key pressed" events
2016 ignore_repeated_key = TRUE;
2021 if ((key == KSYM_0 || key == KSYM_KP_0 ||
2022 key == KSYM_minus || key == KSYM_KP_Subtract ||
2023 key == KSYM_plus || key == KSYM_KP_Add ||
2024 key == KSYM_equal) && // ("Shift-=" is "+" on US keyboards)
2025 (GetKeyModState() & (KMOD_Control | KMOD_Meta)) &&
2026 video.window_scaling_available &&
2027 !video.fullscreen_enabled)
2029 if (key == KSYM_0 || key == KSYM_KP_0)
2030 setup.window_scaling_percent = STD_WINDOW_SCALING_PERCENT;
2031 else if (key == KSYM_minus || key == KSYM_KP_Subtract)
2032 setup.window_scaling_percent -= STEP_WINDOW_SCALING_PERCENT;
2034 setup.window_scaling_percent += STEP_WINDOW_SCALING_PERCENT;
2036 if (setup.window_scaling_percent < MIN_WINDOW_SCALING_PERCENT)
2037 setup.window_scaling_percent = MIN_WINDOW_SCALING_PERCENT;
2038 else if (setup.window_scaling_percent > MAX_WINDOW_SCALING_PERCENT)
2039 setup.window_scaling_percent = MAX_WINDOW_SCALING_PERCENT;
2041 ToggleFullscreenOrChangeWindowScalingIfNeeded();
2043 if (game_status == GAME_MODE_SETUP)
2044 RedrawSetupScreenAfterFullscreenToggle();
2049 if (HandleGlobalAnimClicks(-1, -1, (key == KSYM_space ||
2050 key == KSYM_Return ||
2051 key == KSYM_Escape)))
2053 // do not handle this key event anymore
2054 if (key != KSYM_Escape) // always allow ESC key to be handled
2058 if (game_status == GAME_MODE_PLAYING && game.all_players_gone &&
2059 (key == KSYM_Return || key == setup.shortcut.toggle_pause))
2066 if (game_status == GAME_MODE_MAIN &&
2067 (key == setup.shortcut.toggle_pause || key == KSYM_space))
2069 StartGameActions(network.enabled, setup.autorecord, level.random_seed);
2074 if (game_status == GAME_MODE_MAIN || game_status == GAME_MODE_PLAYING)
2076 if (key == setup.shortcut.save_game)
2078 else if (key == setup.shortcut.load_game)
2080 else if (key == setup.shortcut.toggle_pause)
2081 TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
2083 HandleTapeButtonKeys(key);
2084 HandleSoundButtonKeys(key);
2087 if (game_status == GAME_MODE_PLAYING && !network_playing)
2089 int centered_player_nr_next = -999;
2091 if (key == setup.shortcut.focus_player_all)
2092 centered_player_nr_next = -1;
2094 for (i = 0; i < MAX_PLAYERS; i++)
2095 if (key == setup.shortcut.focus_player[i])
2096 centered_player_nr_next = i;
2098 if (centered_player_nr_next != -999)
2100 game.centered_player_nr_next = centered_player_nr_next;
2101 game.set_centered_player = TRUE;
2105 tape.centered_player_nr_next = game.centered_player_nr_next;
2106 tape.set_centered_player = TRUE;
2111 HandleKeysSpecial(key);
2113 if (HandleGadgetsKeyInput(key))
2115 if (key != KSYM_Escape) // always allow ESC key to be handled
2116 key = KSYM_UNDEFINED;
2119 switch (game_status)
2121 case GAME_MODE_PSEUDO_TYPENAME:
2122 HandleTypeName(0, key);
2125 case GAME_MODE_TITLE:
2126 case GAME_MODE_MAIN:
2127 case GAME_MODE_LEVELS:
2128 case GAME_MODE_LEVELNR:
2129 case GAME_MODE_SETUP:
2130 case GAME_MODE_INFO:
2131 case GAME_MODE_SCORES:
2133 if (anyTextGadgetActiveOrJustFinished && key != KSYM_Escape)
2140 if (game_status == GAME_MODE_TITLE)
2141 HandleTitleScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2142 else if (game_status == GAME_MODE_MAIN)
2143 HandleMainMenu(0, 0, 0, 0, MB_MENU_CHOICE);
2144 else if (game_status == GAME_MODE_LEVELS)
2145 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_CHOICE);
2146 else if (game_status == GAME_MODE_LEVELNR)
2147 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_CHOICE);
2148 else if (game_status == GAME_MODE_SETUP)
2149 HandleSetupScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2150 else if (game_status == GAME_MODE_INFO)
2151 HandleInfoScreen(0, 0, 0, 0, MB_MENU_CHOICE);
2152 else if (game_status == GAME_MODE_SCORES)
2153 HandleHallOfFame(0, 0, 0, 0, MB_MENU_CHOICE);
2157 if (game_status != GAME_MODE_MAIN)
2158 FadeSkipNextFadeIn();
2160 if (game_status == GAME_MODE_TITLE)
2161 HandleTitleScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2162 else if (game_status == GAME_MODE_LEVELS)
2163 HandleChooseLevelSet(0, 0, 0, 0, MB_MENU_LEAVE);
2164 else if (game_status == GAME_MODE_LEVELNR)
2165 HandleChooseLevelNr(0, 0, 0, 0, MB_MENU_LEAVE);
2166 else if (game_status == GAME_MODE_SETUP)
2167 HandleSetupScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2168 else if (game_status == GAME_MODE_INFO)
2169 HandleInfoScreen(0, 0, 0, 0, MB_MENU_LEAVE);
2170 else if (game_status == GAME_MODE_SCORES)
2171 HandleHallOfFame(0, 0, 0, 0, MB_MENU_LEAVE);
2175 if (game_status == GAME_MODE_LEVELS)
2176 HandleChooseLevelSet(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2177 else if (game_status == GAME_MODE_LEVELNR)
2178 HandleChooseLevelNr(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2179 else if (game_status == GAME_MODE_SETUP)
2180 HandleSetupScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2181 else if (game_status == GAME_MODE_INFO)
2182 HandleInfoScreen(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2183 else if (game_status == GAME_MODE_SCORES)
2184 HandleHallOfFame(0, 0, 0, -1 * SCROLL_PAGE, MB_MENU_MARK);
2187 case KSYM_Page_Down:
2188 if (game_status == GAME_MODE_LEVELS)
2189 HandleChooseLevelSet(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2190 else if (game_status == GAME_MODE_LEVELNR)
2191 HandleChooseLevelNr(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2192 else if (game_status == GAME_MODE_SETUP)
2193 HandleSetupScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2194 else if (game_status == GAME_MODE_INFO)
2195 HandleInfoScreen(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2196 else if (game_status == GAME_MODE_SCORES)
2197 HandleHallOfFame(0, 0, 0, +1 * SCROLL_PAGE, MB_MENU_MARK);
2205 case GAME_MODE_EDITOR:
2206 if (!anyTextGadgetActiveOrJustFinished || key == KSYM_Escape)
2207 HandleLevelEditorKeyInput(key);
2210 case GAME_MODE_PLAYING:
2215 RequestQuitGame(setup.ask_on_escape);
2225 if (key == KSYM_Escape)
2227 SetGameStatus(GAME_MODE_MAIN);
2235 HandleKeysDebug(key);
2238 void HandleNoEvent(void)
2240 HandleMouseCursor();
2242 switch (game_status)
2244 case GAME_MODE_PLAYING:
2245 HandleButtonOrFinger(-1, -1, -1);
2250 void HandleEventActions(void)
2252 // if (button_status && game_status != GAME_MODE_PLAYING)
2253 if (button_status && (game_status != GAME_MODE_PLAYING ||
2255 level.game_engine_type == GAME_ENGINE_TYPE_MM))
2257 HandleButton(0, 0, button_status, -button_status);
2264 if (network.enabled)
2267 switch (game_status)
2269 case GAME_MODE_MAIN:
2270 DrawPreviewLevelAnimation();
2273 case GAME_MODE_EDITOR:
2274 HandleLevelEditorIdle();
2282 static void HandleTileCursor(int dx, int dy, int button)
2285 ClearPlayerMouseAction();
2292 SetPlayerMouseAction(tile_cursor.x, tile_cursor.y,
2293 (dx < 0 ? MB_LEFTBUTTON :
2294 dx > 0 ? MB_RIGHTBUTTON : MB_RELEASED));
2296 else if (!tile_cursor.moving)
2298 int old_xpos = tile_cursor.xpos;
2299 int old_ypos = tile_cursor.ypos;
2300 int new_xpos = old_xpos;
2301 int new_ypos = old_ypos;
2303 if (IN_LEV_FIELD(old_xpos + dx, old_ypos))
2304 new_xpos = old_xpos + dx;
2306 if (IN_LEV_FIELD(old_xpos, old_ypos + dy))
2307 new_ypos = old_ypos + dy;
2309 SetTileCursorTargetXY(new_xpos, new_ypos);
2313 static int HandleJoystickForAllPlayers(void)
2317 boolean no_joysticks_configured = TRUE;
2318 boolean use_as_joystick_nr = (game_status != GAME_MODE_PLAYING);
2319 static byte joy_action_last[MAX_PLAYERS];
2321 for (i = 0; i < MAX_PLAYERS; i++)
2322 if (setup.input[i].use_joystick)
2323 no_joysticks_configured = FALSE;
2325 // if no joysticks configured, map connected joysticks to players
2326 if (no_joysticks_configured)
2327 use_as_joystick_nr = TRUE;
2329 for (i = 0; i < MAX_PLAYERS; i++)
2331 byte joy_action = 0;
2333 joy_action = JoystickExt(i, use_as_joystick_nr);
2334 result |= joy_action;
2336 if ((setup.input[i].use_joystick || no_joysticks_configured) &&
2337 joy_action != joy_action_last[i])
2338 stored_player[i].action = joy_action;
2340 joy_action_last[i] = joy_action;
2346 void HandleJoystick(void)
2348 static unsigned int joytest_delay = 0;
2349 static unsigned int joytest_delay_value = GADGET_FRAME_DELAY;
2350 static int joytest_last = 0;
2351 int delay_value_first = GADGET_FRAME_DELAY_FIRST;
2352 int delay_value = GADGET_FRAME_DELAY;
2353 int joystick = HandleJoystickForAllPlayers();
2354 int keyboard = key_joystick_mapping;
2355 int joy = (joystick | keyboard);
2356 int joytest = joystick;
2357 int left = joy & JOY_LEFT;
2358 int right = joy & JOY_RIGHT;
2359 int up = joy & JOY_UP;
2360 int down = joy & JOY_DOWN;
2361 int button = joy & JOY_BUTTON;
2362 int newbutton = (AnyJoystickButton() == JOY_BUTTON_NEW_PRESSED);
2363 int dx = (left ? -1 : right ? 1 : 0);
2364 int dy = (up ? -1 : down ? 1 : 0);
2365 boolean use_delay_value_first = (joytest != joytest_last);
2367 if (HandleGlobalAnimClicks(-1, -1, newbutton))
2369 // do not handle this button event anymore
2373 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2375 if (game_status == GAME_MODE_PLAYING)
2377 // when playing MM style levels, also use delay for keyboard events
2378 joytest |= keyboard;
2380 // only use first delay value for new events, but not for changed events
2381 use_delay_value_first = (!joytest != !joytest_last);
2383 // only use delay after the initial keyboard event
2387 // for any joystick or keyboard event, enable playfield tile cursor
2388 if (dx || dy || button)
2389 SetTileCursorEnabled(TRUE);
2392 if (joytest && !button && !DelayReached(&joytest_delay, joytest_delay_value))
2394 // delay joystick/keyboard actions if axes/keys continually pressed
2395 newbutton = dx = dy = 0;
2399 // first start with longer delay, then continue with shorter delay
2400 joytest_delay_value =
2401 (use_delay_value_first ? delay_value_first : delay_value);
2404 joytest_last = joytest;
2406 switch (game_status)
2408 case GAME_MODE_TITLE:
2409 case GAME_MODE_MAIN:
2410 case GAME_MODE_LEVELS:
2411 case GAME_MODE_LEVELNR:
2412 case GAME_MODE_SETUP:
2413 case GAME_MODE_INFO:
2414 case GAME_MODE_SCORES:
2416 if (anyTextGadgetActive())
2419 if (game_status == GAME_MODE_TITLE)
2420 HandleTitleScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2421 else if (game_status == GAME_MODE_MAIN)
2422 HandleMainMenu(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2423 else if (game_status == GAME_MODE_LEVELS)
2424 HandleChooseLevelSet(0,0,dx,dy,newbutton?MB_MENU_CHOICE : MB_MENU_MARK);
2425 else if (game_status == GAME_MODE_LEVELNR)
2426 HandleChooseLevelNr(0,0,dx,dy,newbutton? MB_MENU_CHOICE : MB_MENU_MARK);
2427 else if (game_status == GAME_MODE_SETUP)
2428 HandleSetupScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2429 else if (game_status == GAME_MODE_INFO)
2430 HandleInfoScreen(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2431 else if (game_status == GAME_MODE_SCORES)
2432 HandleHallOfFame(0,0,dx,dy, newbutton ? MB_MENU_CHOICE : MB_MENU_MARK);
2437 case GAME_MODE_PLAYING:
2439 // !!! causes immediate GameEnd() when solving MM level with keyboard !!!
2440 if (tape.playing || keyboard)
2441 newbutton = ((joy & JOY_BUTTON) != 0);
2444 if (newbutton && game.all_players_gone)
2451 if (tape.single_step && tape.recording && tape.pausing && !tape.use_mouse)
2453 if (joystick & JOY_ACTION)
2454 TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
2456 else if (tape.recording && tape.pausing && !tape.use_mouse)
2458 if (joystick & JOY_ACTION)
2459 TapeTogglePause(TAPE_TOGGLE_MANUAL);
2462 if (level.game_engine_type == GAME_ENGINE_TYPE_MM)
2463 HandleTileCursor(dx, dy, button);
2472 void HandleSpecialGameControllerButtons(Event *event)
2474 #if defined(TARGET_SDL2)
2478 switch (event->type)
2480 case SDL_CONTROLLERBUTTONDOWN:
2481 key_status = KEY_PRESSED;
2484 case SDL_CONTROLLERBUTTONUP:
2485 key_status = KEY_RELEASED;
2492 switch (event->cbutton.button)
2494 case SDL_CONTROLLER_BUTTON_START:
2498 case SDL_CONTROLLER_BUTTON_BACK:
2506 HandleKey(key, key_status);
2510 void HandleSpecialGameControllerKeys(Key key, int key_status)
2512 #if defined(TARGET_SDL2)
2513 #if defined(KSYM_Rewind) && defined(KSYM_FastForward)
2514 int button = SDL_CONTROLLER_BUTTON_INVALID;
2516 // map keys to joystick buttons (special hack for Amazon Fire TV remote)
2517 if (key == KSYM_Rewind)
2518 button = SDL_CONTROLLER_BUTTON_A;
2519 else if (key == KSYM_FastForward || key == KSYM_Menu)
2520 button = SDL_CONTROLLER_BUTTON_B;
2522 if (button != SDL_CONTROLLER_BUTTON_INVALID)
2526 event.type = (key_status == KEY_PRESSED ? SDL_CONTROLLERBUTTONDOWN :
2527 SDL_CONTROLLERBUTTONUP);
2529 event.cbutton.which = 0; // first joystick (Amazon Fire TV remote)
2530 event.cbutton.button = button;
2531 event.cbutton.state = (key_status == KEY_PRESSED ? SDL_PRESSED :
2534 HandleJoystickEvent(&event);
2540 boolean DoKeysymAction(int keysym)
2544 Key key = (Key)(-keysym);
2546 HandleKey(key, KEY_PRESSED);
2547 HandleKey(key, KEY_RELEASED);