--- name: systray description: Expert guidance for using the github.com/getlantern/systray package in Go to create cross-platform system tray applications with icons, menus, and notifications. Use when working with: (1) Creating system tray applications, (2) Adding menu items to system tray, (3) Handling tray icon clicks, (4) Managing menu item state (enabled/disabled, checked/unchecked), (5) Setting tray icons and tooltips, or any other system tray functionality on macOS, Windows, or Linux. --- # Systray - Cross-Platform System Tray for Go This skill provides comprehensive guidance for using the `github.com/getlantern/systray` package to create system tray applications in Go. ## Reference Documentation **API Reference**: [./references/api-reference.txt](./references/api-reference.txt) - Complete package documentation with all functions, types, and methods ## Installation ```bash go get github.com/getlantern/systray
Core Concepts
1. Application Lifecycle
The systray package provides two main functions for initializing the application:
Run (blocking):
import "github.com/getlantern/systray"
func main() {
systray.Run(onReady, onExit)
}
func onReady() {
// Set up tray icon and menu
systray.SetIcon(icon.Data)
systray.SetTitle("My App")
systray.SetTooltip("My Application Tooltip")
}
func onExit() {
// Cleanup before exit
}
Register (non-blocking):
func main() {
systray.Register(onReady, onExit)
// Show other UI (e.g., webview)
// Run event loop elsewhere
}
Key differences:
- •
Run(): Blocks untilsystray.Quit()is called - use for tray-only apps - •
Register(): Returns immediately - use when showing other UI elements alongside the tray - •On macOS pre-Catalina,
Register()behaves identically toRun()
2. Setting Tray Icon
Standard icon:
systray.SetIcon(iconBytes)
Template icon (macOS-specific with fallback):
// On macOS: uses template icon (adapts to menu bar theme) // On Windows/Linux: falls back to regular icon systray.SetTemplateIcon(templateIconBytes, regularIconBytes)
Icon format requirements:
- •Windows:
.ico - •macOS/Linux:
.ico,.jpg, or.png
3. Creating Menu Items
Basic menu item:
mItem := systray.AddMenuItem("Menu Item", "Tooltip text")
Menu item with checkbox (Linux-specific):
// Linux: creates checkable menu item
// Windows/macOS: same as AddMenuItem (items are checkable by default)
mItemCheckbox := systray.AddMenuItemCheckbox("Checkable Item", "Tooltip", true)
Sub-menu items:
parentItem := systray.AddMenuItem("Parent", "Parent tooltip")
subItem := parentItem.AddSubMenuItem("Child", "Child tooltip")
// With checkbox on Linux
subItemCheck := parentItem.AddSubMenuItemCheckbox("Checkable Child", "Tooltip", false)
Separator:
systray.AddSeparator()
4. Handling Menu Item Clicks
Menu items have a ClickedCh channel that receives notifications when clicked:
mItem := systray.AddMenuItem("Click Me", "Tooltip")
// Handle clicks in a goroutine
go func() {
for range mItem.ClickedCh {
fmt.Println("Menu item clicked!")
// Perform action
}
}()
Pattern for quit button:
mQuit := systray.AddMenuItem("Quit", "Exit the application")
go func() {
<-mQuit.ClickedCh
systray.Quit()
}()
5. Menu Item State Management
Enable/Disable:
mItem.Disable() // Grays out the item mItem.Enable() // Makes it clickable again mItem.Disabled() // Returns bool status
Check/Uncheck:
mItem.Check() // Shows checkmark mItem.Uncheck() // Removes checkmark mItem.Checked() // Returns bool status
Show/Hide:
mItem.Hide() // Hides the menu item mItem.Show() // Shows the menu item again
Set title and tooltip:
mItem.SetTitle("New Title")
mItem.SetTooltip("New Tooltip")
Set icon (macOS/Windows only):
mItem.SetIcon(iconBytes) mItem.SetTemplateIcon(templateIconBytes, regularIconBytes)
6. Setting Tray Title and Tooltip
Title (macOS/Linux only):
systray.SetTitle("App Name")
Tooltip (macOS/Windows only):
systray.SetTooltip("Hover text for the tray icon")
7. Quitting the Application
systray.Quit() // Stops the event loop and triggers onExit callback
Common Patterns
Pattern 1: Simple Tray Application
package main
import (
"github.com/getlantern/systray"
"github.com/getlantern/systray/example/icon"
)
func main() {
systray.Run(onReady, onExit)
}
func onReady() {
systray.SetIcon(icon.Data)
systray.SetTitle("My App")
systray.SetTooltip("My Application")
mQuit := systray.AddMenuItem("Quit", "Quit the application")
go func() {
<-mQuit.ClickedCh
systray.Quit()
}()
}
func onExit() {
// Cleanup
}
Pattern 2: Menu with Multiple Actions
func onReady() {
systray.SetIcon(icon.Data)
systray.SetTooltip("Multi-action App")
mShow := systray.AddMenuItem("Show Window", "Display main window")
mSettings := systray.AddMenuItem("Settings", "Open settings")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Exit")
go func() {
for {
select {
case <-mShow.ClickedCh:
showMainWindow()
case <-mSettings.ClickedCh:
openSettings()
case <-mQuit.ClickedCh:
systray.Quit()
return
}
}
}()
}
Pattern 3: Checkable Menu Items
func onReady() {
systray.SetIcon(icon.Data)
mEnable := systray.AddMenuItemCheckbox("Enable Feature", "Toggle feature", false)
go func() {
for range mEnable.ClickedCh {
if mEnable.Checked() {
mEnable.Uncheck()
disableFeature()
} else {
mEnable.Check()
enableFeature()
}
}
}()
}
Pattern 4: Dynamic Menu Updates
func onReady() {
systray.SetIcon(icon.Data)
mStatus := systray.AddMenuItem("Status: Idle", "Current status")
mStatus.Disable() // Status display only
mStart := systray.AddMenuItem("Start", "Start process")
mStop := systray.AddMenuItem("Stop", "Stop process")
mStop.Disable() // Initially disabled
go func() {
for range mStart.ClickedCh {
mStatus.SetTitle("Status: Running")
mStart.Disable()
mStop.Enable()
startProcess()
}
}()
go func() {
for range mStop.ClickedCh {
mStatus.SetTitle("Status: Idle")
mStart.Enable()
mStop.Disable()
stopProcess()
}
}()
}
Pattern 5: Sub-menus for Organization
func onReady() {
systray.SetIcon(icon.Data)
// Main menu
mFile := systray.AddMenuItem("File", "File operations")
mEdit := systray.AddMenuItem("Edit", "Edit operations")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "Exit")
// File sub-menu
mNew := mFile.AddSubMenuItem("New", "Create new file")
mOpen := mFile.AddSubMenuItem("Open", "Open file")
mSave := mFile.AddSubMenuItem("Save", "Save file")
// Edit sub-menu
mCopy := mEdit.AddSubMenuItem("Copy", "Copy")
mPaste := mEdit.AddSubMenuItem("Paste", "Paste")
// Handle clicks
go func() {
for {
select {
case <-mNew.ClickedCh:
createNewFile()
case <-mOpen.ClickedCh:
openFile()
case <-mSave.ClickedCh:
saveFile()
case <-mCopy.ClickedCh:
copyToClipboard()
case <-mPaste.ClickedCh:
pasteFromClipboard()
case <-mQuit.ClickedCh:
systray.Quit()
return
}
}
}()
}
Pattern 6: Icon in Embedded Assets
import _ "embed"
//go:embed icon.png
var iconData []byte
func onReady() {
systray.SetIcon(iconData)
// ...
}
Platform-Specific Behavior
macOS
- •Template icons (via
SetTemplateIcon) adapt to light/dark menu bar themes - •Title visible in menu bar (via
SetTitle) - •Tooltip shows on hover (via
SetTooltip) - •Menu items checkable by default
- •
Register()behaves likeRun()on pre-Catalina versions
Windows
- •Only
.icoformat for icons - •Tooltip shows on hover (via
SetTooltip) - •Menu items checkable by default
- •Title not supported
Linux
- •Icons support
.ico,.jpg,.png - •Title visible (via
SetTitle) - •Tooltip not supported
- •Use
AddMenuItemCheckboxfor checkable items (not automatic)
Best Practices
1. Always Use Goroutines for Click Handlers
Menu item click handlers should run in separate goroutines to avoid blocking the UI:
mItem := systray.AddMenuItem("Action", "Perform action")
go func() {
for range mItem.ClickedCh {
performAction()
}
}()
2. Proper Cleanup
Use the onExit callback for cleanup:
func onExit() {
// Close connections
// Save state
// Release resources
}
3. Icon Formats
Provide appropriate icon formats for each platform:
- •Windows: require
.ico - •macOS/Linux: prefer
.pngfor clarity
4. Menu Organization
- •Use separators to group related items
- •Place quit/exit at the bottom
- •Use sub-menus for complex hierarchies
- •Keep menu depth minimal (max 2-3 levels)
5. State Management
Keep menu items in sync with application state:
func updateMenu(running bool) {
if running {
mStart.Disable()
mStop.Enable()
mStatus.SetTitle("Status: Running")
} else {
mStart.Enable()
mStop.Disable()
mStatus.SetTitle("Status: Stopped")
}
}
6. Error Handling
The systray package doesn't return errors from most functions. Handle errors in your click handlers:
go func() {
for range mItem.ClickedCh {
if err := performAction(); err != nil {
log.Printf("Action failed: %v", err)
// Update menu to reflect error state
mItem.SetTitle("Action Failed")
}
}
}()
Common Use Cases
Use Case 1: Background Service Monitor
Create a tray app that monitors a background service and allows starting/stopping:
func onReady() {
systray.SetIcon(iconStopped)
mStatus := systray.AddMenuItem("Service: Stopped", "")
mStatus.Disable()
mStart := systray.AddMenuItem("Start Service", "")
mStop := systray.AddMenuItem("Stop Service", "")
mStop.Disable()
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "")
// Handle service state changes
go monitorService(mStatus, mStart, mStop)
}
Use Case 2: Quick Actions Menu
Provide quick access to common actions:
func onReady() {
systray.SetIcon(icon.Data)
mAction1 := systray.AddMenuItem("Quick Action 1", "")
mAction2 := systray.AddMenuItem("Quick Action 2", "")
mAction3 := systray.AddMenuItem("Quick Action 3", "")
systray.AddSeparator()
mQuit := systray.AddMenuItem("Quit", "")
// Handle actions
go handleQuickActions(mAction1, mAction2, mAction3, mQuit)
}
Use Case 3: Settings Toggle
Simple on/off toggle for features:
func onReady() {
systray.SetIcon(icon.Data)
mFeature := systray.AddMenuItemCheckbox("Feature Enabled", "", loadSetting())
go func() {
for range mFeature.ClickedCh {
enabled := !mFeature.Checked()
if enabled {
mFeature.Check()
} else {
mFeature.Uncheck()
}
saveSetting(enabled)
applyFeature(enabled)
}
}()
}
Troubleshooting
Menu Items Not Responding
Ensure click handlers run in goroutines:
go func() {
for range mItem.ClickedCh {
// Handle click
}
}()
Icon Not Showing
- •Verify icon format is correct for the platform
- •Check that icon data is valid
- •Ensure icon is set in
onReadycallback
Application Not Quitting
Ensure systray.Quit() is called:
go func() {
<-mQuit.ClickedCh
systray.Quit() // Don't forget this!
}()
Template Icon Not Working
Template icons only work on macOS. On other platforms, provide a regular icon as fallback:
systray.SetTemplateIcon(templateIconBytes, regularIconBytes)
Quick Reference
// Lifecycle systray.Run(onReady, onExit) // Blocking systray.Register(onReady, onExit) // Non-blocking systray.Quit() // Exit application // Tray setup systray.SetIcon(iconBytes) // Set icon systray.SetTemplateIcon(tpl, reg) // Template icon (macOS) systray.SetTitle(title) // Set title (macOS/Linux) systray.SetTooltip(tooltip) // Set tooltip (macOS/Windows) // Menu items systray.AddMenuItem(title, tooltip) // Add menu item systray.AddMenuItemCheckbox(title, tooltip, checked) // Checkable (Linux) systray.AddSeparator() // Add separator item.AddSubMenuItem(title, tooltip) // Add sub-item item.AddSubMenuItemCheckbox(title, tooltip, checked) // Checkable sub-item (Linux) // Menu item state item.Enable() / item.Disable() / item.Disabled() // Enable/disable item.Check() / item.Uncheck() / item.Checked() // Check/uncheck item.Show() / item.Hide() // Show/hide item.SetTitle(title) // Update title item.SetTooltip(tooltip) // Update tooltip item.SetIcon(iconBytes) // Set icon (macOS/Windows) item.SetTemplateIcon(tpl, reg) // Template icon (macOS/Windows) // Click handling <-item.ClickedCh // Wait for click (in goroutine)