现象
Windows 上若 OS 拒绝低层键盘 hook 安装(`SetWindowsHookExW(WH_KEYBOARD_LL)` 因 UAC / 第三方安全软件 / 防御性策略而长期 starting),OpenLess 主窗口永久卡在 "正在启动" 灰屏,UI 永远不出来——用户点 OpenLess.exe 后看到一个空 shell,没法进设置改 hotkey、没法看错误。
复现
人为复现路径:
- Windows 上的反作弊/EDR 安全软件 block 第三方进程的 keyboard hook
- 或测试用 `SetWindowsHookExW` mock 永远返回 NULL
- 启动 OpenLess
- 观察:UI 卡 StartupShell 灰屏不动,无超时不弹错误
实际场景:用户报反馈"打开 OpenLess 看到一片空白不动"——根因可能就是这条。
根因
App.tsx:60 Windows 启动 gate 用无限轮询查 hotkey status:
```tsx
const pollHotkeyStatus = async () => {
while (!cancelled) {
const status = await getHotkeyStatus();
if (cancelled) return;
if (status.state !== 'starting') {
setGate('ready');
return;
}
await new Promise(resolve => window.setTimeout(resolve, 200));
}
};
```
- 只在 `state !== 'starting'` 才 break
- 后端 `hotkey_supervisor_loop` 永远 starting(hook 安装失败但持续重试)→ 前端永远 200ms 一次 ping,永不退出
- catch 分支会兜底但 try 内
getHotkeyStatus() 不抛错只回 'starting' 时,永远进不去 catch
影响
- 平台:仅 Windows(macOS / Linux 走不同 gate 逻辑)
- 频率:边缘场景(依赖第三方安全软件配置),但当下没有任何超时保护
- 用户损失:完全无法使用 OpenLess,且看不到错误信息
建议 fix
加最大轮询次数 + 超时 fallback:
```tsx
const MAX_POLL = 50; // 50 * 200ms = 10s
let attempts = 0;
while (!cancelled && attempts < MAX_POLL) {
attempts++;
const status = await getHotkeyStatus();
if (cancelled) return;
if (status.state !== 'starting') {
setGate('ready');
return;
}
await new Promise(resolve => window.setTimeout(resolve, 200));
}
// 超时强 setGate('ready'),让用户进 Permissions 页看错误
if (!cancelled) {
console.warn('[startup] hotkey gate timed out after 10s');
setGate('ready');
}
```
Permissions 页应该已经能显示 hotkey_status 的 lastError + 引导用户检查权限/安全软件。先让用户能看到这个页面。
现象
Windows 上若 OS 拒绝低层键盘 hook 安装(`SetWindowsHookExW(WH_KEYBOARD_LL)` 因 UAC / 第三方安全软件 / 防御性策略而长期 starting),OpenLess 主窗口永久卡在 "正在启动" 灰屏,UI 永远不出来——用户点 OpenLess.exe 后看到一个空 shell,没法进设置改 hotkey、没法看错误。
复现
人为复现路径:
实际场景:用户报反馈"打开 OpenLess 看到一片空白不动"——根因可能就是这条。
根因
App.tsx:60Windows 启动 gate 用无限轮询查 hotkey status:```tsx
const pollHotkeyStatus = async () => {
while (!cancelled) {
const status = await getHotkeyStatus();
if (cancelled) return;
if (status.state !== 'starting') {
setGate('ready');
return;
}
await new Promise(resolve => window.setTimeout(resolve, 200));
}
};
```
getHotkeyStatus()不抛错只回 'starting' 时,永远进不去 catch影响
建议 fix
加最大轮询次数 + 超时 fallback:
```tsx
const MAX_POLL = 50; // 50 * 200ms = 10s
let attempts = 0;
while (!cancelled && attempts < MAX_POLL) {
attempts++;
const status = await getHotkeyStatus();
if (cancelled) return;
if (status.state !== 'starting') {
setGate('ready');
return;
}
await new Promise(resolve => window.setTimeout(resolve, 200));
}
// 超时强 setGate('ready'),让用户进 Permissions 页看错误
if (!cancelled) {
console.warn('[startup] hotkey gate timed out after 10s');
setGate('ready');
}
```
Permissions页应该已经能显示 hotkey_status 的 lastError + 引导用户检查权限/安全软件。先让用户能看到这个页面。