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{