fix(windows): align Win10 HUD restore with interactive fallback

This commit is contained in:
webadderall
2026-04-04 17:22:37 +11:00
parent 52578a2f1f
commit 965d4efac7
2 changed files with 57 additions and 28 deletions
+29 -22
View File
@@ -8,8 +8,8 @@ import {
dialog,
ipcMain,
Menu,
nativeImage,
Notification,
nativeImage,
session,
systemPreferences,
Tray,
@@ -22,27 +22,28 @@ import {
killWindowsCaptureProcess,
registerIpcHandlers,
} from "./ipc/handlers";
import type { UpdateToastPayload } from "./updater";
import {
checkForAppUpdates,
deferUpdateReminder,
dismissUpdateToast,
downloadAvailableUpdate,
deferUpdateReminder,
getCurrentUpdateToastPayload,
getUpdaterLogPath,
getUpdateStatusSummary,
installDownloadedUpdateNow,
previewUpdateToast,
skipAvailableUpdateVersion,
setupAutoUpdates,
skipAvailableUpdateVersion,
} from "./updater";
import type { UpdateToastPayload } from "./updater";
import {
createEditorWindow,
createHudOverlayWindow,
createSourceSelectorWindow,
getUpdateToastWindow,
getHudOverlayWindow,
getUpdateToastWindow,
hideUpdateToastWindow,
isHudOverlayMousePassthroughSupported,
showUpdateToastWindow,
} from "./windows";
@@ -76,14 +77,8 @@ async function logSmokeExportGpuDiagnostics() {
}
try {
console.log(
"[smoke-export] GPU feature status",
JSON.stringify(app.getGPUFeatureStatus()),
);
console.log(
"[smoke-export] GPU info",
JSON.stringify(await app.getGPUInfo("basic")),
);
console.log("[smoke-export] GPU feature status", JSON.stringify(app.getGPUFeatureStatus()));
console.log("[smoke-export] GPU info", JSON.stringify(await app.getGPUInfo("basic")));
} catch (error) {
console.warn("[smoke-export] Failed to read GPU diagnostics:", error);
}
@@ -207,11 +202,17 @@ function focusOrCreateMainWindow() {
return;
}
// On Win32, calling show/moveTop/focus on the transparent HUD overlay
// permanently corrupts setIgnoreMouseEvents forwarding, making it
// click-through. Only focus the editor window; the HUD is alwaysOnTop
// so it doesn't need explicit focus.
if (process.platform === "win32" && !isEditorWindow(mainWindow)) {
// On Win32 with mouse passthrough enabled (Win11+), calling
// show/moveTop/focus on the transparent HUD overlay permanently corrupts
// setIgnoreMouseEvents forwarding, making it click-through. Only focus
// the editor window; the HUD is alwaysOnTop so it doesn't need explicit
// focus. On Win10 (passthrough disabled), the HUD is always interactive
// and can be safely shown/restored.
if (
process.platform === "win32" &&
!isEditorWindow(mainWindow) &&
isHudOverlayMousePassthroughSupported()
) {
return;
}
@@ -229,7 +230,7 @@ function focusOrCreateMainWindow() {
* operation that may alter focus or z-order so that hover detection keeps working.
*/
function reassertHudOverlayMouseState() {
if (process.platform !== "win32") {
if (process.platform !== "win32" || !isHudOverlayMousePassthroughSupported()) {
return;
}
@@ -448,7 +449,9 @@ function sendUpdateToastToWindows(channel: "update-toast-state", payload: unknow
return false;
}
const notificationKey = [updatePayload.phase, updatePayload.version, updatePayload.detail].join(":");
const notificationKey = [updatePayload.phase, updatePayload.version, updatePayload.detail].join(
":",
);
if (activeUpdateNotificationKey === notificationKey) {
return true;
}
@@ -723,10 +726,14 @@ app.whenReady().then(async () => {
const cameraStatus = systemPreferences.getMediaAccessStatus("camera");
const micStatus = systemPreferences.getMediaAccessStatus("microphone");
if (cameraStatus !== "granted") {
console.warn(`[permissions] Camera access is "${cameraStatus}" — webcam may not work. Check Windows Settings > Privacy > Camera.`);
console.warn(
`[permissions] Camera access is "${cameraStatus}" — webcam may not work. Check Windows Settings > Privacy > Camera.`,
);
}
if (micStatus !== "granted") {
console.warn(`[permissions] Microphone access is "${micStatus}" — mic recording may not work. Check Windows Settings > Privacy > Microphone.`);
console.warn(
`[permissions] Microphone access is "${micStatus}" — mic recording may not work. Check Windows Settings > Privacy > Microphone.`,
);
}
}
+28 -6
View File
@@ -1,5 +1,6 @@
import fs from "node:fs";
import { createRequire } from "node:module";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { app, BrowserWindow, ipcMain } from "electron";
@@ -96,8 +97,26 @@ function isHudOverlayCaptureProtectionSupported(): boolean {
return process.platform !== "linux";
}
function isHudOverlayMousePassthroughSupported(): boolean {
return process.platform !== "linux";
function getWindowsBuildNumber(): number | null {
if (process.platform !== "win32") {
return null;
}
const build = Number.parseInt(os.release().split(".")[2] ?? "", 10);
return Number.isFinite(build) ? build : null;
}
export function isHudOverlayMousePassthroughSupported(): boolean {
if (process.platform === "linux") {
return false;
}
const build = getWindowsBuildNumber();
if (build !== null && build < 22000) {
return false;
}
return true;
}
function loadHudOverlayCaptureProtectionSetting(): boolean {
@@ -138,7 +157,9 @@ function persistHudOverlayCaptureProtectionSetting(enabled: boolean): void {
function getScreen() {
if (!app.isReady()) {
throw new Error("getScreen() called before app is ready. Ensure all screen access happens after app.whenReady().");
throw new Error(
"getScreen() called before app is ready. Ensure all screen access happens after app.whenReady().",
);
}
return nodeRequire("electron").screen as typeof import("electron").screen;
}
@@ -405,12 +426,13 @@ export function createHudOverlayWindow(): BrowserWindow {
win.setIgnoreMouseEvents(true, { forward: true });
}
// On Windows 10, focus changes (e.g. showing a native notification) can break
// On Windows 11+, focus changes (e.g. showing a native notification) can break
// setIgnoreMouseEvents forwarding on a transparent always-on-top window, making
// it permanently click-through without hover detection. Re-initialise the
// pass-through-with-forwarding state whenever the window gains focus by toggling
// the flag off then back on so the native WS_EX_TRANSPARENT flag is fully reset.
if (process.platform === "win32") {
// On Windows 10 (build < 22000) passthrough is disabled entirely, so skip this.
if (process.platform === "win32" && isHudOverlayMousePassthroughSupported()) {
win.on("focus", () => {
if (!win.isDestroyed()) {
win.setIgnoreMouseEvents(false);
@@ -429,7 +451,7 @@ export function createHudOverlayWindow(): BrowserWindow {
if (!win.isDestroyed()) {
win.show();
win.moveTop();
if (process.platform === "win32") {
if (process.platform === "win32" && isHudOverlayMousePassthroughSupported()) {
win.setIgnoreMouseEvents(false);
setTimeout(() => {
if (!win.isDestroyed()) {