diff --git a/internal-plugins/setting/src/components/common/ShortcutEditor/ShortcutEditor.vue b/internal-plugins/setting/src/components/common/ShortcutEditor/ShortcutEditor.vue index acc34b77..0a193b1b 100644 --- a/internal-plugins/setting/src/components/common/ShortcutEditor/ShortcutEditor.vue +++ b/internal-plugins/setting/src/components/common/ShortcutEditor/ShortcutEditor.vue @@ -13,35 +13,50 @@ >
- -
- 应用快捷键仅在 ZTools 窗口激活时生效,不会与其他应用冲突 -
+
+
+ +
+ 应用快捷键仅在 ZTools 窗口激活时生效,不会与其他应用冲突 +
- -
- - - 点击上方输入框录制快捷键 -
+ +
+ + + 点击上方输入框录制快捷键 +
- -
- - - 支持「指令名称」或「插件名称/指令名称」格式,建议加上插件名称以避免歧义 + +
+ + + 仅对截图类全局快捷键生效,触发后会优先预抓取屏幕首帧 +
+
+ +
+ + +
@@ -50,25 +65,33 @@ + + + + diff --git a/resources/preload.js b/resources/preload.js index a915c941..21a3d7b7 100644 --- a/resources/preload.js +++ b/resources/preload.js @@ -982,16 +982,24 @@ window.ztools = { await electron.ipcRenderer.invoke('internal:get-plugin-memory-info', pluginPath), // ==================== 全局快捷键 API ==================== - registerGlobalShortcut: async (shortcut, target, autoCopy) => - await electron.ipcRenderer.invoke('register-global-shortcut', shortcut, target, autoCopy), + registerGlobalShortcut: async (shortcut, target, autoCopy, preScreenshotOptimization) => + await electron.ipcRenderer.invoke( + 'internal:register-global-shortcut', + shortcut, + target, + autoCopy, + preScreenshotOptimization + ), unregisterGlobalShortcut: async (shortcut) => - await electron.ipcRenderer.invoke('unregister-global-shortcut', shortcut), + await electron.ipcRenderer.invoke('internal:unregister-global-shortcut', shortcut), updateGlobalShortcutConfig: async (shortcut, config) => - await electron.ipcRenderer.invoke('update-global-shortcut-config', shortcut, config), - startHotkeyRecording: async () => await electron.ipcRenderer.invoke('start-hotkey-recording'), + await electron.ipcRenderer.invoke('internal:update-global-shortcut-config', shortcut, config), + startHotkeyRecording: async () => + await electron.ipcRenderer.invoke('internal:start-hotkey-recording'), updateShortcut: async (shortcut) => - await electron.ipcRenderer.invoke('update-shortcut', shortcut), - getCurrentShortcut: async () => await electron.ipcRenderer.invoke('get-current-shortcut'), + await electron.ipcRenderer.invoke('internal:update-shortcut', shortcut), + getCurrentShortcut: async () => + await electron.ipcRenderer.invoke('internal:get-current-shortcut'), onHotkeyRecorded: (callback) => { if (callback && typeof callback === 'function') { hotkeyRecordedCallback = callback diff --git a/src/main/api/index.ts b/src/main/api/index.ts index afe8a9f3..890ac4ba 100644 --- a/src/main/api/index.ts +++ b/src/main/api/index.ts @@ -241,6 +241,13 @@ class APIManager { }) } + /** + * 清理 API 管理器持有的运行时资源。 + */ + public cleanup(): void { + settingsAPI.cleanup() + } + /** * 设置启动参数(用于插件进入时传递参数) */ @@ -287,13 +294,23 @@ class APIManager { windowAPI.resizeWindow(height) } + /** + * 归一化快捷键目标字符串,兼容历史上带空格的“插件 / 指令”格式。 + */ + private parseShortcutTarget(target: string): string[] { + return target + .split('/') + .map((part) => part.trim()) + .filter((part) => part.length > 0) + } + /** * 预解析全局快捷键目标,判断启动前是否需要采集选中文本。 * 仅文本类插件命令会触发复制取词,避免无关快捷键产生副作用。 */ public async prepareGlobalShortcut(target: string): Promise { try { - const parts = target.split('/') + const parts = this.parseShortcutTarget(target) const plugins: any = databaseAPI.dbGet('plugins') const disabledPlugins = pluginsAPI.getDisabledPluginSet() const pluginList = Array.isArray(plugins) @@ -320,11 +337,11 @@ class APIManager { return { target, shouldCaptureSelectedText: false } } - const pluginMatches: { cmdType: string }[] = [] + const pluginMatches: Array<{ cmdType: string; plugin: any; feature: any }> = [] for (const plugin of pluginList) { const result = await this.findCommandInPlugin(plugin, target) if (result) { - pluginMatches.push({ cmdType: result.cmdType }) + pluginMatches.push({ cmdType: result.cmdType, plugin, feature: result.feature }) } } @@ -555,7 +572,7 @@ class APIManager { ? plugins.filter((plugin: any) => !disabledPlugins.has(plugin.path)) : [] - const parts = target.split('/') + const parts = this.parseShortcutTarget(target) if (parts.length === 2) { // 格式: 插件名称/指令名称 diff --git a/src/main/api/plugin/internal.ts b/src/main/api/plugin/internal.ts index 1973dcb5..f7174e33 100644 --- a/src/main/api/plugin/internal.ts +++ b/src/main/api/plugin/internal.ts @@ -687,11 +687,22 @@ export class InternalPluginAPI { // ==================== 全局快捷键 API ==================== ipcMain.handle( 'internal:register-global-shortcut', - async (event, shortcut: string, target: string) => { + async ( + event, + shortcut: string, + target: string, + autoCopy?: boolean, + preScreenshotOptimization?: boolean + ) => { if (!requireInternalPlugin(this.pluginManager, event)) { throw new PermissionDeniedError('internal:register-global-shortcut') } - return settingsAPI.registerGlobalShortcut(shortcut, target) + return settingsAPI.registerGlobalShortcut( + shortcut, + target, + autoCopy ?? false, + preScreenshotOptimization ?? false + ) } ) @@ -709,6 +720,13 @@ export class InternalPluginAPI { return await settingsAPI.startHotkeyRecording() }) + ipcMain.handle('internal:get-current-shortcut', async (event) => { + if (!requireInternalPlugin(this.pluginManager, event)) { + throw new PermissionDeniedError('internal:get-current-shortcut') + } + return settingsAPI.getCurrentShortcutValue() + }) + ipcMain.handle('internal:update-shortcut', async (event, shortcut: string) => { if (!requireInternalPlugin(this.pluginManager, event)) { throw new PermissionDeniedError('internal:update-shortcut') @@ -716,6 +734,20 @@ export class InternalPluginAPI { return await settingsAPI.updateShortcut(shortcut) }) + ipcMain.handle( + 'internal:update-global-shortcut-config', + async ( + event, + shortcut: string, + config: { autoCopy: boolean; preScreenshotOptimization: boolean } + ) => { + if (!requireInternalPlugin(this.pluginManager, event)) { + throw new PermissionDeniedError('internal:update-global-shortcut-config') + } + return await settingsAPI.updateGlobalShortcutConfig(shortcut, config) + } + ) + // ==================== 应用快捷键 API ==================== ipcMain.handle( 'internal:register-app-shortcut', diff --git a/src/main/api/renderer/settings.ts b/src/main/api/renderer/settings.ts index b74a30cd..fc739d05 100644 --- a/src/main/api/renderer/settings.ts +++ b/src/main/api/renderer/settings.ts @@ -3,12 +3,16 @@ import fs from 'fs' import type { PluginManager } from '../../managers/pluginManager' // 共享API(主程序和插件都能用) -import { WindowManager as NativeWindowManager } from '../../core/native/index.js' +import { + OptimizedShortcutManager, + WindowManager as NativeWindowManager +} from '../../core/native/index.js' import { getCurrentShortcut, updateShortcut } from '../../index.js' import doubleTapManager from '../../core/doubleTapManager.js' import proxyManager from '../../managers/proxyManager.js' import windowManager from '../../managers/windowManager.js' +import { primeScreenCaptureFrame } from '../../core/screenCapture' import type { GlobalShortcutPreparation } from '../index' import api from '../index' import databaseAPI from '../shared/database' @@ -48,14 +52,127 @@ export class SettingsAPI { this.loadAndApplySettings() } + /** + * 停止设置模块持有的 native 快捷键监听资源。 + */ + public cleanup(): void { + if (process.platform === 'win32') { + OptimizedShortcutManager.stopListener() + } + this.nativeOptimizedShortcutSet.clear() + } + // 临时快捷键录制相关 private recordingShortcuts: string[] = [] // 全局快捷键配置映射(存储每个快捷键的 autoCopy 等配置) - private globalShortcutConfigs: Map = new Map() + private globalShortcutConfigs: Map< + string, + { autoCopy: boolean; preScreenshotOptimization: boolean } + > = new Map() + private globalShortcutTargets = new Map() + private globalShortcutPreparations = new Map() + private nativeOptimizedShortcutSet = new Set() private globalShortcutKeyboardStateReleasers = new Map void>() // 全局快捷键触发流程执行中时,后续触发会被忽略,避免重复复制和重复启动。 private isGlobalShortcutTriggering = false + // 启动 native 优化快捷键监听,并把命中事件回流到既有执行链路。 + private setupOptimizedShortcutListener(): void { + if (process.platform !== 'win32') { + return + } + + OptimizedShortcutManager.ensureListener(({ shortcut }) => { + console.log(`[Settings] native 优化快捷键触发: ${shortcut}`) + const preparation = this.globalShortcutPreparations.get(shortcut) + if (!preparation) { + console.warn(`[Settings] 未找到 native 优化快捷键预处理信息: ${shortcut}`) + return + } + void this.triggerGlobalShortcut(shortcut, preparation, true) + }) + } + + // 判断某个快捷键是否应由 native 接管监听。 + private shouldUseNativeOptimizedShortcut( + shortcut: string, + preScreenshotOptimization: boolean + ): boolean { + return ( + process.platform === 'win32' && + preScreenshotOptimization && + !this.isDoubleTapShortcut(shortcut) + ) + } + + // 注册单个 native 优化快捷键,并确保底层监听存在。 + private registerNativeOptimizedShortcut(shortcut: string): void { + this.setupOptimizedShortcutListener() + const result = OptimizedShortcutManager.registerShortcut(shortcut) + if (!result.success) { + throw new Error(result.error || 'native 优化快捷键注册失败') + } + this.nativeOptimizedShortcutSet.add(shortcut) + } + + // 注销单个 native 优化快捷键,并在为空时停止底层监听。 + private unregisterNativeOptimizedShortcut(shortcut: string): void { + const result = OptimizedShortcutManager.unregisterShortcut(shortcut) + if (!result.success) { + throw new Error(result.error || 'native 优化快捷键注销失败') + } + this.nativeOptimizedShortcutSet.delete(shortcut) + if (this.nativeOptimizedShortcutSet.size === 0) { + OptimizedShortcutManager.stopListener() + } + } + + // 统一清理一个全局快捷键的运行时注册状态。 + private unregisterGlobalShortcutBackend(shortcut: string): void { + if (this.isDoubleTapShortcut(shortcut)) { + const modifier = this.getDoubleTapModifier(shortcut) + doubleTapManager.unregister(modifier) + return + } + + if (this.nativeOptimizedShortcutSet.has(shortcut)) { + this.unregisterNativeOptimizedShortcut(shortcut) + return + } + + globalShortcut.unregister(shortcut) + } + + // 重新应用某个快捷键配置,必要时切换 Electron/native 后端。 + private async rebindGlobalShortcut( + shortcut: string, + force: boolean = false + ): Promise<{ success: boolean; error?: string }> { + const config = this.globalShortcutConfigs.get(shortcut) + const target = this.globalShortcutTargets.get(shortcut) + if (!config || !target) { + return { success: false, error: '快捷键信息不存在' } + } + + const previousWasNative = this.nativeOptimizedShortcutSet.has(shortcut) + const nextUseNative = this.shouldUseNativeOptimizedShortcut( + shortcut, + config.preScreenshotOptimization + ) + + if (!force && previousWasNative === nextUseNative) { + return { success: true } + } + + this.unregisterGlobalShortcutBackend(shortcut) + return await this.registerGlobalShortcut( + shortcut, + target, + config.autoCopy, + config.preScreenshotOptimization + ) + } + private setupIPC(): void { // 主题 ipcMain.handle('set-theme', (_event, theme: string) => this.setTheme(theme)) @@ -68,19 +185,33 @@ export class SettingsAPI { // 快捷键 ipcMain.handle('update-shortcut', (_event, shortcut: string) => this.updateShortcut(shortcut)) - ipcMain.handle('get-current-shortcut', () => this.getCurrentShortcut()) + ipcMain.handle('get-current-shortcut', () => this.getCurrentShortcutValue()) ipcMain.handle( 'register-global-shortcut', - (_event, shortcut: string, target: string, autoCopy?: boolean) => - this.registerGlobalShortcut(shortcut, target, autoCopy ?? false) + ( + _event, + shortcut: string, + target: string, + autoCopy?: boolean, + preScreenshotOptimization?: boolean + ) => + this.registerGlobalShortcut( + shortcut, + target, + autoCopy ?? false, + preScreenshotOptimization ?? false + ) ) ipcMain.handle('unregister-global-shortcut', (_event, shortcut: string) => this.unregisterGlobalShortcut(shortcut) ) ipcMain.handle( 'update-global-shortcut-config', - (_event, shortcut: string, config: { autoCopy: boolean }) => - this.updateGlobalShortcutConfig(shortcut, config) + ( + _event, + shortcut: string, + config: { autoCopy: boolean; preScreenshotOptimization: boolean } + ) => this.updateGlobalShortcutConfig(shortcut, config) ) // 应用快捷键 @@ -169,7 +300,8 @@ export class SettingsAPI { await this.registerGlobalShortcut( shortcut.shortcut, shortcut.target, - shortcut.autoCopy ?? false + shortcut.autoCopy ?? false, + shortcut.preScreenshotOptimization ?? false ) } catch (error) { console.error(`注册全局快捷键失败: ${shortcut.shortcut}`, error) @@ -239,7 +371,7 @@ export class SettingsAPI { } // 获取当前快捷键 - private getCurrentShortcut(): string { + public getCurrentShortcutValue(): string { return getCurrentShortcut() } @@ -265,17 +397,33 @@ export class SettingsAPI { public async registerGlobalShortcut( shortcut: string, target: string, - autoCopy: boolean = false + autoCopy: boolean = false, + preScreenshotOptimization: boolean = false ): Promise { - console.log(`[Settings] 注册全局快捷键: ${shortcut} -> ${target}, autoCopy: ${autoCopy}`) + console.log( + `[Settings] 注册全局快捷键: ${shortcut} -> ${target}, autoCopy: ${autoCopy}, preScreenshotOptimization: ${preScreenshotOptimization}` + ) try { // 存储快捷键配置 - this.globalShortcutConfigs.set(shortcut, { autoCopy }) + this.globalShortcutConfigs.set(shortcut, { autoCopy, preScreenshotOptimization }) + this.globalShortcutTargets.set(shortcut, target) console.log('[Settings] 快捷键配置已存储到 Map') this.ensureGlobalShortcutKeyboardState(shortcut) const preparation = await api.prepareGlobalShortcut(target) + this.globalShortcutPreparations.set(shortcut, preparation) + + const previousWasNative = this.nativeOptimizedShortcutSet.has(shortcut) + const nextUseNative = this.shouldUseNativeOptimizedShortcut( + shortcut, + preScreenshotOptimization + ) + + // 重新注册前先切换掉该快捷键当前占用的监听后端,避免 native/Electron 双后端互相占用。 + if (previousWasNative !== nextUseNative) { + this.unregisterGlobalShortcutBackend(shortcut) + } if (this.isDoubleTapShortcut(shortcut)) { const modifier = this.getDoubleTapModifier(shortcut) @@ -288,6 +436,13 @@ export class SettingsAPI { return { success: true } } + if (this.shouldUseNativeOptimizedShortcut(shortcut, preScreenshotOptimization)) { + globalShortcut.unregister(shortcut) + this.registerNativeOptimizedShortcut(shortcut) + console.log(`成功注册 native 优化快捷键: ${shortcut} -> ${target}`) + return { success: true } + } + // 先尝试取消注册该快捷键(如果已被注册),避免重复注册导致失败 globalShortcut.unregister(shortcut) @@ -299,6 +454,8 @@ export class SettingsAPI { if (!success) { this.releaseGlobalShortcutKeyboardState(shortcut) this.globalShortcutConfigs.delete(shortcut) + this.globalShortcutTargets.delete(shortcut) + this.globalShortcutPreparations.delete(shortcut) return { success: false, error: '快捷键注册失败,可能已被其他应用占用' } } @@ -307,6 +464,9 @@ export class SettingsAPI { } catch (error: unknown) { this.releaseGlobalShortcutKeyboardState(shortcut) this.globalShortcutConfigs.delete(shortcut) + this.globalShortcutTargets.delete(shortcut) + this.globalShortcutPreparations.delete(shortcut) + this.nativeOptimizedShortcutSet.delete(shortcut) console.error('[Settings] 注册全局快捷键失败:', error) return { success: false, error: error instanceof Error ? error.message : '未知错误' } } @@ -315,17 +475,11 @@ export class SettingsAPI { // 注销全局快捷键 public unregisterGlobalShortcut(shortcut: string): any { try { + this.unregisterGlobalShortcutBackend(shortcut) this.releaseGlobalShortcutKeyboardState(shortcut) this.globalShortcutConfigs.delete(shortcut) - - if (this.isDoubleTapShortcut(shortcut)) { - const modifier = this.getDoubleTapModifier(shortcut) - doubleTapManager.unregister(modifier) - console.log(`成功注销双击修饰键快捷键: ${shortcut}`) - return { success: true } - } - - globalShortcut.unregister(shortcut) + this.globalShortcutTargets.delete(shortcut) + this.globalShortcutPreparations.delete(shortcut) console.log(`成功注销全局快捷键: ${shortcut}`) return { success: true } } catch (error: unknown) { @@ -335,16 +489,48 @@ export class SettingsAPI { } /** - * 更新全局快捷键的配置(如 autoCopy) - * 仅更新配置,不重新注册快捷键 + * 更新全局快捷键的配置,并在后端需要切换时重新注册。 */ - public updateGlobalShortcutConfig(shortcut: string, config: { autoCopy: boolean }): any { + public async updateGlobalShortcutConfig( + shortcut: string, + config: { autoCopy: boolean; preScreenshotOptimization: boolean } + ): Promise<{ success: boolean; error?: string }> { + const previousConfig = this.globalShortcutConfigs.get(shortcut) + const previousTarget = this.globalShortcutTargets.get(shortcut) + const previousPreparation = this.globalShortcutPreparations.get(shortcut) + try { - console.log(`[Settings] 更新全局快捷键配置: ${shortcut}, autoCopy: ${config.autoCopy}`) + console.log( + `[Settings] 更新全局快捷键配置: ${shortcut}, autoCopy: ${config.autoCopy}, preScreenshotOptimization: ${config.preScreenshotOptimization}` + ) this.globalShortcutConfigs.set(shortcut, config) + const rebindResult = await this.rebindGlobalShortcut(shortcut) + if (!rebindResult.success) { + if (previousConfig) { + this.globalShortcutConfigs.set(shortcut, previousConfig) + if (previousTarget) { + this.globalShortcutTargets.set(shortcut, previousTarget) + } + if (previousPreparation) { + this.globalShortcutPreparations.set(shortcut, previousPreparation) + } + await this.rebindGlobalShortcut(shortcut, true) + } + return rebindResult + } console.log('[Settings] 配置更新成功') return { success: true } } catch (error: unknown) { + if (previousConfig) { + this.globalShortcutConfigs.set(shortcut, previousConfig) + if (previousTarget) { + this.globalShortcutTargets.set(shortcut, previousTarget) + } + if (previousPreparation) { + this.globalShortcutPreparations.set(shortcut, previousPreparation) + } + await this.rebindGlobalShortcut(shortcut, true) + } console.error('[Settings] 更新全局快捷键配置失败:', error) return { success: false, error: error instanceof Error ? error.message : '未知错误' } } @@ -378,7 +564,8 @@ export class SettingsAPI { */ private async triggerGlobalShortcut( shortcut: string, - preparation: GlobalShortcutPreparation + preparation: GlobalShortcutPreparation, + skipPrime: boolean = false ): Promise { if (!this.shouldTriggerGlobalShortcut(preparation.target)) { console.log(`[Settings] 上一次全局快捷键流程未完成,忽略本次触发: ${shortcut}`) @@ -391,10 +578,16 @@ export class SettingsAPI { // 读取该快捷键的 autoCopy 配置,默认 false const config = this.globalShortcutConfigs.get(shortcut) const autoCopy = config?.autoCopy ?? false + const preScreenshotOptimization = config?.preScreenshotOptimization ?? false console.log(`[Settings] 快捷键触发: ${shortcut}`) console.log(`[Settings] 指令类型需要文本: ${preparation.shouldCaptureSelectedText}`) console.log(`[Settings] 用户启用自动复制: ${autoCopy}`) + console.log(`[Settings] 用户启用预截图优化: ${preScreenshotOptimization}`) + + if (preScreenshotOptimization && !skipPrime) { + primeScreenCaptureFrame() + } // 双重判断:指令类型需要文本 AND 用户启用自动复制 const shouldCapture = preparation.shouldCaptureSelectedText && autoCopy diff --git a/src/main/core/native/index.ts b/src/main/core/native/index.ts index 5315b6d7..812f5432 100644 --- a/src/main/core/native/index.ts +++ b/src/main/core/native/index.ts @@ -54,9 +54,20 @@ interface NativeAddon { activateWindow: (identifier: string | number) => boolean simulatePaste: () => boolean simulateKeyboardTap: (key: string, ...modifiers: string[]) => boolean + ensureOptimizedShortcutListener: ( + callback: (payload: { shortcut: string; primed: boolean }) => void + ) => void + stopOptimizedShortcutListener: () => void + registerOptimizedShortcut: (shortcut: string) => { success: boolean; error?: string } + unregisterOptimizedShortcut: (shortcut: string) => { success: boolean; error?: string } + getOptimizedShortcutCount: () => number + primeScreenshotFrame: () => boolean startRegionCapture: ( callback: (result: { success: boolean; width?: number; height?: number }) => void ) => void + startRegionCaptureWithPrimedFrame: ( + callback: (result: { success: boolean; width?: number; height?: number }) => void + ) => void getClipboardFiles: () => ClipboardFile[] setClipboardFiles: (files: Array) => boolean simulateMouseMove: (x: number, y: number) => boolean @@ -770,10 +781,95 @@ export class MouseMonitor { } } +export class OptimizedShortcutManager { + private static _callback: ((payload: { shortcut: string; primed: boolean }) => void) | null = null + private static _isListening = false + + /** + * 启动 native 优化快捷键监听。 + */ + static ensureListener(callback: (payload: { shortcut: string; primed: boolean }) => void): void { + if (platform !== 'win32') { + return + } + + if (typeof callback !== 'function') { + throw new TypeError('Callback must be a function') + } + + OptimizedShortcutManager._callback = callback + if (OptimizedShortcutManager._isListening) { + return + } + + ;(addon as NativeAddon).ensureOptimizedShortcutListener((payload) => { + OptimizedShortcutManager._callback?.(payload) + }) + OptimizedShortcutManager._isListening = true + } + + /** + * 停止 native 优化快捷键监听。 + */ + static stopListener(): void { + if (platform !== 'win32') { + return + } + if (!OptimizedShortcutManager._isListening) { + return + } + + ;(addon as NativeAddon).stopOptimizedShortcutListener() + OptimizedShortcutManager._isListening = false + OptimizedShortcutManager._callback = null + } + + /** + * 注册一个由 native 接管监听的优化快捷键。 + */ + static registerShortcut(shortcut: string): { success: boolean; error?: string } { + if (platform !== 'win32') { + return { success: false, error: 'optimized shortcut is only supported on Windows' } + } + return (addon as NativeAddon).registerOptimizedShortcut(shortcut) + } + + /** + * 注销一个由 native 接管监听的优化快捷键。 + */ + static unregisterShortcut(shortcut: string): { success: boolean; error?: string } { + if (platform !== 'win32') { + return { success: false, error: 'optimized shortcut is only supported on Windows' } + } + return (addon as NativeAddon).unregisterOptimizedShortcut(shortcut) + } + + /** + * 获取当前由 native 接管的优化快捷键数量。 + */ + static getShortcutCount(): number { + if (platform !== 'win32') { + return 0 + } + return (addon as NativeAddon).getOptimizedShortcutCount() + } +} + /** * 区域截图类 */ export class ScreenCapture { + /** + * 预抓取当前虚拟屏幕帧 + */ + static prime(): boolean { + if (platform === 'darwin') { + throw new Error('ScreenCapture is not yet supported on macOS') + } + + return (addon as NativeAddon).primeScreenshotFrame() + } + /** * 启动区域截图 * @param {Function} callback - 截图完成时的回调函数 @@ -802,7 +898,7 @@ export class ScreenCapture { throw new TypeError('Callback must be a function') } - ;(addon as NativeAddon).startRegionCapture((result) => { + ;(addon as NativeAddon).startRegionCaptureWithPrimedFrame((result) => { callback(result) }) } diff --git a/src/main/core/screenCapture.ts b/src/main/core/screenCapture.ts index 1096c405..2af8f1bf 100644 --- a/src/main/core/screenCapture.ts +++ b/src/main/core/screenCapture.ts @@ -171,6 +171,19 @@ export const handleLinuxScreenShot = (cb: (image: string) => void): void => { } } +export const primeScreenCaptureFrame = (): boolean => { + if (process.platform !== 'win32') { + return false + } + + try { + return ScreenCapture.prime() + } catch (error) { + console.warn('[ScreenCapture] 预抓取屏幕失败:', error) + return false + } +} + export const screenCapture = ( mainWindow?: BrowserWindow, restoreShowWindow: boolean = true diff --git a/src/main/index.ts b/src/main/index.ts index 1ba87053..1d2bcee2 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -201,6 +201,7 @@ app.on('window-all-closed', () => { app.on('will-quit', () => { windowManager.unregisterAllShortcuts() + api.cleanup() // 停止应用目录监听 appWatcher.stop() // 清理悬浮球 diff --git a/src/preload/index.ts b/src/preload/index.ts index 3d92da3d..e261ab80 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -278,10 +278,25 @@ const api = { // 快捷键相关 updateShortcut: (shortcut: string) => ipcRenderer.invoke('update-shortcut', shortcut), getCurrentShortcut: () => ipcRenderer.invoke('get-current-shortcut'), - registerGlobalShortcut: (shortcut: string, target: string) => - ipcRenderer.invoke('register-global-shortcut', shortcut, target), + registerGlobalShortcut: ( + shortcut: string, + target: string, + autoCopy?: boolean, + preScreenshotOptimization?: boolean + ) => + ipcRenderer.invoke( + 'register-global-shortcut', + shortcut, + target, + autoCopy, + preScreenshotOptimization + ), unregisterGlobalShortcut: (shortcut: string) => ipcRenderer.invoke('unregister-global-shortcut', shortcut), + updateGlobalShortcutConfig: ( + shortcut: string, + config: { autoCopy: boolean; preScreenshotOptimization: boolean } + ) => ipcRenderer.invoke('update-global-shortcut-config', shortcut, config), // 快捷键录制(临时注册,触发后自动注销) startHotkeyRecording: () => ipcRenderer.invoke('start-hotkey-recording'), onHotkeyRecorded: (callback: (shortcut: string) => void) => { @@ -662,9 +677,15 @@ declare global { getCurrentShortcut: () => Promise registerGlobalShortcut: ( shortcut: string, - target: string + target: string, + autoCopy?: boolean, + preScreenshotOptimization?: boolean ) => Promise<{ success: boolean; error?: string }> unregisterGlobalShortcut: (shortcut: string) => Promise<{ success: boolean; error?: string }> + updateGlobalShortcutConfig: ( + shortcut: string, + config: { autoCopy: boolean; preScreenshotOptimization: boolean } + ) => Promise<{ success: boolean; error?: string }> // 窗口相关 windowPaste: () => Promise<{ success: boolean; error?: string }> // 子输入框相关 diff --git a/src/renderer/src/env.d.ts b/src/renderer/src/env.d.ts index 76d7ec72..243f2e95 100644 --- a/src/renderer/src/env.d.ts +++ b/src/renderer/src/env.d.ts @@ -231,12 +231,13 @@ declare global { registerGlobalShortcut: ( shortcut: string, target: string, - autoCopy?: boolean + autoCopy?: boolean, + preScreenshotOptimization?: boolean ) => Promise<{ success: boolean; error?: string }> unregisterGlobalShortcut: (shortcut: string) => Promise<{ success: boolean; error?: string }> updateGlobalShortcutConfig: ( shortcut: string, - config: { autoCopy: boolean } + config: { autoCopy: boolean; preScreenshotOptimization: boolean } ) => Promise<{ success: boolean; error?: string }> // 快捷键录制(临时注册,触发后自动注销) startHotkeyRecording: () => Promise<{ success: boolean; error?: string }>