Skip to content
Open
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
1 change: 1 addition & 0 deletions internal-plugins/setting/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type AutoClearOption = 'immediately' | '1m' | '2m' | '3m' | '5m' | '10m'

// 自动返回搜索选项类型
export type AutoBackToSearchOption = 'immediately' | '30s' | '1m' | '3m' | '5m' | '10m' | 'never'
export type WindowPositionStrategy = 'remember' | 'cursor' | 'primary' | 'lastActive'

// 主题类型
export type ThemeType = 'system' | 'light' | 'dark'
Expand Down
1 change: 1 addition & 0 deletions internal-plugins/setting/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ declare global {
updateAutoPaste: (autoPaste: string) => Promise<void>
updateAutoClear: (autoClear: string) => Promise<void>
updateAutoBackToSearch: (autoBackToSearch: string) => Promise<void>
updateWindowPositionStrategy: (strategy: string) => Promise<void>
updateShowRecentInSearch: (showRecentInSearch: boolean) => Promise<void>
updateMatchRecommendation: (showMatchRecommendation: boolean) => Promise<void>
updateLocalAppSearch: (enabled: boolean) => Promise<void>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
type AutoPasteOption,
type MouseButtonType,
type PrimaryColor,
type ThemeType
type ThemeType,
type WindowPositionStrategy
} from '@/constants'
import { Dropdown, HotkeyInput, Slider, useToast } from '@/components'
import { applyCustomColor, applyPrimaryColor } from '@/utils'
Expand Down Expand Up @@ -56,6 +57,13 @@ const autoBackToSearchOptions = [
{ label: '从不', value: 'never' }
]

const windowPositionStrategyOptions = [
{ label: '记住上次位置', value: 'remember' },
{ label: '鼠标屏居中', value: 'cursor' },
{ label: '主屏居中', value: 'primary' },
{ label: '上次活动屏居中', value: 'lastActive' }
]

const recentRowsOptions = [
{ label: '1行', value: 1 },
{ label: '2行', value: 2 },
Expand Down Expand Up @@ -129,6 +137,7 @@ const avatar = ref(DEFAULT_AVATAR)
const autoPaste = ref<AutoPasteOption>('3s')
const autoClear = ref<AutoClearOption>('immediately')
const autoBackToSearch = ref<AutoBackToSearchOption>('never')
const windowPositionStrategy = ref<WindowPositionStrategy>('remember')
const showRecentInSearch = ref(true)
const showMatchRecommendation = ref(true)
const localAppSearch = ref(true)
Expand Down Expand Up @@ -501,6 +510,17 @@ async function handleAutoBackToSearchChange(): Promise<void> {
}
}

// 处理窗口呼出位置策略变化
async function handleWindowPositionStrategyChange(): Promise<void> {
try {
await saveSettings()
await window.ztools.internal.updateWindowPositionStrategy(windowPositionStrategy.value)
console.log('窗口呼出位置策略已更新:', windowPositionStrategy.value)
} catch (error) {
console.error('保存窗口呼出位置策略失败:', error)
}
}

