Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/main/api/plugin/dialog.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ipcMain, dialog, app } from 'electron'
import detachedWindowManager from '../../core/detachedWindowManager'
import windowManager from '../../managers/windowManager'

/**
* 对话框API - 插件专用
Expand Down Expand Up @@ -75,7 +76,9 @@ export class PluginDialogAPI {
event.returnValue = undefined
return
}
const result = dialog.showSaveDialogSync(targetWindow, options)
const result = windowManager.withBlurHideSuppressedSync(() =>
dialog.showSaveDialogSync(targetWindow, options)
)
event.returnValue = result
} catch (error) {
console.error('[PluginDialog] 显示文件保存对话框失败:', error)
Expand All @@ -94,7 +97,9 @@ export class PluginDialogAPI {
event.returnValue = []
return
}
const result = dialog.showOpenDialogSync(targetWindow, options)
const result = windowManager.withBlurHideSuppressedSync(() =>
dialog.showOpenDialogSync(targetWindow, options)
)
event.returnValue = result || []
} catch (error) {
console.error('[PluginDialog] 显示文件打开对话框失败:', error)
Expand Down
94 changes: 93 additions & 1 deletion src/main/managers/windowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import pluginManager from './pluginManager'
// 窗口材质类型
type WindowMaterial = 'mica' | 'acrylic' | 'none'
const WINDOW_BLUR_DRAG_INPUT_CONSUMER = 'window-blur-drag'
const DEFAULT_MODAL_DIALOG_BLUR_HIDE_RELEASE_DELAY_MS = 500

/**
* 应用快捷键触发时携带的文件输入
Expand Down Expand Up @@ -83,6 +84,10 @@ class WindowManager {
private lastFocusTarget: 'mainWindow' | 'plugin' | null = null // 窗口隐藏前的焦点状态
private isRestoringFocus: boolean = false // 是否正在恢复焦点状态(防止 focus 事件监听器干扰)
private suppressBlurHide: boolean = false // 临时抑制 blur 事件隐藏窗口(文件关联打开等场景)
// 原生模态对话框关闭前后可能发出排队的 blur/mouseup 事件。
private modalDialogBlurHideSuppressed: boolean = false
private modalDialogBlurHideReleaseTimer: ReturnType<typeof setTimeout> | null = null
private modalDialogBlurHideSuppressionDepth: number = 0
Comment on lines +87 to +90
private lastBlurHideTime: number = 0 // blur 导致隐藏窗口的时间戳(用于解决托盘点击竞态)
private blurHideTimer: ReturnType<typeof setTimeout> | null = null // Linux blur 延迟隐藏定时器
// Double-tap 唤醒窗口时,Windows 可能紧跟一个短暂 blur;这两个 timer 用于跳过误关闭并补一次焦点。
Expand Down Expand Up @@ -142,6 +147,45 @@ class WindowManager {
}
}

private isBlurHideSuppressed(): boolean {
return this.suppressBlurHide || this.modalDialogBlurHideSuppressed
}

private beginModalDialogBlurHideSuppression(): void {
if (this.modalDialogBlurHideReleaseTimer) {
clearTimeout(this.modalDialogBlurHideReleaseTimer)
this.modalDialogBlurHideReleaseTimer = null
}

this.modalDialogBlurHideSuppressionDepth += 1
this.modalDialogBlurHideSuppressed = true
}

private endModalDialogBlurHideSuppression(releaseDelayMs: number): void {
this.modalDialogBlurHideSuppressionDepth = Math.max(
0,
this.modalDialogBlurHideSuppressionDepth - 1
)
if (this.modalDialogBlurHideSuppressionDepth > 0) return

if (this.modalDialogBlurHideReleaseTimer) {
clearTimeout(this.modalDialogBlurHideReleaseTimer)
}

this.modalDialogBlurHideReleaseTimer = setTimeout(() => {
this.modalDialogBlurHideSuppressed = false
this.modalDialogBlurHideReleaseTimer = null
}, releaseDelayMs)
}
Comment on lines +164 to +179

private isPromiseLike<T>(value: T | PromiseLike<T>): value is PromiseLike<T> {
return (
value !== null &&
(typeof value === 'object' || typeof value === 'function') &&
typeof (value as { then?: unknown }).then === 'function'
)
}

private deferBlurHideUntilMouseUp(): void {
this.pendingBlurHideOnMouseUp = true
this.clearPendingBlurHideTimer()
Expand All @@ -152,6 +196,7 @@ class WindowManager {
if (!this.pendingBlurHideOnMouseUp) return

this.pendingBlurHideOnMouseUp = false
if (this.isBlurHideSuppressed()) return
if (this.mainWindow?.isFocused()) return
if (pluginManager.isPluginViewFocused()) return

Expand All @@ -169,6 +214,7 @@ class WindowManager {

private resolveMouseUpVisibility(): void {
if (!this.mainWindow?.isVisible()) return
if (this.isBlurHideSuppressed()) return

// 拖拽最终落在窗口内时保持窗口;落在窗口外时按普通外部点击处理并关闭。
const cursorPoint = screen.getCursorScreenPoint()
Expand Down Expand Up @@ -392,7 +438,7 @@ class WindowManager {
})

this.mainWindow.on('blur', () => {
if (this.suppressBlurHide) return
if (this.isBlurHideSuppressed()) return

// 左键仍按下时可能是从外部拖文件进窗口,先等 mouseup 再决定是否隐藏。
if (this.leftMouseDown) {
Expand All @@ -409,6 +455,7 @@ class WindowManager {
}
this.blurHideTimer = setTimeout(() => {
this.blurHideTimer = null
if (this.isBlurHideSuppressed()) return
// 主窗口重新获焦 → 不隐藏
if (this.mainWindow?.isFocused()) return
// 插件视图持有焦点(应用内部切换)→ 不隐藏
Expand Down Expand Up @@ -853,6 +900,51 @@ class WindowManager {
this.startAutoBackToSearchTimer()
}

public withBlurHideSuppressed<T>(
callback: () => PromiseLike<T>,
releaseDelayMs?: number
): Promise<T>
public withBlurHideSuppressed<T>(callback: () => T, releaseDelayMs?: number): T
public withBlurHideSuppressed<T>(
callback: () => T | PromiseLike<T>,
releaseDelayMs: number = DEFAULT_MODAL_DIALOG_BLUR_HIDE_RELEASE_DELAY_MS
): T | Promise<T> {
Comment on lines +908 to +911
this.beginModalDialogBlurHideSuppression()
try {
const result = callback()
if (this.isPromiseLike(result)) {
return Promise.resolve(result).finally(() => {
this.endModalDialogBlurHideSuppression(releaseDelayMs)
})
}

this.endModalDialogBlurHideSuppression(releaseDelayMs)
return result
} catch (error) {
this.endModalDialogBlurHideSuppression(releaseDelayMs)
throw error
}
}
Comment on lines +903 to +927

public withBlurHideSuppressedSync<T>(
callback: () => T,
releaseDelayMs: number = DEFAULT_MODAL_DIALOG_BLUR_HIDE_RELEASE_DELAY_MS
): T {
this.beginModalDialogBlurHideSuppression()
try {
const result = callback()
if (this.isPromiseLike(result)) {
throw new TypeError('withBlurHideSuppressedSync callback must not return a Promise')
}

this.endModalDialogBlurHideSuppression(releaseDelayMs)
return result
} catch (error) {
this.endModalDialogBlurHideSuppression(releaseDelayMs)
throw error
}
}
Comment thread
jn12-29 marked this conversation as resolved.

/**
* 启动自动返回搜索定时器
*/
Expand Down