From 1a5bdc14aa897071ce9db612491cfd7df1f54f4b Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Fri, 21 Aug 2026 04:23:42 +1000 Subject: [PATCH] [client] input: fix focus capture startup Capture-on-focus could consume the initial focus event before an input provider became available. The focused state then suppressed any later attempt to establish capture. Treat explicit capture as local window state, refresh keyboard ownership on focus and input-policy changes, and preserve pre-ungrab availability for key release. Fall back to the window center when guest cursor geometry is not ready. --- client/src/app.c | 22 ++++++++++++++-------- client/src/core.c | 32 ++++++++++++++++++++------------ client/src/core.h | 1 + client/src/keybind.c | 1 + 4 files changed, 36 insertions(+), 20 deletions(-) diff --git a/client/src/app.c b/client/src/app.c index 23ad6d6d..4c20610d 100644 --- a/client/src/app.c +++ b/client/src/app.c @@ -269,9 +269,9 @@ void app_handleFocusEvent(bool focused) if (g_state.focused == focused) return; - g_state.focused = focused; - g_cursor.motionValid = false; - const bool inputEnabled = core_inputEnabled(); + g_state.focused = focused; + g_cursor.motionValid = false; + const bool inputWasEnabled = core_inputEnabled(); // release any imgui buttons/keys if we lost focus if (!focused && app_isOverlayMode()) @@ -285,6 +285,15 @@ void app_handleFocusEvent(bool focused) core_setGrabQuiet(false); core_setCursorInView(false); } + else + { + if (g_params.captureOnFocus && !g_state.ignoreInput) + core_setGrab(true); + core_updateKeyboardGrab(); + } + + const bool inputEnabled = focused ? + core_inputEnabled() : inputWasEnabled; if (!inputEnabled) { @@ -304,10 +313,6 @@ void app_handleFocusEvent(bool focused) if (g_params.minimizeOnFocusLoss) g_state.ds->minimize(); } - else - if (g_params.captureOnFocus) - core_setGrab(true); - g_cursor.realign = true; g_state.ds->realignPointer(); } @@ -518,7 +523,8 @@ void app_handleKeyReleaseInternal(int sc) { if (g_state.escapeAction == -1) { - if (!g_state.escapeHelp && lgInput_available() && + if (!g_state.escapeHelp && + (g_cursor.grab || lgInput_available()) && !app_isOverlayMode()) core_setGrab(!g_cursor.grab); } diff --git a/client/src/core.c b/client/src/core.c index 4b411ffe..2537a862 100644 --- a/client/src/core.c +++ b/client/src/core.c @@ -137,14 +137,18 @@ bool core_inputEnabled(void) ((g_cursor.grab && g_params.captureInputOnly) || !g_params.captureInputOnly); } -static void updateKeyboardGrab(void) +void core_updateKeyboardGrab(void) { - const bool capture = g_cursor.grab && g_params.grabKeyboard; - const bool view = g_cursor.inView && g_params.grabKeyboardOnFocus; - const bool automatic = !g_cursor.grab && + const bool inputEnabled = core_inputEnabled(); + /* Explicit capture is local window state and must survive an input provider + * becoming unavailable or changing during startup. */ + const bool capture = g_cursor.grab && g_params.grabKeyboard && + !g_state.ignoreInput; + const bool view = g_cursor.inView && g_params.grabKeyboardOnFocus; + const bool automatic = !g_cursor.grab && g_cursor.autoCaptureActive && !g_params.captureInputOnly; - const bool active = g_state.focused && core_inputEnabled() && - !app_isOverlayMode() && (capture || view || automatic); + const bool active = g_state.focused && !app_isOverlayMode() && + (capture || (inputEnabled && (view || automatic))); if (active) g_state.ds->grabKeyboard(); @@ -162,7 +166,7 @@ static void setAutoCapture(bool active) MTRACE("auto capture active=%d old=%d", active, g_cursor.autoCaptureActive); g_cursor.autoCaptureActive = active; - updateKeyboardGrab(); + core_updateKeyboardGrab(); } static void applyView(bool active, bool force) @@ -175,7 +179,7 @@ static void applyView(bool active, bool force) if (!force && g_cursor.inView == active) { - updateKeyboardGrab(); + core_updateKeyboardGrab(); return; } @@ -204,7 +208,7 @@ static void applyView(bool active, bool force) g_state.ds->setPointer(LG_POINTER_SQUARE); } - updateKeyboardGrab(); + core_updateKeyboardGrab(); g_cursor.warpState = WARP_STATE_ON; } @@ -323,7 +327,7 @@ void core_setGrabQuiet(bool enable) g_state.ignoreInput = false; g_cursor.grab = true; g_cursor.autoCaptureActive = false; - updateKeyboardGrab(); + core_updateKeyboardGrab(); core_setCursorInView(true); /* ensure the local mouse is inside the window before we capture, this fixes @@ -331,7 +335,11 @@ void core_setGrabQuiet(bool enable) * was focused without the cursor being in window already */ if (warpSupport != LG_DS_WARP_NONE) { - struct DoublePoint local; + struct DoublePoint local = + { + .x = g_state.windowCX, + .y = g_state.windowCY, + }; const bool valid = util_guestCurToLocal(&local); MTRACE("grab align valid=%d", valid); core_warpPointer(local.x, local.y, true); @@ -345,7 +353,7 @@ void core_setGrabQuiet(bool enable) g_cursor.grab = false; g_cursor.autoCaptureActive = g_params.autoCapture && g_cursor.inView && !g_params.captureInputOnly; - updateKeyboardGrab(); + core_updateKeyboardGrab(); if (warpSupport == LG_DS_WARP_NONE) core_handleMouseAbsolute(); diff --git a/client/src/core.h b/client/src/core.h index 49e3e653..e33f4d79 100644 --- a/client/src/core.h +++ b/client/src/core.h @@ -24,6 +24,7 @@ #include bool core_inputEnabled(void); +void core_updateKeyboardGrab(void); void core_invalidatePointer(bool detectInView); void core_setCursorInView(bool enable); void core_handleGrabEvent(bool active); diff --git a/client/src/keybind.c b/client/src/keybind.c index 6da01f1b..96bca141 100644 --- a/client/src/keybind.c +++ b/client/src/keybind.c @@ -63,6 +63,7 @@ static void bind_input(int sc, void * opaque) core_setCursorInView(false); else g_state.ds->realignPointer(); + core_updateKeyboardGrab(); app_alert( LG_ALERT_INFO,