diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx
index 8afb4bc39..c94be6695 100644
--- a/docs/src/content/docs/changelog.mdx
+++ b/docs/src/content/docs/changelog.mdx
@@ -45,6 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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)
+- Systray `SetTooltip` support by [@leaanthony](https://github.com/leaanthony). Original idea by [@lujihong](https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304)
### Fixed
diff --git a/docs/src/content/docs/learn/systray.mdx b/docs/src/content/docs/learn/systray.mdx
index 5e352ce09..fe306e99b 100644
--- a/docs/src/content/docs/learn/systray.mdx
+++ b/docs/src/content/docs/learn/systray.mdx
@@ -56,6 +56,14 @@ systray.SetTemplateIcon(iconBytes)
For more details on creating template icons, read this [great article](https://bjango.com/articles/designingmenubarextras/).
+## Setting the Tooltip
+
+You can set a tooltip for your system tray icon that appears when hovering over the icon:
+
+```go
+systray.SetTooltip("My Application Tooltip")
+```
+
## Setting the Label
You can set a text label for your system tray icon:
@@ -66,6 +74,10 @@ 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.
+:::note
+On Windows, setting the label is the equivalent of setting the tooltip.
+:::
+
## Adding a Menu
You can add a menu to your system tray icon:
@@ -172,6 +184,7 @@ Explore these examples for more advanced usage:
| `NewSystemTray()` | Creates a new system tray instance |
| `Run()` | Starts the system tray |
| `SetLabel(label string)` | Sets the text label |
+| `SetTooltip(tooltip string)` | Sets the tooltip text (Windows) |
| `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) |
@@ -208,4 +221,3 @@ Explore these examples for more advanced usage:
|----------|-----------------------------|
| `Show()` | Makes the tray icon visible |
| `Hide()` | Hides the tray icon |
-
diff --git a/v3/examples/systray-custom/main.go b/v3/examples/systray-custom/main.go
index 2164e42f7..2aa244ba4 100644
--- a/v3/examples/systray-custom/main.go
+++ b/v3/examples/systray-custom/main.go
@@ -56,6 +56,7 @@ func main() {
app.Quit()
})
systemTray.SetMenu(menu)
+ systemTray.SetTooltip("Systray Demo")
if runtime.GOOS == "darwin" {
systemTray.SetTemplateIcon(icons.SystrayMacTemplate)
diff --git a/v3/pkg/application/systemtray.go b/v3/pkg/application/systemtray.go
index d7a5b5716..f3daa1e51 100644
--- a/v3/pkg/application/systemtray.go
+++ b/v3/pkg/application/systemtray.go
@@ -25,6 +25,7 @@ const (
type systemTrayImpl interface {
setLabel(label string)
+ setTooltip(tooltip string)
run()
setIcon(icon []byte)
setMenu(menu *Menu)
@@ -43,6 +44,7 @@ type systemTrayImpl interface {
type SystemTray struct {
id uint
label string
+ tooltip string
icon []byte
darkModeIcon []byte
iconPosition IconPosition
@@ -67,6 +69,7 @@ func newSystemTray(id uint) *SystemTray {
result := &SystemTray{
id: id,
label: "",
+ tooltip: "",
iconPosition: NSImageLeading,
attachedWindow: WindowAttachConfig{
Window: nil,
@@ -183,6 +186,16 @@ func (s *SystemTray) SetTemplateIcon(icon []byte) *SystemTray {
return s
}
+func (s *SystemTray) SetTooltip(tooltip string) {
+ if s.impl == nil {
+ s.tooltip = tooltip
+ return
+ }
+ InvokeSync(func() {
+ s.impl.setTooltip(tooltip)
+ })
+}
+
func (s *SystemTray) Destroy() {
globalApplication.destroySystemTray(s)
}
diff --git a/v3/pkg/application/systemtray_darwin.go b/v3/pkg/application/systemtray_darwin.go
index 0d60940a1..677abbbce 100644
--- a/v3/pkg/application/systemtray_darwin.go
+++ b/v3/pkg/application/systemtray_darwin.go
@@ -189,6 +189,10 @@ func (s *macosSystemTray) setTemplateIcon(icon []byte) {
})
}
+func (s *macosSystemTray) setTooltip(tooltip string) {
+ // Tooltips not supported on macOS
+}
+
func newSystemTrayImpl(s *SystemTray) systemTrayImpl {
result := &macosSystemTray{
parent: s,
diff --git a/v3/pkg/application/systemtray_linux.go b/v3/pkg/application/systemtray_linux.go
index 801bde339..fba5cc65a 100644
--- a/v3/pkg/application/systemtray_linux.go
+++ b/v3/pkg/application/systemtray_linux.go
@@ -6,16 +6,16 @@ Portions of this code are derived from the project:
*/
package application
+import "C"
import (
"fmt"
- "os"
-
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"github.com/godbus/dbus/v5/prop"
"github.com/wailsapp/wails/v3/internal/dbus/menu"
"github.com/wailsapp/wails/v3/internal/dbus/notifier"
"github.com/wailsapp/wails/v3/pkg/icons"
+ "os"
)
const (
@@ -31,7 +31,7 @@ type linuxSystemTray struct {
icon []byte
menu *Menu
- iconPosition int
+ iconPosition IconPosition
isTemplateIcon bool
quitChan chan struct{}
@@ -41,6 +41,7 @@ type linuxSystemTray struct {
menuVersion uint32 // need to bump this anytime we change anything
itemMap map[int32]*systrayMenuItem
+ tooltip string
}
func (s *linuxSystemTray) getScreen() (*Screen, error) {
@@ -103,9 +104,9 @@ func (s *systrayMenuItem) setHidden(hidden bool) {
s.sysTray.update(s)
}
-func (i systrayMenuItem) dbus() *dbusMenu {
+func (s *systrayMenuItem) dbus() *dbusMenu {
item := &dbusMenu{
- V0: int32(i.menuItem.id),
+ V0: int32(s.menuItem.id),
V1: map[string]dbus.Variant{},
V2: []dbus.Variant{},
}
@@ -364,9 +365,21 @@ func (s *linuxSystemTray) run() {
}
}
}()
+
+ if s.parent.label != "" {
+ s.setLabel(s.parent.label)
+ }
+
+ if s.parent.tooltip != "" {
+ s.setTooltip(s.parent.tooltip)
+ }
s.setMenu(s.menu)
}
+func (s *linuxSystemTray) setTooltip(_ string) {
+ // TBD
+}
+
func (s *linuxSystemTray) setIcon(icon []byte) {
s.icon = icon
diff --git a/v3/pkg/application/systemtray_windows.go b/v3/pkg/application/systemtray_windows.go
index 11a24d41d..8b0943812 100644
--- a/v3/pkg/application/systemtray_windows.go
+++ b/v3/pkg/application/systemtray_windows.go
@@ -223,6 +223,10 @@ func (s *windowsSystemTray) run() {
s.updateMenu(s.parent.menu)
}
+ if s.parent.tooltip != "" {
+ s.setTooltip(s.parent.tooltip)
+ }
+
// Set Default Callbacks
if s.parent.clickHandler == nil {
s.parent.clickHandler = func() {
@@ -367,12 +371,36 @@ func (s *windowsSystemTray) updateMenu(menu *Menu) {
s.menu.Update()
}
-// ---- Unsupported ----
+// Based on the idea from https://github.com/wailsapp/wails/issues/3487#issuecomment-2633242304
+func (s *windowsSystemTray) setTooltip(tooltip string) {
+ // Ensure the tooltip length is within the limit (64 characters for szTip)
+ if len(tooltip) > 64 {
+ tooltip = tooltip[:64]
+ }
-func (s *windowsSystemTray) setLabel(_ string) {
- // Unsupported - do nothing
+ // Create a new NOTIFYICONDATA structure
+ nid := s.newNotifyIconData()
+ nid.UFlags = w32.NIF_TIP
+ tooltipUTF16, err := w32.StringToUTF16(tooltip)
+ if err != nil {
+ return
+ }
+
+ copy(nid.SzTip[:], tooltipUTF16)
+
+ // Modify the tray icon with the new tooltip
+ if !w32.ShellNotifyIcon(w32.NIM_MODIFY, &nid) {
+ return
+ }
+ nid.UVersion = 3 // Version 4 does not suport
+ if !w32.ShellNotifyIcon(w32.NIM_SETVERSION, &nid) {
+ return
+ }
}
+// ---- Unsupported ----
+func (s *windowsSystemTray) setLabel(label string) {}
+
func (s *windowsSystemTray) setTemplateIcon(_ []byte) {
// Unsupported - do nothing
}
diff --git a/v3/pkg/w32/window.go b/v3/pkg/w32/window.go
index 3b4cefa79..3e488a49c 100644
--- a/v3/pkg/w32/window.go
+++ b/v3/pkg/w32/window.go
@@ -191,6 +191,11 @@ func MustStringToUTF16(input string) []uint16 {
return lo.Must(syscall.UTF16FromString(input))
}
+func StringToUTF16(input string) ([]uint16, error) {
+ input = stripNulls(input)
+ return syscall.UTF16FromString(input)
+}
+
func CenterWindow(hwnd HWND) {
windowInfo := getWindowInfo(hwnd)
frameless := windowInfo.IsPopup()
@@ -329,7 +334,10 @@ func FindWindowW(className, windowName *uint16) HWND {
func SendMessageToWindow(hwnd HWND, msg string) {
// Convert data to UTF16 string
- dataUTF16 := MustStringToUTF16(msg)
+ dataUTF16, err := StringToUTF16(msg)
+ if err != nil {
+ return
+ }
// Prepare COPYDATASTRUCT
cds := COPYDATASTRUCT{