// 处理主题变化
async function handleThemeChange(): Promise<void> {
try {
Expand Down Expand Up @@ -1203,6 +1223,7 @@ async function loadSettings(): Promise<void> {
autoPaste.value = data.autoPaste ?? '3s'
autoClear.value = data.autoClear ?? 'immediately'
autoBackToSearch.value = data.autoBackToSearch ?? 'never'
windowPositionStrategy.value = data.windowPositionStrategy ?? 'remember'
showRecentInSearch.value = data.showRecentInSearch ?? true
showMatchRecommendation.value = data.showMatchRecommendation ?? true
localAppSearch.value = data.localAppSearch ?? true
Expand Down Expand Up @@ -1296,6 +1317,7 @@ async function saveSettings(): Promise<void> {
autoPaste: autoPaste.value,
autoClear: autoClear.value,
autoBackToSearch: autoBackToSearch.value,
windowPositionStrategy: windowPositionStrategy.value,
showRecentInSearch: showRecentInSearch.value,
showMatchRecommendation: showMatchRecommendation.value,
localAppSearch: localAppSearch.value,
Expand Down Expand Up @@ -1872,6 +1894,20 @@ onUnmounted(() => {
</div>
</div>

<div class="setting-item">
<div class="setting-label">
<span>窗口呼出位置</span>
<span class="setting-desc">每次呼出主窗口时的定位策略</span>
</div>
<div class="setting-control">
<Dropdown
v-model="windowPositionStrategy"
:options="windowPositionStrategyOptions"
@change="handleWindowPositionStrategyChange"
/>
</div>
</div>

<div class="setting-item">
<div class="setting-label">
<span>插件默认高度</span>
Expand Down
2 changes: 2 additions & 0 deletions resources/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,8 @@ window.ztools = {
// 通知主渲染进程更新自动返回搜索配置
updateAutoBackToSearch: async (autoBackToSearch) =>
await electron.ipcRenderer.invoke('internal:update-auto-back-to-search', autoBackToSearch),
updateWindowPositionStrategy: async (strategy) =>
await electron.ipcRenderer.invoke('internal:update-window-position-strategy', strategy),
// 通知主渲染进程更新显示最近使用配置
updateShowRecentInSearch: async (showRecentInSearch) =>
await electron.ipcRenderer.invoke(
Expand Down
9 changes: 9 additions & 0 deletions src/main/api/plugin/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,15 @@ export class InternalPluginAPI {
}
)

// 更新窗口呼出位置策略(直接通知主进程)
ipcMain.handle('internal:update-window-position-strategy', async (event, strategy: string) => {
if (!requireInternalPlugin(this.pluginManager, event)) {
throw new PermissionDeniedError('internal:update-window-position-strategy')
}
await windowAPI.updateWindowPositionStrategy(strategy)
return { success: true }
})

// 通知主渲染进程更新显示最近使用配置
ipcMain.handle(
'internal:update-show-recent-in-search',
Expand Down
5 changes: 5 additions & 0 deletions src/main/api/renderer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ export class SettingsAPI {
await windowManager.updateAutoBackToSearch(data.autoBackToSearch)
console.log('[Settings] 启动时应用自动返回搜索设置:', data.autoBackToSearch)
}
// 应用窗口呼出位置策略设置
if (data.windowPositionStrategy) {
await windowManager.updateWindowPositionStrategy(data.windowPositionStrategy)
console.log('[Settings] 启动时应用窗口呼出位置策略:', data.windowPositionStrategy)
}
// 应用代理配置
if (data.proxyEnabled !== undefined && data.proxyUrl !== undefined) {
proxyManager.setProxyConfig({
Expand Down
5 changes: 5 additions & 0 deletions src/main/api/renderer/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ export class WindowAPI {
await windowManager.updateAutoBackToSearch(autoBackToSearch)
console.log('[WindowAPI] 更新自动返回搜索配置:', autoBackToSearch)
}

public async updateWindowPositionStrategy(strategy: string): Promise<void> {
await windowManager.updateWindowPositionStrategy(strategy)
console.log('[WindowAPI] 更新窗口呼出位置策略:', strategy)
}
}

export default new WindowAPI()
79 changes: 64 additions & 15 deletions src/main/managers/windowManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class WindowManager {
private windowPositionsByDisplay: Record<number, { x: number; y: number }> = {}
private autoBackToSearchTimer: NodeJS.Timeout | null = null // 自动返回搜索定时器
private autoBackToSearchConfig: string = 'never' // 自动返回搜索配置
private windowPositionStrategy: string = 'remember' // 窗口呼出位置策略
private lastFocusTarget: 'mainWindow' | 'plugin' | null = null // 窗口隐藏前的焦点状态
private isRestoringFocus: boolean = false // 是否正在恢复焦点状态(防止 focus 事件监听器干扰)
private suppressBlurHide: boolean = false // 临时抑制 blur 事件隐藏窗口(文件关联打开等场景)
Expand Down Expand Up @@ -843,28 +844,68 @@ class WindowManager {
}

/**
* 将窗口移动到鼠标所在显示器
* 优先恢复该显示器记忆的位置,否则居中显示
* 根据窗口呼出位置策略将窗口移动到目标显示器并定位
* - remember:优先恢复该显示器记忆的位置,否则居中
* - cursor:鼠标所在显示器居中
* - primary:主显示器居中
* - lastActive:上次活动窗口所在显示器居中(无记录时 fallback 到鼠标屏)
*/
private moveWindowToCursor(): void {
if (!this.mainWindow) return

const { width, height, x: displayX, y: displayY, id: displayId } = this.getDisplayAtCursor()
let target: { width: number; height: number; x: number; y: number; id: number }

const savedPosition = this.windowPositionsByDisplay[displayId]

let x: number, y: number

if (savedPosition) {
// 恢复该显示器记忆的位置
x = savedPosition.x
y = savedPosition.y
} else {
// 计算默认居中位置(基于最大窗口高度)
x = displayX + Math.floor((width - WINDOW_WIDTH) / 2)
y = displayY + Math.floor((height - WINDOW_DEFAULT_HEIGHT) / 2)
switch (this.windowPositionStrategy) {
case 'primary': {
const display = screen.getPrimaryDisplay()
target = { ...display.workArea, id: display.id }
break
}
case 'lastActive': {
const prev = this.previousActiveWindow
if (
prev &&
typeof prev.x === 'number' &&
typeof prev.y === 'number' &&
Number.isFinite(prev.x) &&
Number.isFinite(prev.y)
) {
const centerX =
prev.x +
(typeof prev.width === 'number' && Number.isFinite(prev.width)
? Math.floor(prev.width / 2)
: 0)
const centerY =
prev.y +
(typeof prev.height === 'number' && Number.isFinite(prev.height)
? Math.floor(prev.height / 2)
: 0)
const display = screen.getDisplayNearestPoint({ x: centerX, y: centerY })
target = { ...display.workArea, id: display.id }
} else {
target = this.getDisplayAtCursor()
}
break
}
Comment thread
Cateds marked this conversation as resolved.
case 'cursor': {
target = this.getDisplayAtCursor()
break
}
case 'remember':
default: {
const { width, height, x: displayX, y: displayY, id: displayId } = this.getDisplayAtCursor()
const savedPosition = this.windowPositionsByDisplay[displayId]
if (savedPosition) {
this.mainWindow.setPosition(savedPosition.x, savedPosition.y, false)
return
}
target = { width, height, x: displayX, y: displayY, id: displayId }
break
}
}

const x = target.x + Math.floor((target.width - WINDOW_WIDTH) / 2)
const y = target.y + Math.floor((target.height - WINDOW_DEFAULT_HEIGHT) / 2)
this.mainWindow.setPosition(x, y, false)
}

Expand Down Expand Up @@ -1054,6 +1095,14 @@ class WindowManager {
console.log('[Window] 更新自动返回搜索配置:', config)
}

/**
* 更新窗口呼出位置策略
*/
public async updateWindowPositionStrategy(strategy: string): Promise<void> {
this.windowPositionStrategy = strategy
console.log('[Window] 更新窗口呼出位置策略:', strategy)
}

/**
* 获取打开窗口前激活的窗口
*/
Expand Down