Compare commits

...
Author SHA1 Message Date
Mike Klaas 1bbbc98065 Harden input reset on activation changes 2026-04-24 23:12:21 -07:00
Mike Klaas b0b08ed14e Avoid recursive sfall tap_key hooks 2026-04-24 23:05:40 -07:00
Mike Klaas 5b8281922d Fix worldmap 100% cpu lockup
On Mac, when on worldmap, cmd-tab out of a windowed game then back locks the game 100% of the time for me.  This PR does a more thorough reset of the input to prevent it from getting out of sync
2026-04-23 22:32:28 -07:00
7 changed files with 107 additions and 11 deletions
+46 -8
View File
@@ -48,6 +48,8 @@ static void buildNormalizedQwertyKeys();
static void _GNW95_process_key(KeyboardData* data);
static int inputGetHookMouseButton(int sdlButton);
static void inputHandleMouseClickHook(int sdlButton, bool pressed);
static void inputResetInternalEventQueue();
static void inputHandleProgramActivationChange(bool isActive);
// 0x51E23C
static int gKeyboardKeyRepeatRate = 80;
@@ -135,6 +137,39 @@ static void inputHandleMouseClickHook(int sdlButton, bool pressed)
ScriptHookCall(HOOK_MOUSECLICK, 0, { pressed ? 1 : 0, hookButton }).call();
}
static void inputHandleProgramActivationChange(bool isActive)
{
if (gProgramIsActive == isActive) {
return;
}
gProgramIsActive = isActive;
// Cmd-tab on macOS can leave modifier/key repeat state and mouse mode out
// of sync with SDL's actual app activation state. Drop queued input on
// both transitions and reapply the desired mouse mode on return.
keyboardReset();
mouseReset();
windowResetButtonState();
inputResetInternalEventQueue();
if (isActive) {
mouseDeviceInitMode();
windowRefreshAll(&_scr_size);
audioEngineResume();
} else {
audioEnginePause();
}
}
static void inputResetInternalEventQueue()
{
gInputEventQueueReadIndex = -1;
gInputEventQueueWriteIndex = 0;
_input_mx = -1;
_input_my = -1;
}
// 0x4C8A70
int inputInit()
{
@@ -300,8 +335,7 @@ static int dequeueInputEvent()
// 0x4C8D04
void inputEventQueueReset()
{
gInputEventQueueReadIndex = -1;
gInputEventQueueWriteIndex = 0;
inputResetInternalEventQueue();
SDL_Event e;
while (SDL_PollEvent(&e)) { } // Clear all input events
}
@@ -1016,8 +1050,9 @@ void _GNW95_process_message()
if (!keyboardIsDisabled()) {
keyboardData.key = e.key.keysym.scancode;
keyboardData.down = (e.key.state & SDL_PRESSED) != 0;
bool syntheticSfallKey = sfall_kb_consume_synthetic_key_event(keyboardData.key, keyboardData.down);
if (!e.key.repeat) {
if (!e.key.repeat && !syntheticSfallKey) {
int keyOverride = sfall_kb_handle_key_pressed(keyboardData.key, keyboardData.down);
if (keyOverride != SDL_SCANCODE_UNKNOWN) {
keyboardData.key = keyOverride;
@@ -1034,14 +1069,17 @@ void _GNW95_process_message()
case SDL_WINDOWEVENT_SIZE_CHANGED:
handleWindowSizeChanged();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
gProgramIsActive = true;
case SDL_WINDOWEVENT_SHOWN:
case SDL_WINDOWEVENT_RESTORED:
windowRefreshAll(&_scr_size);
audioEngineResume();
break;
case SDL_WINDOWEVENT_FOCUS_GAINED:
inputHandleProgramActivationChange(true);
break;
case SDL_WINDOWEVENT_HIDDEN:
case SDL_WINDOWEVENT_MINIMIZED:
case SDL_WINDOWEVENT_FOCUS_LOST:
gProgramIsActive = false;
audioEnginePause();
inputHandleProgramActivationChange(false);
break;
}
break;
+9
View File
@@ -656,6 +656,15 @@ int mouseGetEvent()
return gMouseEvent;
}
void mouseReset()
{
gMouseEvent = 0;
_raw_buttons = 0;
last_buttons = 0;
gMouseWheelX = 0;
gMouseWheelY = 0;
}
// 0x4CAAA8
bool cursorIsHidden()
{
+1
View File
@@ -47,6 +47,7 @@ void mouseGetRect(Rect* rect);
void mouseGetPosition(int* out_x, int* out_y);
void _mouse_set_position(int x, int y);
int mouseGetEvent();
void mouseReset();
bool cursorIsHidden();
void _mouse_get_raw_state(int* out_x, int* out_y, int* out_buttons);
void mouseSetSensitivity(double value);
+34 -3
View File
@@ -4,7 +4,9 @@
#include "game.h"
#include "sfall_script_hooks.h"
#include "svga.h"
#include <deque>
#include <unordered_map>
namespace fallout {
@@ -272,6 +274,7 @@ static constexpr SDL_Scancode kDiks[DIK_MAP_COUNT] = {
};
std::unordered_map<SDL_Scancode, int> kScanCodeToDik;
std::deque<std::pair<SDL_Scancode, bool>> syntheticKeyEvents;
/// Translates Sfall key code (DIK or VK constant) to SDL scancode.
static SDL_Scancode get_scancode_from_key(int key)
@@ -319,13 +322,41 @@ void sfall_kb_press_key(int key)
}
SDL_Event event;
event.key.keysym.scancode = scancode;
SDL_zero(event);
event.type = SDL_KEYDOWN;
SDL_PushEvent(&event);
event.key.timestamp = SDL_GetTicks();
event.key.windowID = gSdlWindow != nullptr ? SDL_GetWindowID(gSdlWindow) : 0;
event.key.state = SDL_PRESSED;
event.key.repeat = 0;
event.key.keysym.scancode = scancode;
event.key.keysym.sym = SDL_GetKeyFromScancode(scancode);
event.key.keysym.mod = SDL_GetModState();
if (SDL_PushEvent(&event) == 1) {
syntheticKeyEvents.emplace_back(scancode, true);
}
event.type = SDL_KEYUP;
SDL_PushEvent(&event);
event.key.timestamp = SDL_GetTicks();
event.key.state = SDL_RELEASED;
if (SDL_PushEvent(&event) == 1) {
syntheticKeyEvents.emplace_back(scancode, false);
}
}
bool sfall_kb_consume_synthetic_key_event(int sdlScanCode, bool pressed)
{
if (syntheticKeyEvents.empty()) {
return false;
}
const auto& [expectedScanCode, expectedPressed] = syntheticKeyEvents.front();
if (expectedScanCode != static_cast<SDL_Scancode>(sdlScanCode) || expectedPressed != pressed) {
return false;
}
syntheticKeyEvents.pop_front();
return true;
}
int sfall_kb_handle_key_pressed(int sdlScanCode, bool pressed)
+3
View File
@@ -9,6 +9,9 @@ bool sfall_kb_is_key_pressed(int key);
/// Simulates pressing `key`.
void sfall_kb_press_key(int key);
/// Returns `true` when the next matching SDL key event was injected by `tap_key`.
bool sfall_kb_consume_synthetic_key_event(int sdlScanCode, bool pressed);
int sfall_kb_handle_key_pressed(int sdlScanCode, bool pressed);
} // namespace fallout
+13
View File
@@ -1238,6 +1238,19 @@ int _win_check_all_buttons()
return keyCode;
}
void windowResetButtonState()
{
if (!gWindowSystemInitialized) {
return;
}
for (int index = 0; index < gWindowsLength; index++) {
Window* window = gWindows[index];
window->hoveredButton = nullptr;
window->clickedButton = nullptr;
}
}
// 0x4D79DC
Button* buttonGetButton(int btn, Window** windowPtr)
{
+1
View File
@@ -195,6 +195,7 @@ int windowGetHeight(int win);
int windowGetRect(int win, Rect* rect);
int _win_check_all_buttons();
int _GNW_check_menu_bars(int input);
void windowResetButtonState();
void programWindowSetTitle(const char* title);
bool showMesageBox(const char* str);
int buttonCreate(int win, int x, int y, int width, int height, int mouseEnterEventCode, int mouseExitEventCode, int mouseDownEventCode, int mouseUpEventCode, unsigned char* up, unsigned char* dn, unsigned char* hover, int flags);