Files
MyGoNavi/internal/nativewindow/dock_menu.go
Syngnat 5c3535c85d feat(native-window): 支持 macOS Dock 管理独立窗口
- 在主程序 Dock 菜单动态列出并聚焦已就绪的独立窗口
- 在 Wails 启动前固定子进程为 Accessory,消除临时 Dock 图标闪现
- 按多显示器可见区域修正越界窗口并回写实际坐标
- 将自定义 AI 面板快捷键同步到独立窗口并转交主窗口处理
- 补齐 Go、Objective-C、前端及 Wails 绑定回归测试
2026-07-21 18:52:54 +08:00

129 lines
2.9 KiB
Go

package nativewindow
import (
"encoding/json"
"sort"
"strings"
"sync"
)
type dockMenuWindow struct {
ID string `json:"id"`
Title string `json:"title"`
PID int `json:"pid,omitempty"`
}
var detachedDockMenuManager struct {
sync.RWMutex
manager *Manager
}
var detachedDockMenuPublisher struct {
sync.Mutex
revision uint64
}
func registerDetachedDockMenuManager(manager *Manager) {
if manager == nil || !supportsDetachedDockMenu() {
return
}
detachedDockMenuManager.Lock()
detachedDockMenuManager.manager = manager
detachedDockMenuManager.Unlock()
installDetachedDockMenu()
publishDetachedDockMenuSnapshot(manager)
}
func unregisterDetachedDockMenuManager(manager *Manager) {
if !supportsDetachedDockMenu() {
return
}
detachedDockMenuManager.Lock()
if detachedDockMenuManager.manager == manager {
detachedDockMenuManager.manager = nil
detachedDockMenuManager.Unlock()
publishDetachedDockMenuSnapshot(nil)
return
}
detachedDockMenuManager.Unlock()
}
func currentDetachedDockMenuManager() *Manager {
detachedDockMenuManager.RLock()
manager := detachedDockMenuManager.manager
detachedDockMenuManager.RUnlock()
return manager
}
func publishDetachedDockMenuSnapshot(manager *Manager) {
if !supportsDetachedDockMenu() {
return
}
detachedDockMenuPublisher.Lock()
defer detachedDockMenuPublisher.Unlock()
var windows []WindowInfo
if manager == nil {
if currentDetachedDockMenuManager() != nil {
return
}
} else {
if currentDetachedDockMenuManager() != manager {
return
}
manager.mu.RLock()
if manager.started && !manager.closing {
windows = make([]WindowInfo, 0, len(manager.windows))
for _, entry := range manager.windows {
windows = append(windows, entry.info)
}
}
manager.mu.RUnlock()
}
payload, err := json.Marshal(buildDockMenuSnapshot(windows))
if err != nil {
return
}
detachedDockMenuPublisher.revision++
publishDetachedDockMenuSnapshotToPlatform(payload, detachedDockMenuPublisher.revision)
}
func buildDockMenuSnapshot(windows []WindowInfo) []dockMenuWindow {
current := make([]WindowInfo, 0, len(windows))
for _, window := range windows {
window.ID = strings.TrimSpace(window.ID)
if window.ID == "" || !window.Ready || window.CloseSent {
continue
}
current = append(current, window)
}
sort.Slice(current, func(i int, j int) bool {
if current[i].OpenedAt != current[j].OpenedAt {
return current[i].OpenedAt < current[j].OpenedAt
}
return current[i].ID < current[j].ID
})
result := make([]dockMenuWindow, 0, len(current))
for _, window := range current {
title := strings.TrimSpace(window.Title)
if title == "" {
title = "GoNavi"
}
result = append(result, dockMenuWindow{
ID: window.ID,
Title: title,
PID: window.PID,
})
}
return result
}
func focusDetachedDockMenuWindow(id string) {
manager := currentDetachedDockMenuManager()
if manager == nil {
return
}
manager.Focus(strings.TrimSpace(id))
}