Skip to content

fix(window): 修复 macOS 全屏应用下呼出窗口一闪而过并落到桌面的问题#577

Open
Ethan0x0000 wants to merge 1 commit into
ZToolsCenter:mainfrom
Ethan0x0000:fix/macos-window-fullscreen-activation
Open

fix(window): 修复 macOS 全屏应用下呼出窗口一闪而过并落到桌面的问题#577
Ethan0x0000 wants to merge 1 commit into
ZToolsCenter:mainfrom
Ethan0x0000:fix/macos-window-fullscreen-activation

Conversation

@Ethan0x0000

Copy link
Copy Markdown
Contributor

问题

macOS 下(尤其前台应用处于原生全屏时)通过全局快捷键呼出主窗口,偶发「一闪而过」或窗口掉到桌面 Space,需要手动切到桌面才能看到。

原因

ZTools 在 macOS 下 app.dock.hide() 使其成为 accessory 应用,面板(type: 'panel')依靠 visibleOnFullScreen: true 才能覆盖全屏应用。两个叠加问题:

  1. 激活顺序forceActivateWindow 在 macOS 下先 show()app.focus({ steal: true }),dock 隐藏的应用会把窗口显示到「家 Space」(桌面),而非当前 Space。
  2. blur 竞态:macOS 的 blur 处理器会立即 hideWindow()。从全屏应用抢焦点瞬间系统会补发一次瞬时 blur,直接触发隐藏,造成「一闪而过」。双击唤醒路径已有 suppressBlurHide 规避,但全局快捷键的 showWindow() 路径没有。

改动

  • macOS 下 forceActivateWindow 调整为先 app.focus({ steal: true }) 激活到当前 Space 再 show(),避免面板落到桌面 Space。
  • 新增 suppressBlurHideTransiently(durationMs):呼出/激活瞬间短暂抑制 blur(默认 200ms),防止抢焦点时被瞬时 blur 触发 hideWindow;多次调用取最长时长,避免叠加(如双击唤醒 350ms + 普通呼出 200ms)时被提前释放。原双击唤醒的内联抑制逻辑复用此 helper。
  • openPluginInstaller 的 macOS 分支同步相同的激活顺序(该路径有自己的长时 blur 抑制,未直接复用 forceActivateWindow 以免瞬时抑制与外层 500ms 手动释放冲突)。
  • 补充 tests/main/windowManagerMacActivation.test.ts 单元测试。

验证

  • pnpm typecheck:node 通过
  • tests/main/windowManagerMacActivation.test.ts 单测通过
  • 改动仅限 macOS 激活路径,Windows / Linux 行为不变。

- macOS 下 forceActivateWindow 改为先 app.focus({steal:true}) 激活到当前 Space 再 show,避免 dock 隐藏应用的面板落到桌面 Space
- 新增 suppressBlurHideTransiently,呼出瞬间短暂抑制 blur,防止抢焦点时被瞬时 blur 触发 hideWindow 造成一闪而过(多次调用取最长时长)
- openPluginInstaller 的 macOS 分支同步相同的激活顺序
- 补充 windowManagerMacActivation 单元测试

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the window activation sequence on macOS to prevent the window from flashing or disappearing. It introduces a transient blur suppression mechanism (suppressBlurHideTransiently) to handle overlapping suppressions and adds a test suite to verify macOS activation behavior. The reviewer identified a potential race condition where suppressBlurHideTransiently directly modifies the shared this.suppressBlurHide flag, which can conflict with other long-running suppressions. They suggested decoupling transient suppression from the main flag and checking the timer state directly in isBlurHideSuppressed() to avoid conflicts.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +172 to +187
private suppressBlurHideTransiently(durationMs: number): void {
this.suppressBlurHide = true
const until = Date.now() + durationMs
if (this.transientBlurSuppressTimer && this.transientBlurSuppressUntil >= until) {
return
}
if (this.transientBlurSuppressTimer) {
clearTimeout(this.transientBlurSuppressTimer)
}
this.transientBlurSuppressUntil = until
this.transientBlurSuppressTimer = setTimeout(() => {
this.suppressBlurHide = false
this.transientBlurSuppressTimer = null
this.transientBlurSuppressUntil = 0
}, durationMs)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

当前实现中,suppressBlurHideTransiently 直接修改了 this.suppressBlurHide 共享变量。这会与 openPluginInstaller 中手动设置的 this.suppressBlurHide = true 产生冲突和竞态(例如,瞬时抑制定时器触发时会错误地将 this.suppressBlurHide 置为 false,从而提前释放了 openPluginInstaller 的长时抑制)。

为了彻底解决这个问题,建议将瞬时抑制与长时抑制解耦:

  1. suppressBlurHideTransiently 中不再修改 this.suppressBlurHide
  2. 修改 isBlurHideSuppressed() 方法(在上方第 162 行),将 !!this.transientBlurSuppressTimer 纳入判断:
    private isBlurHideSuppressed(): boolean {
      return this.suppressBlurHide || this.modalDialogBlurHideSuppressed || !!this.transientBlurSuppressTimer
    }

这样不仅能避免竞态,还能让 openPluginInstaller 直接复用 forceActivateWindow(),消除重复代码。

  private suppressBlurHideTransiently(durationMs: number): void {
    const until = Date.now() + durationMs
    if (this.transientBlurSuppressTimer && this.transientBlurSuppressUntil >= until) {
      return
    }
    if (this.transientBlurSuppressTimer) {
      clearTimeout(this.transientBlurSuppressTimer)
    }
    this.transientBlurSuppressUntil = until
    this.transientBlurSuppressTimer = setTimeout(() => {
      this.transientBlurSuppressTimer = null
      this.transientBlurSuppressUntil = 0
    }, durationMs)
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant