GUI packages, examples, and documentation for building desktop applications with Go and web technologies. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
5.5 KiB
Go
200 lines
5.5 KiB
Go
package display
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"runtime"
|
|
|
|
"github.com/wailsapp/wails/v3/pkg/application"
|
|
)
|
|
|
|
//go:embed assets/apptray.png
|
|
var assets embed.FS
|
|
|
|
// activeTray holds the reference to the system tray for management.
|
|
var activeTray *application.SystemTray
|
|
|
|
// systemTray configures and creates the system tray icon and menu.
|
|
func (s *Service) systemTray() {
|
|
|
|
systray := s.app.SystemTray().New()
|
|
activeTray = systray
|
|
systray.SetTooltip("Core")
|
|
systray.SetLabel("Core")
|
|
|
|
// Load and set tray icon
|
|
appTrayIcon, err := assets.ReadFile("assets/apptray.png")
|
|
if err == nil {
|
|
if runtime.GOOS == "darwin" {
|
|
systray.SetTemplateIcon(appTrayIcon)
|
|
} else {
|
|
// Support for light/dark mode icons
|
|
systray.SetDarkModeIcon(appTrayIcon)
|
|
systray.SetIcon(appTrayIcon)
|
|
}
|
|
}
|
|
// Create a hidden window for the system tray menu to interact with
|
|
trayWindow, _ := s.NewWithStruct(&Window{
|
|
Name: "system-tray",
|
|
Title: "System Tray Status",
|
|
URL: "/system-tray",
|
|
Width: 400,
|
|
Frameless: true,
|
|
Hidden: true,
|
|
})
|
|
systray.AttachWindow(trayWindow).WindowOffset(5)
|
|
|
|
// --- Build Tray Menu ---
|
|
trayMenu := s.app.Menu().New()
|
|
trayMenu.Add("Open Desktop").OnClick(func(ctx *application.Context) {
|
|
for _, window := range s.app.Window().GetAll() {
|
|
window.Show()
|
|
}
|
|
})
|
|
trayMenu.Add("Close Desktop").OnClick(func(ctx *application.Context) {
|
|
for _, window := range s.app.Window().GetAll() {
|
|
window.Hide()
|
|
}
|
|
})
|
|
|
|
trayMenu.Add("Environment Info").OnClick(func(ctx *application.Context) {
|
|
s.ShowEnvironmentDialog()
|
|
})
|
|
// Add brand-specific menu items
|
|
//switch d.brand {
|
|
//case AdminHub:
|
|
// trayMenu.Add("Manage Workspace").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
//case ServerHub:
|
|
// trayMenu.Add("Server Control").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
//case GatewayHub:
|
|
// trayMenu.Add("Routing Table").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
//case DeveloperHub:
|
|
// trayMenu.Add("Debug Console").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
//case ClientHub:
|
|
// trayMenu.Add("Connect").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
// trayMenu.Add("Disconnect").OnClick(func(ctx *application.Context) { /* TODO */ })
|
|
//}
|
|
|
|
trayMenu.AddSeparator()
|
|
trayMenu.Add("Quit").OnClick(func(ctx *application.Context) {
|
|
s.app.Quit()
|
|
})
|
|
|
|
systray.SetMenu(trayMenu)
|
|
}
|
|
|
|
// SetTrayIcon sets the system tray icon from raw PNG data.
|
|
func (s *Service) SetTrayIcon(iconData []byte) error {
|
|
if activeTray == nil {
|
|
return fmt.Errorf("system tray not initialized")
|
|
}
|
|
if runtime.GOOS == "darwin" {
|
|
activeTray.SetTemplateIcon(iconData)
|
|
} else {
|
|
activeTray.SetIcon(iconData)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetTrayTooltip sets the system tray tooltip text.
|
|
func (s *Service) SetTrayTooltip(tooltip string) error {
|
|
if activeTray == nil {
|
|
return fmt.Errorf("system tray not initialized")
|
|
}
|
|
activeTray.SetTooltip(tooltip)
|
|
return nil
|
|
}
|
|
|
|
// SetTrayLabel sets the system tray label text.
|
|
func (s *Service) SetTrayLabel(label string) error {
|
|
if activeTray == nil {
|
|
return fmt.Errorf("system tray not initialized")
|
|
}
|
|
activeTray.SetLabel(label)
|
|
return nil
|
|
}
|
|
|
|
// TrayMenuItem represents a menu item for the system tray.
|
|
type TrayMenuItem struct {
|
|
Label string `json:"label"`
|
|
Type string `json:"type,omitempty"` // "normal", "separator", "checkbox", "radio"
|
|
Checked bool `json:"checked,omitempty"` // for checkbox/radio items
|
|
Disabled bool `json:"disabled,omitempty"`
|
|
Tooltip string `json:"tooltip,omitempty"`
|
|
Submenu []TrayMenuItem `json:"submenu,omitempty"`
|
|
ActionID string `json:"actionId,omitempty"` // ID for callback
|
|
}
|
|
|
|
// trayMenuCallbacks stores callbacks for tray menu items.
|
|
var trayMenuCallbacks = make(map[string]func())
|
|
|
|
// SetTrayMenu sets the system tray menu from a list of menu items.
|
|
func (s *Service) SetTrayMenu(items []TrayMenuItem) error {
|
|
if activeTray == nil {
|
|
return fmt.Errorf("system tray not initialized")
|
|
}
|
|
|
|
menu := s.app.Menu().New()
|
|
s.buildTrayMenu(menu, items)
|
|
activeTray.SetMenu(menu)
|
|
return nil
|
|
}
|
|
|
|
// buildTrayMenu recursively builds a menu from TrayMenuItem items.
|
|
func (s *Service) buildTrayMenu(menu *application.Menu, items []TrayMenuItem) {
|
|
for _, item := range items {
|
|
switch item.Type {
|
|
case "separator":
|
|
menu.AddSeparator()
|
|
case "checkbox":
|
|
menuItem := menu.AddCheckbox(item.Label, item.Checked)
|
|
if item.Disabled {
|
|
menuItem.SetEnabled(false)
|
|
}
|
|
if item.ActionID != "" {
|
|
actionID := item.ActionID
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
if cb, ok := trayMenuCallbacks[actionID]; ok {
|
|
cb()
|
|
}
|
|
})
|
|
}
|
|
default:
|
|
if len(item.Submenu) > 0 {
|
|
submenu := menu.AddSubmenu(item.Label)
|
|
s.buildTrayMenu(submenu, item.Submenu)
|
|
} else {
|
|
menuItem := menu.Add(item.Label)
|
|
if item.Disabled {
|
|
menuItem.SetEnabled(false)
|
|
}
|
|
if item.Tooltip != "" {
|
|
menuItem.SetTooltip(item.Tooltip)
|
|
}
|
|
if item.ActionID != "" {
|
|
actionID := item.ActionID
|
|
menuItem.OnClick(func(ctx *application.Context) {
|
|
if cb, ok := trayMenuCallbacks[actionID]; ok {
|
|
cb()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// RegisterTrayMenuCallback registers a callback for a tray menu action ID.
|
|
func (s *Service) RegisterTrayMenuCallback(actionID string, callback func()) {
|
|
trayMenuCallbacks[actionID] = callback
|
|
}
|
|
|
|
// GetTrayInfo returns information about the current tray state.
|
|
func (s *Service) GetTrayInfo() map[string]any {
|
|
if activeTray == nil {
|
|
return map[string]any{"active": false}
|
|
}
|
|
return map[string]any{
|
|
"active": true,
|
|
}
|
|
}
|