Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions v3/UNRELEASED_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ After processing, the content will be moved to the main changelog and this file
## Fixed
<!-- Bug fixes -->
- Increase WebView2 embed timeout to 60 seconds in [PR](https://github.com/wailsapp/wails/pull/6043) by @Grantmartin2002
- Fix right-clicking a Linux system tray icon running the click handler as well as opening the menu, so an attached window toggled on every right-click (#6018)

## Deprecated
<!-- Soon-to-be removed features -->
Expand Down
8 changes: 5 additions & 3 deletions v3/pkg/application/systemtray_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,9 +718,11 @@ func (s *linuxSystemTray) Event(id int32, eventID string, data dbus.Variant, tim
gtkDispatch(item.menuItem.handleClick)
}
case "opened":
if s.parent.clickHandler != nil {
s.parent.clickHandler()
}
// Not the click handler: "opened" says the host is about to show the
// context menu, which is the secondary button. ItemIsMenu is false, so
// a primary click arrives as Activate and is handled there — firing the
// click handler here as well makes every right-click do whatever a
// left-click does, on top of opening the menu.
if s.parent.onMenuOpen != nil {
s.parent.onMenuOpen()
}
Comment on lines 720 to 728

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 54652d6v3/pkg/application/systemtray_linux_event_test.go, linux-tagged and sitting next to the existing systemtray_linux_race_test.go. TestLinuxSystemTrayMenuEventsDoNotClick asserts Event("opened") runs onMenuOpen and not clickHandler, and Event("closed") runs onMenuClose and not clickHandler. TestLinuxSystemTrayActivateClicks covers the other direction — Activate is the path a primary click actually arrives on, since ItemIsMenu is published as false, so it must still run clickHandler (and record the click position). TestLinuxSystemTrayEventsWithoutHandlers checks neither path panics with no handlers attached. Verified both ways: restoring the clickHandler() call in the "opened" case makes the first test fail, and the set passes with the fix in place.

Expand Down
94 changes: 94 additions & 0 deletions v3/pkg/application/systemtray_linux_event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//go:build linux && !android && !server

package application

import (
"testing"

"github.com/godbus/dbus/v5"
)

// trayHandlerCounts records which of the SystemTray callbacks a dbusmenu
// event reached, so each assertion can pin the whole set rather than the one
// callback it is about — an event running a callback it should not is the
// failure mode here.
type trayHandlerCounts struct {
clicks, opens, closes int
}

// newCountingTray builds a tray with all three callbacks attached and an App
// with no logger installed: the dbusmenu callbacks log through
// globalApplication, which is nil in a test binary.
func newCountingTray(t *testing.T, counts *trayHandlerCounts) *linuxSystemTray {
t.Helper()

prev := globalApplication
globalApplication = &App{}
t.Cleanup(func() { globalApplication = prev })

return &linuxSystemTray{parent: &SystemTray{
clickHandler: func() { counts.clicks++ },
onMenuOpen: func() { counts.opens++ },
onMenuClose: func() { counts.closes++ },
}}
}

func (c trayHandlerCounts) check(t *testing.T, after string, clicks, opens, closes int) {
t.Helper()
if c.clicks != clicks || c.opens != opens || c.closes != closes {
t.Errorf("after %s: click=%d onMenuOpen=%d onMenuClose=%d, want %d/%d/%d",
after, c.clicks, c.opens, c.closes, clicks, opens, closes)
}
}

// The dbusmenu "opened" event is the host announcing that it is about to show
// the context menu, which is the secondary button. Running the click handler
// here as well made every right-click do whatever a left-click does — on an
// app whose click handler toggles a window, right-clicking toggled it.
func TestLinuxSystemTrayMenuEventsDoNotClick(t *testing.T) {
var counts trayHandlerCounts
tray := newCountingTray(t, &counts)

if err := tray.Event(0, "opened", dbus.Variant{}, 0); err != nil {
t.Fatalf(`Event("opened") returned %v`, err)
}
counts.check(t, `Event("opened")`, 0, 1, 0)

if err := tray.Event(0, "closed", dbus.Variant{}, 0); err != nil {
t.Fatalf(`Event("closed") returned %v`, err)
}
counts.check(t, `Event("closed")`, 0, 1, 1)
}

// The counterpart: ItemIsMenu is published as false, so the primary button
// arrives as Activate, which is now the only path to the click handler.
func TestLinuxSystemTrayActivateClicks(t *testing.T) {
var counts trayHandlerCounts
tray := newCountingTray(t, &counts)

if err := tray.Activate(12, 34); err != nil {
t.Fatalf("Activate returned %v", err)
}
counts.check(t, "Activate", 1, 0, 0)

if tray.lastClickX != 12 || tray.lastClickY != 34 {
t.Errorf("Activate recorded (%d,%d), want (12,34)", tray.lastClickX, tray.lastClickY)
}
}

// A tray with no handlers attached must not panic on the same events.
func TestLinuxSystemTrayEventsWithoutHandlers(t *testing.T) {
prev := globalApplication
globalApplication = &App{}
t.Cleanup(func() { globalApplication = prev })

tray := &linuxSystemTray{parent: &SystemTray{}}
for _, eventID := range []string{"opened", "closed"} {
if err := tray.Event(0, eventID, dbus.Variant{}, 0); err != nil {
t.Fatalf("Event(%q) returned %v", eventID, err)
}
}
if err := tray.Activate(0, 0); err != nil {
t.Fatalf("Activate returned %v", err)
}
}
Loading