Repository navigation
Replies: 3 comments 1 reply
你的排查方向对:通知在渲染进程创建,而"把窗口拉回前台"只有主进程能做1. 机制层面的关键分工
⇒ 所以链路必须是 渲染侧点击 ⇒ IPC 回主进程 ⇒ 主进程 restore + show + focus。这条链路缺一环,就是你看到的"点了没反应";而"手动点任务栏图标能恢复"恰好说明窗口对象本身没坏,只是没人叫它到前台。 2. Windows 上还有第二个坑:前台锁定(foreground lock)即使主进程调用了 win.setAlwaysOnTop(true)
if (win.isMinimized()) win.restore()
win.show()
win.focus()
win.setAlwaysOnTop(false)建议把这两层分开写在报告里(缺 IPC 路由 / 被前台锁定拦下),因为它们是两个不同的修法,维护者需要知道你说的是哪一个。 3. 请补三样(能立刻定性)
4. 版本口径你在 桌面 另: 一条边界我给出的是 Electron 的职责分工与 Windows 前台锁定这一通用机制(足以解释你的现象与那两个候选修法)。你这份构建里通知由谁创建、点击后走了什么以你的排查为准——我没有该构建的源码可读(桌面壳在打包产物内)。 |
补充实测:根因不在"缺 IPC 通道",而在
|
| 点击通知时窗口状态 | 结果 |
|---|---|
| 已最小化 | 窗口正常回到前台 ✅ |
| 可见,但被别的程序压在后面 | 窗口纹丝不动 ❌ |
两种状态外观差别很小(都表现为"不在前台"),所以体感像随机失败。
3. 根因:apps/desktop/src/main.ts:1212
focusPrimaryWindow = () => {
if (quitting) return
if (isMandatory()) { mandatoryUI?.focus(); return }
const window = welcomeWindow ?? mainWindow
if (window === undefined || window.isDestroyed()) { /* ... */ return }
if (window === mainWindow && !enteredWorkspace) return
if (window.isMinimized()) window.restore() // ← 只有"已最小化"时才走
window.show()
window.focus() // ← 遮挡场景只剩这个,被前台锁拒绝
}窗口未被最小化时只会执行 show() + focus(),而后台进程仅凭 SetForegroundWindow(Electron 的 window.focus())会被 Windows 前台锁(HKCU\Control Panel\Desktop\ForegroundLockTimeout,本机 200000 ms)拒绝。
4. 受控实验(真实 dsh://open 深链,非合成输入)
每次试验前用真实遮挡窗口建立前置条件并校验,每次同时确认深链是否触发:
| 场景 | 对应代码路径 | 深链触发 | 抬窗成功 |
|---|---|---|---|
| (A) 窗口可见、其他程序占前台 | show() + focus() |
6/6 | 0/6 ❌ |
| (B) 窗口已最小化、其他程序占前台 | restore() + show() + focus() |
6/6 | 5/6 ✅ |
纯 Win32 原语对照(窗口做成"可见但非前台",不掺任何深链),确认这是 OS 层规则而非协议问题:
| 策略 | 成功 |
|---|---|
SetForegroundWindow(= 壳的 window.focus()) |
0/6 |
minimize → restore |
8/8 |
SetWindowPos(TOPMOST) → focus → 摘除置顶 |
0/3 |
SetWindowPos(TOPMOST) → focus(保持置顶) |
0/4 |
另测:WS_EX_TOPMOST 能把窗口抬到遮挡窗口之上(Z 序 idx 0 vs 2),但拿不到键盘焦点;moveTop + focus 同样 0/1。
方法论提醒:合成输入(CDP
evaluate/Input.dispatchMouseEvent)测不出这条链路——早期我用Start-Process dsh://open做遮挡测试,先后得到"失败"和"成功"两个矛盾结果,正是因为合成输入的前台授权状态不稳定。上表已全部改为可校验前置条件的对照设计。(这一点在 #8800 里也和 chromoany 讨论过。)
5. 修复建议
在 focusPrimaryWindow() 中,当窗口未被最小化但前台权被拒时,把"强制最小化再还原"作为兜底:
if (window.isMinimized()) window.restore()
window.show()
if (!window.isFocused()) { // 前台权被前台锁拒绝时的兜底
window.minimize()
window.restore()
}
window.focus()依据:minimize → restore 在遮挡场景 8/8 成功,而现有路径 0/6;同类问题 #8043(Windows 前台锁 + 窗口不弹前台)的修复方向也是"最小化 → 还原 → SetForegroundWindow",手法一致。该兜底路径实测在 0.1s 内完成。
6. 版本口径
- 本机桌面端 0.2.0-rc.2(Electron 44.0.0),Windows 11 build 26300
dsh-v0.2.1-alpha.1的apps/desktop/src/main.ts与 0.2.0-rc.2 逐字节一致(SHA2560FA49B658B970E19ED1C0F7D1E3EC55DD39F7E8236D282431484BA9C501B3EE8),所以本结论对当前最新版同样成立
7. 一条如实说明
"前台锁"是本现象最合理的解释,但我没有通过直接改写 ForegroundLockTimeout 做因果验证——本机 SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT) 始终返回 ERROR_INVALID_PARAMETER,无法生效。上表结论的因果依据是"同一窗口状态下,仅 focus() 失败而 minimize → restore 成功"这一对照,而非来自开关前台锁。另外"restore() 属于前台锁豁免路径"是推测,未单独验证。
复现脚本(真实深链 A/B + Win32 原语对照)我保留了完整版本,需要的话我贴出来或直接开 PR。
你的受控实验正好落在我们此前分开的那一层——而且它把另一半排除了1. 你的实验否掉的是哪一半我在这个问题的早期回复里给出过两层:
⇒ 你这条实测把第 1 层排除了、把第 2 层坐实了:
⇒ 所以你写"换 IPC 通道解决不了"是对的:换通道后仍会走 2. ⇒ 可执行的修法(Windows 前台锁的常规处置)对"后台进程抢前台被拒"这一类,Windows 上没有纯 API 的正解(前台权限归属于当前前台窗口的线程),但有一条被广泛使用的确定性绕法: ⇒ 原理是先改变窗口的 Z 序状态,使系统把它当作"用户可见的置顶窗口"来激活,再撤掉置顶。 建议诉求写成:
3. 你给的判据很干净(建议保留)
4. 请补两样
5. 一条边界我确认的是**"两层假设"的框架以及你提供的实验数据与第 2 层吻合**。 |
Uh oh!
There was an error while loading. Please reload this page.
Bug 描述
在 Windows 桌面版 0.2.0-rc.2 中,点击系统通知或托盘图标,无法将 DeepSeek Harness 窗口聚焦到前台。
复现步骤
预期行为
点击通知后,DSH 窗口应恢复到前台并聚焦。
实际行为
点击通知无反应,窗口没有回到前台。但手动点击任务栏上的 DSH 图标可以正常恢复窗口。
环境信息
根因分析(已排查)
经过详细排查,问题的根本原因在于 DSH 的架构设计:
win.focus())来激活主进程窗口。focusPrimaryWindow()函数(包含restore,show,focus逻辑),但渲染进程没有 IPC 通道可以调用它。focusWindow(),因此可以正常激活窗口。建议修复方案
focusPrimaryWindow()。PS:这个问题折腾了我很久,换了几个插件都没用,最后用本地模型和API跑了几百万token才排查出根因(钱包在滴血😭)。希望官方大大能早日修复,或者给页面开个IPC权限,非常感谢!
All reactions