detached 21023fa

fix(launcher): give child apps the compositor's cursor size

commit
21023fa94cf2a5c314dfcaa2e82ad8be9630b092
author
brice · · 2026-09-25 14:59 UTC
parent
ed24532
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +15 -8
M internal/adapters/launcher/launcher.go +4 -5
@@ -7,6 +7,7 @@ import (
77 "os/exec"
88 "path/filepath"
99 "sort"
10+ "strconv"
1011 "strings"
1112 "syscall"
1213
@@ -23,7 +24,7 @@ func New(env []string, log zerowrap.Logger) *Launcher {
2324 return &Launcher{env: append([]string(nil), env...), log: log}
2425 }
2526
26-func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
27+func ChildEnv(base []string, waylandDisplay, runtimeDir string, cursorSize int) []string {
2728 values := map[string]string{}
2829 for _, entry := range base {
2930 key, value, ok := strings.Cut(entry, "=")
@@ -40,10 +41,8 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
4041 values["XDG_CURRENT_DESKTOP"] = "nefertty"
4142 values["XDG_SESSION_TYPE"] = "wayland"
4243 // X11 apps (through Xwayland) and toolkits pick their own cursor size:
43- // give them the compositor's, 24 unless set.
44- if values["XCURSOR_SIZE"] == "" {
45- values["XCURSOR_SIZE"] = "24"
46- }
44+ // give them the compositor's.
45+ values["XCURSOR_SIZE"] = strconv.Itoa(cursorSize)
4746 keys := make([]string, 0, len(values))
4847 for key := range values {
4948 keys = append(keys, key)
@@ -7,6 +7,7 @@ import (
7 "os/exec"7 "os/exec"
8 "path/filepath"8 "path/filepath"
9 "sort"9 "sort"
10+ "strconv"
10 "strings"11 "strings"
11 "syscall"12 "syscall"
12 13
@@ -23,7 +24,7 @@ func New(env []string, log zerowrap.Logger) *Launcher {
23 return &Launcher{env: append([]string(nil), env...), log: log}24 return &Launcher{env: append([]string(nil), env...), log: log}
24 }25 }
25 26
26-func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {27+func ChildEnv(base []string, waylandDisplay, runtimeDir string, cursorSize int) []string {
27 values := map[string]string{}28 values := map[string]string{}
28 for _, entry := range base {29 for _, entry := range base {
29 key, value, ok := strings.Cut(entry, "=")30 key, value, ok := strings.Cut(entry, "=")
@@ -40,10 +41,8 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
40 values["XDG_CURRENT_DESKTOP"] = "nefertty"41 values["XDG_CURRENT_DESKTOP"] = "nefertty"
41 values["XDG_SESSION_TYPE"] = "wayland"42 values["XDG_SESSION_TYPE"] = "wayland"
42 // X11 apps (through Xwayland) and toolkits pick their own cursor size:43 // X11 apps (through Xwayland) and toolkits pick their own cursor size:
43- // give them the compositor's, 24 unless set.44+ // give them the compositor's.
44- if values["XCURSOR_SIZE"] == "" {45+ values["XCURSOR_SIZE"] = strconv.Itoa(cursorSize)
45- values["XCURSOR_SIZE"] = "24"
46- }
47 keys := make([]string, 0, len(values))46 keys := make([]string, 0, len(values))
48 for key := range values {47 for key := range values {
49 keys = append(keys, key)48 keys = append(keys, key)
M internal/adapters/launcher/launcher_test.go +10 -2
@@ -5,6 +5,7 @@ import (
55 "os"
66 "path/filepath"
77 "reflect"
8+ "slices"
89 "testing"
910 "time"
1011
@@ -13,11 +14,18 @@ import (
1314 )
1415
1516 func TestChildEnv(t *testing.T) {
16- got := ChildEnv([]string{"TERM=x", "DISPLAY=:0", "WAYLAND_SOCKET=4", "WAYLAND_DISPLAY=old", "HOME=/home/me", "PATH=/bin", "LC_ALL=C", "XDG_RUNTIME_DIR=old", "XCURSOR_THEME=Adwaita", "SECRET=bad"}, "wayland-7", "/run/me")
17- want := []string{"HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "XCURSOR_SIZE=24", "XCURSOR_THEME=Adwaita", "XDG_CURRENT_DESKTOP=nefertty", "XDG_RUNTIME_DIR=/run/me", "XDG_SESSION_TYPE=wayland"}
17+ got := ChildEnv([]string{"TERM=x", "DISPLAY=:0", "WAYLAND_SOCKET=4", "WAYLAND_DISPLAY=old", "HOME=/home/me", "PATH=/bin", "LC_ALL=C", "XDG_RUNTIME_DIR=old", "XCURSOR_THEME=Adwaita", "XCURSOR_PATH=/icons", "SECRET=bad"}, "wayland-7", "/run/me", 24)
18+ want := []string{"HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "XCURSOR_PATH=/icons", "XCURSOR_SIZE=24", "XCURSOR_THEME=Adwaita", "XDG_CURRENT_DESKTOP=nefertty", "XDG_RUNTIME_DIR=/run/me", "XDG_SESSION_TYPE=wayland"}
1819 if !reflect.DeepEqual(got, want) {
1920 t.Fatalf("got %v want %v", got, want)
2021 }
22+ // The compositor's cursor size wins over the inherited one, valid or not.
23+ for _, size := range []string{"", "32", "abc", "1000"} {
24+ env := ChildEnv([]string{"XCURSOR_SIZE=" + size}, "w", "/r", 48)
25+ if !slices.Contains(env, "XCURSOR_SIZE=48") {
26+ t.Errorf("XCURSOR_SIZE=%q: %v", size, env)
27+ }
28+ }
2129 }
2230
2331 func TestRun(t *testing.T) {
@@ -5,6 +5,7 @@ import (
5 "os"5 "os"
6 "path/filepath"6 "path/filepath"
7 "reflect"7 "reflect"
8+ "slices"
8 "testing"9 "testing"
9 "time"10 "time"
10 11
@@ -13,11 +14,18 @@ import (
13 )14 )
14 15
15 func TestChildEnv(t *testing.T) {16 func TestChildEnv(t *testing.T) {
16- got := ChildEnv([]string{"TERM=x", "DISPLAY=:0", "WAYLAND_SOCKET=4", "WAYLAND_DISPLAY=old", "HOME=/home/me", "PATH=/bin", "LC_ALL=C", "XDG_RUNTIME_DIR=old", "XCURSOR_THEME=Adwaita", "SECRET=bad"}, "wayland-7", "/run/me")17+ got := ChildEnv([]string{"TERM=x", "DISPLAY=:0", "WAYLAND_SOCKET=4", "WAYLAND_DISPLAY=old", "HOME=/home/me", "PATH=/bin", "LC_ALL=C", "XDG_RUNTIME_DIR=old", "XCURSOR_THEME=Adwaita", "XCURSOR_PATH=/icons", "SECRET=bad"}, "wayland-7", "/run/me", 24)
17- want := []string{"HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "XCURSOR_SIZE=24", "XCURSOR_THEME=Adwaita", "XDG_CURRENT_DESKTOP=nefertty", "XDG_RUNTIME_DIR=/run/me", "XDG_SESSION_TYPE=wayland"}18+ want := []string{"HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "XCURSOR_PATH=/icons", "XCURSOR_SIZE=24", "XCURSOR_THEME=Adwaita", "XDG_CURRENT_DESKTOP=nefertty", "XDG_RUNTIME_DIR=/run/me", "XDG_SESSION_TYPE=wayland"}
18 if !reflect.DeepEqual(got, want) {19 if !reflect.DeepEqual(got, want) {
19 t.Fatalf("got %v want %v", got, want)20 t.Fatalf("got %v want %v", got, want)
20 }21 }
22+ // The compositor's cursor size wins over the inherited one, valid or not.
23+ for _, size := range []string{"", "32", "abc", "1000"} {
24+ env := ChildEnv([]string{"XCURSOR_SIZE=" + size}, "w", "/r", 48)
25+ if !slices.Contains(env, "XCURSOR_SIZE=48") {
26+ t.Errorf("XCURSOR_SIZE=%q: %v", size, env)
27+ }
28+ }
21 }29 }
22 30
23 func TestRun(t *testing.T) {31 func TestRun(t *testing.T) {
M internal/app/app.go +1 -1
@@ -103,7 +103,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent)
103103 return err
104104 }
105105 log.Info().Str("WAYLAND_DISPLAY", server.SocketName()).Msg("listening")
106- childEnv := launcher.ChildEnv(os.Environ(), server.SocketName(), runtimeDir)
106+ childEnv := launcher.ChildEnv(os.Environ(), server.SocketName(), runtimeDir, cursorSize())
107107 statePath, err := statefile.Path(runtimeDir, server.SocketName())
108108 if err != nil {
109109 log.Warn().Err(err).Msg("state file disabled")
@@ -103,7 +103,7 @@ func run(ctx context.Context, opts Options, inject func(chan<- ports.InputEvent)
103 return err103 return err
104 }104 }
105 log.Info().Str("WAYLAND_DISPLAY", server.SocketName()).Msg("listening")105 log.Info().Str("WAYLAND_DISPLAY", server.SocketName()).Msg("listening")
106- childEnv := launcher.ChildEnv(os.Environ(), server.SocketName(), runtimeDir)106+ childEnv := launcher.ChildEnv(os.Environ(), server.SocketName(), runtimeDir, cursorSize())
107 statePath, err := statefile.Path(runtimeDir, server.SocketName())107 statePath, err := statefile.Path(runtimeDir, server.SocketName())
108 if err != nil {108 if err != nil {
109 log.Warn().Err(err).Msg("state file disabled")109 log.Warn().Err(err).Msg("state file disabled")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 26

Tags 10

↑↓ moveenter openesc close