From 8522a47b5f00eb8699f20c15112b25ca3ccf1a7c Mon Sep 17 00:00:00 2001 From: landaiqing Date: Sun, 8 Jun 2025 23:07:56 +0800 Subject: [PATCH] :sparkles: Added system tray service --- .../voidraft/internal/models/models.ts | 622 ------------------ frontend/src/i18n/locales/en-US.ts | 1 + frontend/src/i18n/locales/zh-CN.ts | 1 + frontend/src/stores/configStore.ts | 7 +- .../src/views/settings/pages/GeneralPage.vue | 9 + internal/events/tray_events.go | 31 +- internal/models/config.go | 6 +- internal/services/config_service.go | 2 + internal/services/service_manager.go | 10 + internal/services/tray_service.go | 79 +++ internal/systray/systray.go | 5 +- main.go | 9 +- 12 files changed, 136 insertions(+), 646 deletions(-) delete mode 100644 frontend/bindings/voidraft/internal/models/models.ts create mode 100644 internal/services/tray_service.go diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts deleted file mode 100644 index b4f0099..0000000 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ /dev/null @@ -1,622 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Unused imports -import {Create as $Create} from "@wailsio/runtime"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Unused imports -import * as time$0 from "../../../time/models.js"; - -/** - * AppConfig 应用配置 - 按照前端设置页面分类组织 - */ -export class AppConfig { - /** - * 通用设置 - */ - "general": GeneralConfig; - - /** - * 编辑设置 - */ - "editing": EditingConfig; - - /** - * 外观设置 - */ - "appearance": AppearanceConfig; - - /** - * 快捷键设置 - */ - "keyBindings": KeyBindingsConfig; - - /** - * 更新设置 - */ - "updates": UpdatesConfig; - - /** - * 配置元数据 - */ - "metadata": ConfigMetadata; - - /** Creates a new AppConfig instance. */ - constructor($$source: Partial = {}) { - if (!("general" in $$source)) { - this["general"] = (new GeneralConfig()); - } - if (!("editing" in $$source)) { - this["editing"] = (new EditingConfig()); - } - if (!("appearance" in $$source)) { - this["appearance"] = (new AppearanceConfig()); - } - if (!("keyBindings" in $$source)) { - this["keyBindings"] = (new KeyBindingsConfig()); - } - if (!("updates" in $$source)) { - this["updates"] = (new UpdatesConfig()); - } - if (!("metadata" in $$source)) { - this["metadata"] = (new ConfigMetadata()); - } - - Object.assign(this, $$source); - } - - /** - * Creates a new AppConfig instance from a string or object. - */ - static createFrom($$source: any = {}): AppConfig { - const $$createField0_0 = $$createType0; - const $$createField1_0 = $$createType1; - const $$createField2_0 = $$createType2; - const $$createField3_0 = $$createType3; - const $$createField4_0 = $$createType4; - const $$createField5_0 = $$createType5; - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("general" in $$parsedSource) { - $$parsedSource["general"] = $$createField0_0($$parsedSource["general"]); - } - if ("editing" in $$parsedSource) { - $$parsedSource["editing"] = $$createField1_0($$parsedSource["editing"]); - } - if ("appearance" in $$parsedSource) { - $$parsedSource["appearance"] = $$createField2_0($$parsedSource["appearance"]); - } - if ("keyBindings" in $$parsedSource) { - $$parsedSource["keyBindings"] = $$createField3_0($$parsedSource["keyBindings"]); - } - if ("updates" in $$parsedSource) { - $$parsedSource["updates"] = $$createField4_0($$parsedSource["updates"]); - } - if ("metadata" in $$parsedSource) { - $$parsedSource["metadata"] = $$createField5_0($$parsedSource["metadata"]); - } - return new AppConfig($$parsedSource as Partial); - } -} - -/** - * AppearanceConfig 外观设置配置 - */ -export class AppearanceConfig { - /** - * 界面语言 - */ - "language": LanguageType; - - /** - * 编辑器主题 - */ - "theme": ThemeType; - - /** Creates a new AppearanceConfig instance. */ - constructor($$source: Partial = {}) { - if (!("language" in $$source)) { - this["language"] = ("" as LanguageType); - } - if (!("theme" in $$source)) { - this["theme"] = ("" as ThemeType); - } - - Object.assign(this, $$source); - } - - /** - * Creates a new AppearanceConfig instance from a string or object. - */ - static createFrom($$source: any = {}): AppearanceConfig { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new AppearanceConfig($$parsedSource as Partial); - } -} - -/** - * ConfigMetadata 配置元数据 - */ -export class ConfigMetadata { - /** - * 配置版本 - */ - "version": string; - - /** - * 最后更新时间 - */ - "lastUpdated": time$0.Time; - - /** Creates a new ConfigMetadata instance. */ - constructor($$source: Partial = {}) { - if (!("version" in $$source)) { - this["version"] = ""; - } - if (!("lastUpdated" in $$source)) { - this["lastUpdated"] = null; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new ConfigMetadata instance from a string or object. - */ - static createFrom($$source: any = {}): ConfigMetadata { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new ConfigMetadata($$parsedSource as Partial); - } -} - -/** - * Document 表示一个文档 - */ -export class Document { - /** - * 元数据 - */ - "meta": DocumentMeta; - - /** - * 文档内容 - */ - "content": string; - - /** Creates a new Document instance. */ - constructor($$source: Partial = {}) { - if (!("meta" in $$source)) { - this["meta"] = (new DocumentMeta()); - } - if (!("content" in $$source)) { - this["content"] = ""; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new Document instance from a string or object. - */ - static createFrom($$source: any = {}): Document { - const $$createField0_0 = $$createType6; - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("meta" in $$parsedSource) { - $$parsedSource["meta"] = $$createField0_0($$parsedSource["meta"]); - } - return new Document($$parsedSource as Partial); - } -} - -/** - * DocumentMeta 文档元数据 - */ -export class DocumentMeta { - /** - * 文档唯一标识 - */ - "id": string; - - /** - * 文档标题 - */ - "title": string; - - /** - * 最后更新时间 - */ - "lastUpdated": time$0.Time; - - /** - * 创建时间 - */ - "createdAt": time$0.Time; - - /** Creates a new DocumentMeta instance. */ - constructor($$source: Partial = {}) { - if (!("id" in $$source)) { - this["id"] = ""; - } - if (!("title" in $$source)) { - this["title"] = ""; - } - if (!("lastUpdated" in $$source)) { - this["lastUpdated"] = null; - } - if (!("createdAt" in $$source)) { - this["createdAt"] = null; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new DocumentMeta instance from a string or object. - */ - static createFrom($$source: any = {}): DocumentMeta { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new DocumentMeta($$parsedSource as Partial); - } -} - -/** - * EditingConfig 编辑设置配置 - */ -export class EditingConfig { - /** - * 字体设置 - * 字体大小 - */ - "fontSize": number; - - /** - * 字体族 - */ - "fontFamily": string; - - /** - * 字体粗细 - */ - "fontWeight": string; - - /** - * 行高 - */ - "lineHeight": number; - - /** - * Tab设置 - * 是否启用Tab缩进 - */ - "enableTabIndent": boolean; - - /** - * Tab大小 - */ - "tabSize": number; - - /** - * Tab类型(空格或Tab) - */ - "tabType": TabType; - - /** - * 保存选项 - * 自动保存延迟(毫秒) - */ - "autoSaveDelay": number; - - /** Creates a new EditingConfig instance. */ - constructor($$source: Partial = {}) { - if (!("fontSize" in $$source)) { - this["fontSize"] = 0; - } - if (!("fontFamily" in $$source)) { - this["fontFamily"] = ""; - } - if (!("fontWeight" in $$source)) { - this["fontWeight"] = ""; - } - if (!("lineHeight" in $$source)) { - this["lineHeight"] = 0; - } - if (!("enableTabIndent" in $$source)) { - this["enableTabIndent"] = false; - } - if (!("tabSize" in $$source)) { - this["tabSize"] = 0; - } - if (!("tabType" in $$source)) { - this["tabType"] = ("" as TabType); - } - if (!("autoSaveDelay" in $$source)) { - this["autoSaveDelay"] = 0; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new EditingConfig instance from a string or object. - */ - static createFrom($$source: any = {}): EditingConfig { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new EditingConfig($$parsedSource as Partial); - } -} - -/** - * GeneralConfig 通用设置配置 - */ -export class GeneralConfig { - /** - * 窗口是否置顶 - */ - "alwaysOnTop": boolean; - - /** - * 数据存储路径 - */ - "dataPath": string; - - /** - * 全局热键设置 - * 是否启用全局热键 - */ - "enableGlobalHotkey": boolean; - - /** - * 全局热键组合 - */ - "globalHotkey": HotkeyCombo; - - /** Creates a new GeneralConfig instance. */ - constructor($$source: Partial = {}) { - if (!("alwaysOnTop" in $$source)) { - this["alwaysOnTop"] = false; - } - if (!("dataPath" in $$source)) { - this["dataPath"] = ""; - } - if (!("enableGlobalHotkey" in $$source)) { - this["enableGlobalHotkey"] = false; - } - if (!("globalHotkey" in $$source)) { - this["globalHotkey"] = (new HotkeyCombo()); - } - - Object.assign(this, $$source); - } - - /** - * Creates a new GeneralConfig instance from a string or object. - */ - static createFrom($$source: any = {}): GeneralConfig { - const $$createField3_0 = $$createType7; - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - if ("globalHotkey" in $$parsedSource) { - $$parsedSource["globalHotkey"] = $$createField3_0($$parsedSource["globalHotkey"]); - } - return new GeneralConfig($$parsedSource as Partial); - } -} - -/** - * HotkeyCombo 热键组合定义 - */ -export class HotkeyCombo { - /** - * Ctrl键 - */ - "ctrl": boolean; - - /** - * Shift键 - */ - "shift": boolean; - - /** - * Alt键 - */ - "alt": boolean; - - /** - * Win键 - */ - "win": boolean; - - /** - * 主键(如 'X', 'F1' 等) - */ - "key": string; - - /** Creates a new HotkeyCombo instance. */ - constructor($$source: Partial = {}) { - if (!("ctrl" in $$source)) { - this["ctrl"] = false; - } - if (!("shift" in $$source)) { - this["shift"] = false; - } - if (!("alt" in $$source)) { - this["alt"] = false; - } - if (!("win" in $$source)) { - this["win"] = false; - } - if (!("key" in $$source)) { - this["key"] = ""; - } - - Object.assign(this, $$source); - } - - /** - * Creates a new HotkeyCombo instance from a string or object. - */ - static createFrom($$source: any = {}): HotkeyCombo { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new HotkeyCombo($$parsedSource as Partial); - } -} - -/** - * KeyBindingsConfig 快捷键设置配置 - */ -export class KeyBindingsConfig { - - /** Creates a new KeyBindingsConfig instance. */ - constructor($$source: Partial = {}) { - - Object.assign(this, $$source); - } - - /** - * Creates a new KeyBindingsConfig instance from a string or object. - */ - static createFrom($$source: any = {}): KeyBindingsConfig { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new KeyBindingsConfig($$parsedSource as Partial); - } -} - -/** - * LanguageType 语言类型定义 - */ -export enum LanguageType { - /** - * The Go zero value for the underlying type of the enum. - */ - $zero = "", - - /** - * LangZhCN 中文简体 - */ - LangZhCN = "zh-CN", - - /** - * LangEnUS 英文-美国 - */ - LangEnUS = "en-US", -}; - -/** - * TabType 定义了制表符类型 - */ -export enum TabType { - /** - * The Go zero value for the underlying type of the enum. - */ - $zero = "", - - /** - * TabTypeSpaces 使用空格作为制表符 - */ - TabTypeSpaces = "spaces", - - /** - * TabTypeTab 使用Tab作为制表符 - */ - TabTypeTab = "tab", -}; - -/** - * ThemeType 主题类型定义 - */ -export enum ThemeType { - /** - * The Go zero value for the underlying type of the enum. - */ - $zero = "", - - /** - * ThemeDefaultDark 默认深色主题 - */ - ThemeDefaultDark = "default-dark", - - /** - * ThemeDracula Dracula主题 - */ - ThemeDracula = "dracula", - - /** - * ThemeAura Aura主题 - */ - ThemeAura = "aura", - - /** - * ThemeGithubDark GitHub深色主题 - */ - ThemeGithubDark = "github-dark", - - /** - * ThemeGithubLight GitHub浅色主题 - */ - ThemeGithubLight = "github-light", - - /** - * ThemeMaterialDark Material深色主题 - */ - ThemeMaterialDark = "material-dark", - - /** - * ThemeMaterialLight Material浅色主题 - */ - ThemeMaterialLight = "material-light", - - /** - * ThemeSolarizedDark Solarized深色主题 - */ - ThemeSolarizedDark = "solarized-dark", - - /** - * ThemeSolarizedLight Solarized浅色主题 - */ - ThemeSolarizedLight = "solarized-light", - - /** - * ThemeTokyoNight Tokyo Night主题 - */ - ThemeTokyoNight = "tokyo-night", - - /** - * ThemeTokyoNightStorm Tokyo Night Storm主题 - */ - ThemeTokyoNightStorm = "tokyo-night-storm", - - /** - * ThemeTokyoNightDay Tokyo Night Day主题 - */ - ThemeTokyoNightDay = "tokyo-night-day", -}; - -/** - * UpdatesConfig 更新设置配置 - */ -export class UpdatesConfig { - - /** Creates a new UpdatesConfig instance. */ - constructor($$source: Partial = {}) { - - Object.assign(this, $$source); - } - - /** - * Creates a new UpdatesConfig instance from a string or object. - */ - static createFrom($$source: any = {}): UpdatesConfig { - let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source; - return new UpdatesConfig($$parsedSource as Partial); - } -} - -// Private type creation functions -const $$createType0 = GeneralConfig.createFrom; -const $$createType1 = EditingConfig.createFrom; -const $$createType2 = AppearanceConfig.createFrom; -const $$createType3 = KeyBindingsConfig.createFrom; -const $$createType4 = UpdatesConfig.createFrom; -const $$createType5 = ConfigMetadata.createFrom; -const $$createType6 = DocumentMeta.createFrom; -const $$createType7 = HotkeyCombo.createFrom; diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 915d0ac..0cd374c 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -76,6 +76,7 @@ export default { enableGlobalHotkey: 'Enable Global Hotkeys', window: 'Window/Application', showInSystemTray: 'Show in System Tray', + enableSystemTray: 'Enable System Tray', alwaysOnTop: 'Always on Top', dataStorage: 'Data Storage', dataPath: 'Data Storage Path', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index d9c7ccb..7c40de1 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -76,6 +76,7 @@ export default { enableGlobalHotkey: '启用全局热键', window: '窗口/应用程序', showInSystemTray: '在系统托盘中显示', + enableSystemTray: '启用系统托盘', alwaysOnTop: '窗口始终置顶', dataStorage: '数据存储', dataPath: '数据存储路径', diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index fb147d5..aff54d0 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -49,6 +49,7 @@ type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight'; const GENERAL_CONFIG_KEY_MAP: GeneralConfigKeyMap = { alwaysOnTop: 'general.always_on_top', dataPath: 'general.data_path', + enableSystemTray: 'general.enable_system_tray', enableGlobalHotkey: 'general.enable_global_hotkey', globalHotkey: 'general.global_hotkey' } as const; @@ -116,6 +117,7 @@ const DEFAULT_CONFIG: AppConfig = { general: { alwaysOnTop: false, dataPath: '', + enableSystemTray: true, enableGlobalHotkey: false, globalHotkey: { ctrl: false, @@ -406,6 +408,9 @@ export const useConfigStore = defineStore('config', () => { // 热键配置相关方法 setEnableGlobalHotkey: (value: boolean) => safeCall(() => updateGeneralConfig('enableGlobalHotkey', value), 'config.saveFailed', 'config.saveSuccess'), - setGlobalHotkey: (hotkey: any) => safeCall(() => updateGeneralConfig('globalHotkey', hotkey), 'config.saveFailed', 'config.saveSuccess') + setGlobalHotkey: (hotkey: any) => safeCall(() => updateGeneralConfig('globalHotkey', hotkey), 'config.saveFailed', 'config.saveSuccess'), + + // 系统托盘配置相关方法 + setEnableSystemTray: (value: boolean) => safeCall(() => updateGeneralConfig('enableSystemTray', value), 'config.saveFailed', 'config.saveSuccess') }; }); \ No newline at end of file diff --git a/frontend/src/views/settings/pages/GeneralPage.vue b/frontend/src/views/settings/pages/GeneralPage.vue index ead8220..119c740 100644 --- a/frontend/src/views/settings/pages/GeneralPage.vue +++ b/frontend/src/views/settings/pages/GeneralPage.vue @@ -187,6 +187,12 @@ const alwaysOnTop = computed({ } }); +// 计算属性 - 启用系统托盘 +const enableSystemTray = computed({ + get: () => configStore.config.general.enableSystemTray, + set: (value: boolean) => configStore.setEnableSystemTray(value) +}); + // 修饰键配置 - 只读计算属性 const modifierKeys = computed(() => ({ ctrl: configStore.config.general.globalHotkey.ctrl, @@ -338,6 +344,9 @@ onUnmounted(() => { + + + diff --git a/internal/events/tray_events.go b/internal/events/tray_events.go index 85514b6..57b376a 100644 --- a/internal/events/tray_events.go +++ b/internal/events/tray_events.go @@ -2,13 +2,14 @@ package events import ( "time" + "voidraft/internal/services" "github.com/wailsapp/wails/v3/pkg/application" wailsevents "github.com/wailsapp/wails/v3/pkg/events" ) // RegisterTrayEvents 注册与系统托盘相关的所有事件 -func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow) { +func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow, trayService *services.TrayService) { // 不附加窗口到系统托盘,避免失去焦点自动缩小 // systray.AttachWindow(mainWindow) @@ -17,31 +18,25 @@ func RegisterTrayEvents(app *application.App, systray *application.SystemTray, m // 设置点击托盘图标显示主窗口 systray.OnClick(func() { - mainWindow.Show() - mainWindow.Restore() - mainWindow.Focus() - // 通知前端窗口已显示 - app.EmitEvent("window:shown", nil) + trayService.ShowWindow() }) - // 处理窗口关闭事件 - 隐藏到托盘 + // 处理窗口关闭事件 - 根据配置决定是隐藏到托盘还是直接退出 mainWindow.RegisterHook(wailsevents.Common.WindowClosing, func(event *application.WindowEvent) { // 取消默认关闭行为 event.Cancel() - // 隐藏窗口到托盘 - mainWindow.Hide() - // 通知前端窗口已隐藏 - app.EmitEvent("window:hidden", nil) + // 使用托盘服务处理关闭事件 + trayService.HandleWindowClose() }) - // 处理窗口最小化事件 - 也隐藏到托盘 + // 处理窗口最小化事件 - 根据配置决定是隐藏到托盘还是正常最小化 mainWindow.RegisterHook(wailsevents.Common.WindowMinimise, func(event *application.WindowEvent) { - // 取消默认最小化行为 - event.Cancel() - // 隐藏窗口到托盘 - mainWindow.Hide() - // 通知前端窗口已隐藏 - app.EmitEvent("window:hidden", nil) + if trayService.ShouldMinimizeToTray() { + // 取消默认最小化行为,隐藏到托盘 + event.Cancel() + trayService.HandleWindowMinimize() + } + // 如果不启用托盘,允许正常最小化(不取消事件) }) } diff --git a/internal/models/config.go b/internal/models/config.go index 2d67387..578f8ed 100644 --- a/internal/models/config.go +++ b/internal/models/config.go @@ -58,8 +58,9 @@ const ( // GeneralConfig 通用设置配置 type GeneralConfig struct { - AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶 - DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径 + AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶 + DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径 + EnableSystemTray bool `json:"enableSystemTray" yaml:"enable_system_tray" mapstructure:"enable_system_tray"` // 是否启用系统托盘 // 全局热键设置 EnableGlobalHotkey bool `json:"enableGlobalHotkey" yaml:"enable_global_hotkey" mapstructure:"enable_global_hotkey"` // 是否启用全局热键 @@ -139,6 +140,7 @@ func NewDefaultAppConfig() *AppConfig { General: GeneralConfig{ AlwaysOnTop: false, DataPath: dataDir, + EnableSystemTray: true, // 默认启用系统托盘 EnableGlobalHotkey: false, GlobalHotkey: HotkeyCombo{ Ctrl: false, diff --git a/internal/services/config_service.go b/internal/services/config_service.go index ccabbbf..fafa2e0 100644 --- a/internal/services/config_service.go +++ b/internal/services/config_service.go @@ -106,6 +106,7 @@ func setDefaults(v *viper.Viper) { // 通用设置默认值 v.SetDefault("general.always_on_top", defaultConfig.General.AlwaysOnTop) v.SetDefault("general.data_path", defaultConfig.General.DataPath) + v.SetDefault("general.enable_system_tray", defaultConfig.General.EnableSystemTray) v.SetDefault("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey) v.SetDefault("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl) v.SetDefault("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift) @@ -240,6 +241,7 @@ func (cs *ConfigService) ResetConfig() { // 通用设置 - 批量设置到viper中 cs.viper.Set("general.always_on_top", defaultConfig.General.AlwaysOnTop) cs.viper.Set("general.data_path", defaultConfig.General.DataPath) + cs.viper.Set("general.enable_system_tray", defaultConfig.General.EnableSystemTray) cs.viper.Set("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey) cs.viper.Set("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl) cs.viper.Set("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift) diff --git a/internal/services/service_manager.go b/internal/services/service_manager.go index 4bba02f..2a5dd4a 100644 --- a/internal/services/service_manager.go +++ b/internal/services/service_manager.go @@ -120,3 +120,13 @@ func (sm *ServiceManager) GetHotkeyService() *HotkeyService { func (sm *ServiceManager) GetDialogService() *DialogService { return sm.dialogService } + +// GetLogger 获取日志服务实例 +func (sm *ServiceManager) GetLogger() *log.LoggerService { + return sm.logger +} + +// GetConfigService 获取配置服务实例 +func (sm *ServiceManager) GetConfigService() *ConfigService { + return sm.configService +} diff --git a/internal/services/tray_service.go b/internal/services/tray_service.go new file mode 100644 index 0000000..087c7b7 --- /dev/null +++ b/internal/services/tray_service.go @@ -0,0 +1,79 @@ +package services + +import ( + "github.com/wailsapp/wails/v3/pkg/application" + "github.com/wailsapp/wails/v3/pkg/services/log" +) + +// TrayService 系统托盘服务 +type TrayService struct { + logger *log.LoggerService + configService *ConfigService + app *application.App + mainWindow *application.WebviewWindow +} + +// NewTrayService 创建新的系统托盘服务实例 +func NewTrayService(logger *log.LoggerService, configService *ConfigService) *TrayService { + return &TrayService{ + logger: logger, + configService: configService, + } +} + +// SetAppReferences 设置应用引用 +func (ts *TrayService) SetAppReferences(app *application.App, mainWindow *application.WebviewWindow) { + ts.app = app + ts.mainWindow = mainWindow +} + +// ShouldMinimizeToTray 检查是否应该最小化到托盘 +func (ts *TrayService) ShouldMinimizeToTray() bool { + config, err := ts.configService.GetConfig() + if err != nil { + ts.logger.Error("TrayService: Failed to get config", "error", err) + return true // 默认行为:隐藏到托盘 + } + + return config.General.EnableSystemTray +} + +// HandleWindowClose 处理窗口关闭事件 +func (ts *TrayService) HandleWindowClose() { + if ts.ShouldMinimizeToTray() { + // 隐藏到托盘 + ts.mainWindow.Hide() + ts.app.EmitEvent("window:hidden", nil) + ts.logger.Info("TrayService: Window hidden to system tray") + } else { + // 直接退出应用 + ts.app.Quit() + ts.logger.Info("TrayService: Application quit") + } +} + +// HandleWindowMinimize 处理窗口最小化事件 +func (ts *TrayService) HandleWindowMinimize() { + if ts.ShouldMinimizeToTray() { + // 隐藏到托盘 + ts.mainWindow.Hide() + ts.app.EmitEvent("window:hidden", nil) + ts.logger.Info("TrayService: Window minimized to system tray") + } else { + // 允许正常最小化(不处理,让系统处理) + ts.logger.Info("TrayService: Window minimized normally") + } +} + +// ShowWindow 显示主窗口 +func (ts *TrayService) ShowWindow() { + if ts.mainWindow != nil { + ts.mainWindow.Show() + ts.mainWindow.Restore() + ts.mainWindow.Focus() + if ts.app != nil { + ts.app.EmitEvent("window:shown", nil) + } + ts.logger.Info("TrayService: Window shown from system tray") + } +} diff --git a/internal/systray/systray.go b/internal/systray/systray.go index 7fae04f..6d448e7 100644 --- a/internal/systray/systray.go +++ b/internal/systray/systray.go @@ -4,10 +4,11 @@ import ( "embed" "github.com/wailsapp/wails/v3/pkg/application" "voidraft/internal/events" + "voidraft/internal/services" ) // SetupSystemTray 设置系统托盘及其功能 -func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS) { +func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS, trayService *services.TrayService) { // 创建系统托盘 systray := app.NewSystemTray() @@ -27,5 +28,5 @@ func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow systray.SetMenu(menu) // 注册托盘相关事件 - events.RegisterTrayEvents(app, systray, mainWindow) + events.RegisterTrayEvents(app, systray, mainWindow, trayService) } diff --git a/main.go b/main.go index 5b2eb8a..d442759 100644 --- a/main.go +++ b/main.go @@ -84,13 +84,20 @@ func main() { Backdrop: application.MacBackdropTranslucent, TitleBar: application.MacTitleBarHiddenInset, }, + Windows: application.WindowsWindow{ + Theme: application.SystemDefault, + }, BackgroundColour: application.NewRGB(27, 38, 54), URL: "/#/", }) mainWindow.Center() + // 创建托盘服务 + trayService := services.NewTrayService(serviceManager.GetLogger(), serviceManager.GetConfigService()) + trayService.SetAppReferences(app, mainWindow) + // 设置系统托盘 - systray.SetupSystemTray(app, mainWindow, assets) + systray.SetupSystemTray(app, mainWindow, assets, trayService) // 初始化热键服务 hotkeyService := serviceManager.GetHotkeyService()