返回 DeepSeek-Reasonix
tray.go
根目录 / desktop / tray.go
1 package main
2
3 import (
4 "sync"
5
6 "fyne.io/systray"
7 )
8
9 type desktopTray struct {
10 end func()
11 openItem *systray.MenuItem
12 quitItem *systray.MenuItem
13 once sync.Once
14 ready chan struct{}
15 readyOnce sync.Once
16 }
17
18 func newDesktopTray() *desktopTray {
19 return &desktopTray{ready: make(chan struct{})}
20 }
21
22 func (t *desktopTray) markReady() {
23 t.readyOnce.Do(func() {
24 close(t.ready)
25 })
26 }
27
28 func (a *App) startTray() bool {
29 if !traySupported() {
30 return false
31 }
32 a.mu.Lock()
33 if a.tray != nil {
34 a.mu.Unlock()
35 return true
36 }
37 t := newDesktopTray()
38 a.tray = t
39 a.mu.Unlock()
40
41 end := startDesktopTray(func() {
42 systray.SetIcon(trayIconBytes)
43 systray.SetTitle("Reasonix")
44 systray.SetTooltip("Reasonix")
45 // Run off the systray Win32 message loop: SetOnTapped fires inside wndProc,
46 // so a blocking showFromTray (a wedged webview after sleep freezes
47 // runtime.WindowShow) would stall the whole tray's message pump (#3834). The
48 // menu items below are already decoupled via goroutines for the same reason.
49 systray.SetOnTapped(func() { a.goSafe("showFromTray", a.showFromTray) })
50 // Keep secondary/right-click on systray's native menu path.
51 systray.SetOnSecondaryTapped(nil)
52
53 labels := trayMenuLabels(a.trayLocale())
54 openItem := systray.AddMenuItem(labels.openTitle, labels.openTooltip)
55 quitItem := systray.AddMenuItem(labels.quitTitle, labels.quitTooltip)
56
57 // Publish the menu items under a.mu: this callback runs on the systray
58 // goroutine while bound settings calls (updateTrayLocale) read them.
59 a.mu.Lock()
60 t.openItem = openItem
61 t.quitItem = quitItem
62 a.trayReady = true
63 a.mu.Unlock()
64 t.markReady()
65
66 a.goSafe("trayOpenLoop", func() {
67 for range openItem.ClickedCh {
68 a.showFromTray()
69 }
70 })
71 a.goSafe("trayQuitLoop", func() {
72 for range quitItem.ClickedCh {
73 a.quitFromTray()
74 }
75 })
76 }, func() {
77 a.mu.Lock()
78 if a.tray == t {
79 a.trayReady = false
80 a.tray = nil
81 }
82 a.mu.Unlock()
83 })
84 a.mu.Lock()
85 t.end = end
86 a.mu.Unlock()
87 return true
88 }
89
90 func (a *App) stopTray() {
91 a.mu.RLock()
92 t := a.tray
93 var end func()
94 if t != nil {
95 end = t.end
96 }
97 a.mu.RUnlock()
98 if t == nil || end == nil {
99 return
100 }
101 t.once.Do(end)
102 }
103
104 func (a *App) updateTrayLocale(locale string) {
105 a.mu.RLock()
106 t := a.tray
107 var openItem, quitItem *systray.MenuItem
108 if t != nil {
109 openItem = t.openItem
110 quitItem = t.quitItem
111 }
112 a.mu.RUnlock()
113 if openItem == nil || quitItem == nil {
114 return
115 }
116 labels := trayMenuLabels(locale)
117 openItem.SetTitle(labels.openTitle)
118 openItem.SetTooltip(labels.openTooltip)
119 quitItem.SetTitle(labels.quitTitle)
120 quitItem.SetTooltip(labels.quitTooltip)
121 }
122
123 func (a *App) trayLocale() string {
124 cfg, _, err := a.loadDesktopUserConfigForView()
125 if err != nil {
126 return ""
127 }
128 return cfg.DesktopLanguage()
129 }
130
131 func (a *App) showFromTray() {
132 a.showMainWindowFrom("tray")
133 }
134
135 func (a *App) quitFromTray() {
136 a.quitApp()
137 }
138
139 type trayLabels struct {
140 openTitle string
141 openTooltip string
142 quitTitle string
143 quitTooltip string
144 }
145
146 func trayMenuLabels(locale string) trayLabels {
147 if locale == "zh" {
148 return trayLabels{
149 openTitle: "打开",
150 openTooltip: "打开 Reasonix 窗口",
151 quitTitle: "退出",
152 quitTooltip: "退出 Reasonix",
153 }
154 }
155 return trayLabels{
156 openTitle: "Open",
157 openTooltip: "Open the Reasonix window",
158 quitTitle: "Quit",
159 quitTooltip: "Quit Reasonix",
160 }
161 }
162
162 lines GO