mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 22:44:10 +00:00
fix(windows): recover orphaned keyboard focus after native VST editor closes
Users reported that while in Rig Builder, suddenly every text box in the app stops accepting input, app-wide, until they hit the Windows key. Cause: on Windows, VST editors open as top-level windows owned by the out-of-process vst-host sandbox (the WndProc crash fix). When that child-process window closes while holding keyboard focus — the user clicks its X, or a chain rebuild tears editors down — Windows leaves the Electron window looking active while the OS keyboard focus points at nothing: Chromium still gets pointer events but no keys. The Windows key "fix" works because it forces an OS foreground cycle. Fix: preload.ts installs a win32-only capture-phase pointerdown watchdog. A click landing while document.hasFocus() is false means the OS won't type into the window being clicked — it asks main via window:recoverFocus to run win.blur() + win.focus() (blur first: in the stale state Electron may believe the window is already focused, so a bare focus() can no-op). The user's first click inside the dead window self-heals it, covering every trigger path without having to enumerate them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4b1a7db70e
commit
575f26fa5b
@@ -568,6 +568,24 @@ function createWindow(port: number): void {
|
|||||||
return mainWindow.webContents.isAudioMuted();
|
return mainWindow.webContents.isAudioMuted();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keyboard-focus recovery (Windows). A native VST editor window — owned by
|
||||||
|
// the out-of-process vst-host sandbox since the WndProc crash fix — can
|
||||||
|
// close while it holds keyboard focus (user clicks its X, or a chain
|
||||||
|
// rebuild tears it down). Windows then leaves this window LOOKING active
|
||||||
|
// while the OS keyboard focus points at nothing: every text box in the app
|
||||||
|
// goes dead until an OS-level foreground cycle (users found the Windows
|
||||||
|
// key un-sticks it). The renderer detects the state — a click landing
|
||||||
|
// while document.hasFocus() is false (see preload.ts watchdog) — and asks
|
||||||
|
// us to run that same cycle programmatically. blur() first: in the stale
|
||||||
|
// state Electron may still believe the window is focused, so a bare
|
||||||
|
// focus() can no-op.
|
||||||
|
ipcMain.on('window:recoverFocus', (event) => {
|
||||||
|
const win = BrowserWindow.fromWebContents(event.sender);
|
||||||
|
if (!win || win.isDestroyed() || !win.isVisible() || win.isMinimized()) return;
|
||||||
|
win.blur();
|
||||||
|
win.focus();
|
||||||
|
});
|
||||||
|
|
||||||
const serverUrl = `http://127.0.0.1:${port}`;
|
const serverUrl = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
// Clear the Chromium HTTP cache before the first load. The server
|
// Clear the Chromium HTTP cache before the first load. The server
|
||||||
|
|||||||
@@ -637,4 +637,32 @@ if (isMainFrame) {
|
|||||||
contextBridge.exposeInMainWorld('slopsmithDesktop', feedBackDesktopApi);
|
contextBridge.exposeInMainWorld('slopsmithDesktop', feedBackDesktopApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard-focus recovery watchdog (Windows). When the sandbox vst-host's
|
||||||
|
// native editor window closes while focused, the OS keyboard focus can be
|
||||||
|
// left orphaned: this window still looks active but document.hasFocus() is
|
||||||
|
// false and NO text box in the app receives keys until a foreground cycle
|
||||||
|
// (the "hit the Windows key and typing works again" reports, always starting
|
||||||
|
// in Rig Builder — the one place native editor windows open). Pointer events
|
||||||
|
// still arrive without keyboard focus, so the user's first click inside the
|
||||||
|
// dead window is the recovery signal: a pointerdown while document.hasFocus()
|
||||||
|
// is false means the OS won't type into the window being interacted with —
|
||||||
|
// ask main to run the blur()/focus() cycle. Capture phase so no
|
||||||
|
// stopPropagation hides the click; throttled in case focus is contended.
|
||||||
|
if (isMainFrame && process.platform === 'win32') {
|
||||||
|
// preload.ts compiles without the DOM lib (see isMainFrame): reach the
|
||||||
|
// frame's window/document through globalThis with minimal local shapes.
|
||||||
|
const g = globalThis as unknown as {
|
||||||
|
addEventListener?: (type: string, cb: () => void, capture?: boolean) => void;
|
||||||
|
document?: { hasFocus?: () => boolean };
|
||||||
|
};
|
||||||
|
let lastFocusRecovery = 0;
|
||||||
|
g.addEventListener?.('pointerdown', () => {
|
||||||
|
if (g.document?.hasFocus?.()) return;
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastFocusRecovery < 1000) return;
|
||||||
|
lastFocusRecovery = now;
|
||||||
|
ipcRenderer.send('window:recoverFocus');
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
Reference in New Issue
Block a user