diff --git a/packages/desktop-client/src/main/island.ts b/packages/desktop-client/src/main/island.ts index 65d715c..2464ad9 100644 --- a/packages/desktop-client/src/main/island.ts +++ b/packages/desktop-client/src/main/island.ts @@ -198,8 +198,16 @@ class IslandController { fullscreenable: false, skipTaskbar: true, hasShadow: false, - // 不抢焦点:通知不应该打断学生正在做的事(键盘焦点仍留在原应用) - focusable: false, + /** + * **必须可激活**(focusable: true)。 + * + * Windows 下 `WS_EX_NOACTIVATE` 且窗口不是活动窗口时,鼠标**按下**事件会被丢掉: + * 渲染进程只会收到 mouseup、收不到 mousedown/click,于是胶囊怎么点都没反应 + * (真机实测:胶囊态只有 mouseup;展开态 focusable 时是完整的 mousedown+mouseup+click)。 + * "不抢焦点"靠 `showInactive()`(显示时不激活)+ 收起时 `blur()` 保证,而不是靠 + * 关掉可激活性 —— 关掉可激活性的代价就是"点不动"。 + */ + focusable: true, alwaysOnTop: true, type: 'toolbar', webPreferences: { @@ -637,7 +645,8 @@ class IslandController { this.emit(); } if (!this.win || this.win.isDestroyed()) return; - this.win.setFocusable(false); + // 焦点/可激活性统一由 applyFocusable 维护(窗口必须始终可激活,否则丢 mousedown) + if (this.win.isFocused()) this.win.blur(); if (this.win.isVisible()) this.win.hide(); this.win.setOpacity(1); } @@ -893,16 +902,21 @@ class IslandController { } /** - * 展开态允许聚焦(用于"点击屏幕任意处收起"),其余时间不抢焦点。 + * 焦点策略:**窗口始终可激活**,只有展开态才真正持有键盘焦点。 * - * 注意:这个开关本身就是 Windows 上那次"假失焦"的来源(见 blur 处理器注释), - * 因此收起时的判断不能只依赖 blur,必须结合指针位置。 + * 为什么不能把可激活性关掉(focusable=false):Windows 下 `WS_EX_NOACTIVATE` 且非活动窗口 + * 会**丢掉鼠标按下事件**(实测只到 mouseup、没有 mousedown/click),胶囊就"点不动"了。 + * 因此收起/隐藏时改为 `blur()`:既不占着用户的键盘焦点,又保持可激活(点击能正常送达)。 + * 展开态 `focus()` 是为了"点屏幕别处 → 失焦 → 回缩为胶囊"这条交互生效。 */ private applyFocusable(): void { if (!this.win || this.win.isDestroyed()) return; - const shouldFocus = this.state.mode === 'expanded'; - if (this.win.isFocusable() !== shouldFocus) this.win.setFocusable(shouldFocus); - if (shouldFocus) this.win.focus(); + if (!this.win.isFocusable()) this.win.setFocusable(true); + if (this.state.mode === 'expanded') { + this.win.focus(); + return; + } + if (this.win.isFocused()) this.win.blur(); } private fadeIn(): void { diff --git a/packages/desktop-client/src/main/smoke.ts b/packages/desktop-client/src/main/smoke.ts index 9d04f11..81e8e4f 100644 --- a/packages/desktop-client/src/main/smoke.ts +++ b/packages/desktop-client/src/main/smoke.ts @@ -928,11 +928,33 @@ async function runIslandChecks( await sleep(700); const afterBlur = island.getState(); record( - '点击屏幕任意位置回缩为胶囊(展开时可聚焦 + 失焦收起)', - focusable && islandWindow?.isFocusable() === false && afterBlur.mode === 'pill', + '点击屏幕任意位置回缩为胶囊(失焦收起;窗口始终可激活)', + focusable && islandWindow?.isFocusable() === true && afterBlur.mode === 'pill', `展开时 focusable=${focusable} 失焦后 mode=${afterBlur.mode} focusable=${islandWindow?.isFocusable() ?? '-'}`, ); + // 4.5.2) 胶囊态窗口**必须始终可激活**:Windows 下 WS_EX_NOACTIVATE 且非活动窗口会丢掉 + // 鼠标**按下**事件(实测只到 mouseup、没有 mousedown/click)→ 胶囊"点不动"。 + // 这是用户反馈的根因,用"胶囊态 isFocusable() 必须为 true"固化下来。 + for (let index = 0; index < 10; index += 1) { + const current = island.getState(); + if (!current.active && current.queued.length === 0) break; + island.handleAction({ action: 'dismiss' }); + await sleep(120); + } + island.pushNotification(makeNotification('smoke-pill-focusable', 'NORMAL', '胶囊可激活校验'), { + inClass: false, + }); + await sleep(500); + const pillFocusState = island.getState(); + record( + '胶囊态窗口仍可激活(否则 Windows 丢掉 mousedown,胶囊"点不动")', + pillFocusState.mode === 'pill' && (islandWindow?.isFocusable() ?? false) === true, + `mode=${pillFocusState.mode} focusable=${islandWindow?.isFocusable() ?? '-'} 当前是否聚焦=${islandWindow?.isFocused() ?? '-'}`, + ); + island.handleAction({ action: 'dismiss' }); + await sleep(300); + // 4.5.1) 展开动作自身引发的**假失焦**不能把刚展开的岛缩回胶囊。 // 用户复现:"收起后再次点击灵动岛没反应" —— 点开胶囊后约 0.5s 被系统收回焦点, // 岛立刻缩回胶囊,看起来就像"点了没反应"。此时指针必然还在岛上,必须忽略该失焦。