Skip to content

Commit c602ec1

Browse files
authored
Fix Ctrl+R desktop reload colliding with terminal reverse-search (#1133)
On Windows/Linux the native Wails Reload menu accelerator resolved to Ctrl+R, the shell's reverse-i-search binding. Native menu accelerators fire regardless of webview focus, so pressing Ctrl+R inside a pod/local/node terminal reloaded the whole app instead of searching history. macOS was unaffected (Cmd+R). Pick the Reload accelerator per platform: keep Cmd+R on macOS, use Ctrl+Shift+R on Windows/Linux. Terminals don't bind Ctrl+Shift+letter, so it keeps a reload hotkey without shadowing a key the terminal sends (plain function keys like F5 are consumed by TUIs and cmd.exe history).
1 parent a713307 commit c602ec1

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

cmd/desktop/menu.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package main
22

33
import (
4+
goruntime "runtime"
5+
46
"github.com/wailsapp/wails/v2/pkg/menu"
57
"github.com/wailsapp/wails/v2/pkg/menu/keys"
68
"github.com/wailsapp/wails/v2/pkg/runtime"
79
)
810

11+
// reloadAccelerator picks the Reload shortcut per platform. Off macOS,
12+
// CmdOrCtrl+R resolves to Ctrl+R — the terminal's reverse-i-search binding — and
13+
// a native menu accelerator fires regardless of webview focus, so it would
14+
// hijack Ctrl+R inside a pod/local shell. Ctrl+Shift+R keeps a reload hotkey
15+
// without shadowing a key terminals actually send (plain function keys like F5
16+
// are consumed by TUIs and cmd.exe history). macOS keeps Cmd+R, which doesn't
17+
// collide (terminals use Ctrl, not Cmd).
18+
func reloadAccelerator(goos string) *keys.Accelerator {
19+
if goos == "darwin" {
20+
return keys.CmdOrCtrl("r")
21+
}
22+
return keys.Combo("r", keys.ControlKey, keys.ShiftKey)
23+
}
24+
925
func createMenu(desktopApp *DesktopApp, version string) *menu.Menu {
1026
appMenu := menu.NewMenu()
1127

@@ -70,7 +86,7 @@ func createMenu(desktopApp *DesktopApp, version string) *menu.Menu {
7086
runtime.WindowExecJS(desktopApp.ctx, "window.history.forward()")
7187
})
7288
viewMenu.AddSeparator()
73-
viewMenu.AddText("Reload", keys.CmdOrCtrl("r"), func(_ *menu.CallbackData) {
89+
viewMenu.AddText("Reload", reloadAccelerator(goruntime.GOOS), func(_ *menu.CallbackData) {
7490
runtime.WindowReloadApp(desktopApp.ctx)
7591
})
7692
viewMenu.AddSeparator()

cmd/desktop/menu_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package main
22

33
import (
44
"reflect"
5+
goruntime "runtime"
56
"testing"
67

78
"github.com/wailsapp/wails/v2/pkg/menu"
9+
"github.com/wailsapp/wails/v2/pkg/menu/keys"
810
)
911

1012
func TestCreateMenuFileMenuExposesSupportedActions(t *testing.T) {
@@ -48,6 +50,43 @@ func TestCreateMenuNativeActionsHaveCallbacks(t *testing.T) {
4850
}
4951
}
5052

53+
func TestReloadAcceleratorAvoidsCtrlROffMac(t *testing.T) {
54+
cases := []struct {
55+
goos string
56+
want *keys.Accelerator
57+
}{
58+
{"darwin", keys.CmdOrCtrl("r")},
59+
{"windows", keys.Combo("r", keys.ControlKey, keys.ShiftKey)},
60+
{"linux", keys.Combo("r", keys.ControlKey, keys.ShiftKey)},
61+
}
62+
for _, tc := range cases {
63+
t.Run(tc.goos, func(t *testing.T) {
64+
got := reloadAccelerator(tc.goos)
65+
if !reflect.DeepEqual(got, tc.want) {
66+
t.Fatalf("reloadAccelerator(%q) = %+v, want %+v", tc.goos, got, tc.want)
67+
}
68+
})
69+
}
70+
}
71+
72+
// TestCreateMenuReloadIsWiredToPlatformAccelerator asserts the Reload item uses
73+
// the platform-picked accelerator. On Linux CI (goruntime.GOOS == "linux") this
74+
// executes the non-mac branch for real, proving Ctrl+R is not bound to Reload.
75+
func TestCreateMenuReloadIsWiredToPlatformAccelerator(t *testing.T) {
76+
appMenu := createMenu(&DesktopApp{}, "test")
77+
reload := findMenuItem(t, findSubmenu(t, appMenu, "View"), "Reload")
78+
79+
if reload.Click == nil {
80+
t.Fatal("Reload item has no callback")
81+
}
82+
if !reflect.DeepEqual(reload.Accelerator, reloadAccelerator(goruntime.GOOS)) {
83+
t.Fatalf("Reload accelerator = %+v, want %+v", reload.Accelerator, reloadAccelerator(goruntime.GOOS))
84+
}
85+
if goruntime.GOOS != "darwin" && reflect.DeepEqual(reload.Accelerator, keys.CmdOrCtrl("r")) {
86+
t.Fatalf("Reload is bound to Ctrl+R on %s — collides with terminal reverse-i-search", goruntime.GOOS)
87+
}
88+
}
89+
5190
func findSubmenu(t *testing.T, root *menu.Menu, label string) *menu.Menu {
5291
t.Helper()
5392
for _, item := range root.Items {

0 commit comments

Comments
 (0)