Add systray Show, Hide and Destroy

This commit is contained in:
Lea Anthony 2025-02-02 17:54:24 +11:00
commit 4caf6d6e50
No known key found for this signature in database
GPG key ID: 33DAF7BB90A58405
10 changed files with 311 additions and 13 deletions

View file

@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- More documentation by [@leaanthony](https://github.com/leaanthony)
- Support cancellation of events in standard event listeners by [@leaanthony](https://github.com/leaanthony)
- Systray `Hide`, `Show` and `Destroy` support by [@leaanthony](https://github.com/leaanthony)
### Fixed

View file

@ -1,5 +1,5 @@
---
title: Customizing Windows in Wails
title: Customising Windows in Wails
sidebar:
order: 10
---

View file

@ -205,6 +205,7 @@ INF I always run after hooks!
<Tabs>
<TabItem label="macOS" icon="fa-brands:apple">
| Event Name | Common Event | Description |
|------------|--------------|-------------|
| WindowDidBecomeKey | WindowFocus | Window became key window |
| WindowDidBecomeMain | - | Window became main window |
| WindowDidBeginSheet | - | Sheet began |
@ -233,7 +234,7 @@ INF I always run after hooks!
| WindowDidExitVersionBrowser | - | Window exited version browser |
| WindowDidExpose | - | Window exposed |
| WindowDidFocus | WindowFocus | Window gained focus |
| WindowDidMiniaturise | WindowMinimise | Window minimised |
| WindowDidMiniaturize | WindowMinimise | Window minimised |
| WindowDidMove | WindowDidMove | Window moved |
| WindowDidOrderOffScreen | - | Window ordered off screen |
| WindowDidOrderOnScreen | - | Window ordered on screen |
@ -255,7 +256,7 @@ INF I always run after hooks!
| WindowMaximise | WindowMaximise | Window maximised |
| WindowShouldClose | WindowClosing | Window should close |
| WindowShow | WindowShow | Window shown |
| WindowUnMaximise | WindowUnMaximise | Window unmaximised |
| WindowUnMaximize | WindowUnMaximise | Window unmaximised |
| WindowZoomIn | WindowZoomIn | Window zoomed in |
| WindowZoomOut | WindowZoomOut | Window zoomed out |
| WindowZoomReset | WindowZoomReset | Window zoom reset |

View file

@ -0,0 +1,212 @@
---
title: System Tray
description: Learn how to create and use system tray icons in Wails
---
import {Badge} from '@astrojs/starlight/components';
## Introduction
The system tray (also known as the notification area) is a section of the desktop environment where applications can display icons and menus. In Wails, you can easily add a system tray icon to your application with full control over its appearance and behavior.
## Basic Usage
To create a basic system tray icon:
```go
app := application.New(options)
systray := app.NewSystemTray()
systray.SetLabel("My App")
systray.SetIcon(iconBytes)
systray.Run()
```
## Setting the Icon
The system tray icon can be set using embedded image files. First, import the `embed` package and declare your icon files:
```go
import "embed"
//go:embed assets/icon.png assets/icon-dark.png
var iconFS embed.FS
```
Then read and set the icons:
```go
// Read icon data
iconBytes, _ := iconFS.ReadFile("assets/icon.png")
darkModeIconBytes, _ := iconFS.ReadFile("assets/icon-dark.png")
// Set icons
systray.SetIcon(iconBytes)
systray.SetDarkModeIcon(darkModeIconBytes)
```
Supported image formats include PNG and JPEG. For best results, use icons with appropriate sizes:
- Windows: 16x16 or 32x32 pixels
- macOS: 18x18 to 128x128 pixels
- Linux: Varies by desktop environment
On macOS, you can mark the icon as a template image for automatic dark/light mode adaptation:
```go
systray.SetTemplateIcon(iconBytes)
```
For more details on creating template icons, read this [great article](https://bjango.com/articles/designingmenubarextras/).
## Setting the Label <Badge text="macOS" variant="success" />
You can set a text label for your system tray icon:
```go
systray.SetLabel("My App")
```
The label will appear next to the icon in the system tray. On some platforms, this text may be truncated if it's too long.
## Adding a Menu
You can add a menu to your system tray icon:
```go
menu := application.NewMenu()
menu.Add("Open").OnClick(func() {
// Handle click
})
menu.Add("Quit").OnClick(func() {
app.Quit()
})
systray.SetMenu(menu)
```
## Attaching a Window
You can attach a window to a system tray icon to gain a number of desirable features:
- The attached window will start hidden
- Left-clicking on the system tray icon will toggle the visibility of the attached window
- Right-clicking on the system tray icon will show the system tray menu, if given
Here's a complete example:
```go
app := application.New()
// Create system tray
systray := app.NewSystemTray()
systray.SetLabel("My App")
// Create a window
window := app.NewWebviewWindow()
// Attach the window to the system tray
systray.AttachWindow(window)
// Optional: Set window offset from tray icon
systray.WindowOffset(10)
// Optional: Set debounce time for window show/hide
systray.WindowDebounce(200 * time.Millisecond)
// Add a menu (optional)
menu := application.NewMenu()
menu.Add("Open").OnClick(func() {
window.Show()
})
menu.Add("Quit").OnClick(func() {
app.Quit()
})
systray.SetMenu(menu)
systray.Run()
```
## Icon Position <Badge text="macOS" variant="success" />
On macOS, you can control the position of the system tray icon relative to other icons:
```go
systray.SetIconPosition(application.IconPositionRight)
```
Available positions:
- `NSImageNone`
- `NSImageOnly`
- `NSImageLeft`
- `NSImageRight`
- `NSImageBelow`
- `NSImageAbove`
- `NSImageOverlaps`
- `NSImageLeading`
- `NSImageTrailing`
## Destroying the System Tray
When you're done with the system tray, you should destroy it to release resources:
```go
systray.Destroy()
```
## Platform Considerations
- **macOS**: Icons support template images for automatic dark/light mode
- **Windows**: Icons should be 16x16 or 32x32 pixels
- **Linux**: Uses the StatusNotifierItem specification
## Examples
Explore these examples for more advanced usage:
- [Basic System Tray](/examples/systray-basic)
- [System Tray with Menu](/examples/systray-menu)
- [Custom System Tray](/examples/systray-custom)
## API Reference
### Core Methods
| Method | Description |
|--------------------------------|--------------------------------------------|
| `NewSystemTray()` | Creates a new system tray instance |
| `Run()` | Starts the system tray |
| `SetLabel(label string)` | Sets the text label |
| `SetIcon(icon []byte)` | Sets the icon image |
| `SetDarkModeIcon(icon []byte)` | Sets the dark mode variant of the icon |
| `SetTemplateIcon(icon []byte)` | Marks the icon as a template image (macOS) |
| `SetIconPosition(position int)`| Sets the icon position (macOS) |
| `Destroy()` | Destroys the system tray |
### Menu Management
| Method | Description |
|-----------------------|----------------------------------|
| `SetMenu(menu *Menu)` | Attaches a menu to the tray icon |
| `OpenMenu()` | Programmatically opens the menu |
### Event Handlers
| Method | Description |
|--------------------------------------|-----------------------------------|
| `OnClick(handler func())` | Handles left-click events |
| `OnRightClick(handler func())` | Handles right-click events |
| `OnDoubleClick(handler func())` | Handles left-double-click events |
| `OnRightDoubleClick(handler func())` | Handles right-double-click events |
| `OnMouseEnter(handler func())` | Handles mouse enter events |
| `OnMouseLeave(handler func())` | Handles mouse leave events |
### Window Attachment
| Method | Description |
|------------------------------------------|---------------------------------------------|
| `AttachWindow(window *WebviewWindow)` | Associates a window with the tray icon |
| `WindowOffset(offset int)` | Sets the offset between the tray and window |
| `WindowDebounce(debounce time.Duration)` | Sets the debounce time for window show/hide |
### Visibility Control
| Method | Description |
|----------|-----------------------------|
| `Show()` | Makes the tray icon visible |
| `Hide()` | Hides the tray icon |
See the [SystemTray API Reference](/api/systemtray) for complete documentation.

View file

@ -5,6 +5,7 @@ import (
"github.com/wailsapp/wails/v3/pkg/events"
"log"
"runtime"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/icons"
@ -90,7 +91,12 @@ func main() {
myMenu.AddRadio("Radio 1", true).OnClick(radioCallback)
myMenu.AddRadio("Radio 2", false).OnClick(radioCallback)
myMenu.AddRadio("Radio 3", false).OnClick(radioCallback)
myMenu.AddSeparator()
myMenu.Add("Hide System tray for 3 seconds...").OnClick(func(ctx *application.Context) {
systemTray.Hide()
time.Sleep(3 * time.Second)
systemTray.Show()
})
myMenu.AddSeparator()
myMenu.Add("Quit").OnClick(func(ctx *application.Context) {
app.Quit()

View file

@ -799,6 +799,14 @@ func (a *App) OnShutdown(f func()) {
a.shutdownTasks = append(a.shutdownTasks, f)
}
func (a *App) destroySystemTray(tray *SystemTray) {
// Remove the system tray from the a.systemTrays map
a.systemTraysLock.Lock()
delete(a.systemTrays, tray.id)
a.systemTraysLock.Unlock()
tray.destroy()
}
func (a *App) cleanup() {
if a.performingShutdown {
return

View file

@ -28,7 +28,7 @@ type systemTrayImpl interface {
run()
setIcon(icon []byte)
setMenu(menu *Menu)
setIconPosition(position int)
setIconPosition(position IconPosition)
setTemplateIcon(icon []byte)
destroy()
setDarkModeIcon(icon []byte)
@ -36,10 +36,8 @@ type systemTrayImpl interface {
getScreen() (*Screen, error)
positionWindow(window *WebviewWindow, offset int) error
openMenu()
}
type PositionOptions struct {
Buffer int
Show()
Hide()
}
type SystemTray struct {
@ -47,7 +45,7 @@ type SystemTray struct {
label string
icon []byte
darkModeIcon []byte
iconPosition int
iconPosition IconPosition
clickHandler func()
rightClickHandler func()
@ -162,7 +160,7 @@ func (s *SystemTray) SetMenu(menu *Menu) *SystemTray {
return s
}
func (s *SystemTray) SetIconPosition(iconPosition int) *SystemTray {
func (s *SystemTray) SetIconPosition(iconPosition IconPosition) *SystemTray {
if s.impl == nil {
s.iconPosition = iconPosition
} else {
@ -185,6 +183,10 @@ func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray {
return s
}
func (s *SystemTray) Destroy() {
globalApplication.destroySystemTray(s)
}
func (s *SystemTray) destroy() {
if s.impl == nil {
return
@ -222,6 +224,24 @@ func (s *SystemTray) OnMouseLeave(handler func()) *SystemTray {
return s
}
func (s *SystemTray) Show() {
if s.impl == nil {
return
}
InvokeSync(func() {
s.impl.Show()
})
}
func (s *SystemTray) Hide() {
if s.impl == nil {
return
}
InvokeSync(func() {
s.impl.Hide()
})
}
type WindowAttachConfig struct {
// Window is the window to attach to the system tray. If it's null, the request to attach will be ignored.
Window *WebviewWindow

View file

@ -9,6 +9,24 @@ package application
#include "Cocoa/Cocoa.h"
#include "menuitem_darwin.h"
#include "systemtray_darwin.h"
// Show the system tray icon
static void systemTrayShow(void* nsStatusItem) {
dispatch_async(dispatch_get_main_queue(), ^{
// Get the NSStatusItem
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
[statusItem setVisible:YES];
});
}
// Hide the system tray icon
static void systemTrayHide(void* nsStatusItem) {
dispatch_async(dispatch_get_main_queue(), ^{
NSStatusItem *statusItem = (NSStatusItem *)nsStatusItem;
[statusItem setVisible:NO];
});
}
*/
import "C"
import (
@ -27,12 +45,26 @@ type macosSystemTray struct {
nsStatusItem unsafe.Pointer
nsImage unsafe.Pointer
nsMenu unsafe.Pointer
iconPosition int
iconPosition IconPosition
isTemplateIcon bool
parent *SystemTray
lastClickedScreen unsafe.Pointer
}
func (s *macosSystemTray) Show() {
if s.nsStatusItem == nil {
return
}
C.systemTrayShow(s.nsStatusItem)
}
func (s *macosSystemTray) Hide() {
if s.nsStatusItem == nil {
return
}
C.systemTrayHide(s.nsStatusItem)
}
func (s *macosSystemTray) openMenu() {
if s.nsMenu == nil {
return
@ -61,7 +93,7 @@ func systrayClickCallback(id C.long, buttonID C.int) {
systemTray.processClick(button(buttonID))
}
func (s *macosSystemTray) setIconPosition(position int) {
func (s *macosSystemTray) setIconPosition(position IconPosition) {
s.iconPosition = position
}

View file

@ -724,6 +724,16 @@ func (s *linuxSystemTray) SecondaryActivate(x int32, y int32) (err *dbus.Error)
return
}
// Show is a no-op for Linux
func (s *linuxSystemTray) Show() {
// No-op
}
// Hide is a no-op for Linux
func (s *linuxSystemTray) Hide() {
// No-op
}
// tooltip is our data for a tooltip property.
// Param names need to match the generated code...
type tooltip = struct {

View file

@ -394,3 +394,11 @@ func (s *windowsSystemTray) destroy() {
globalApplication.debug(syscall.GetLastError().Error())
}
}
func (s *windowsSystemTray) Show() {
// No-op
}
func (s *windowsSystemTray) Hide() {
// No-op
}