fix(launcher): give child apps the compositor's cursor size
- commit
- 21023fa94cf2a5c314dfcaa2e82ad8be9630b092
- author
- brice · · 2026-09-25 14:59 UTC
- parent
- ed24532
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +15 -8
M internal/adapters/launcher/launcher.go +4 -5
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "os/exec" | |
| 8 | 8 | "path/filepath" | |
| 9 | 9 | "sort" | |
| 10 | + | "strconv" | |
| 10 | 11 | "strings" | |
| 11 | 12 | "syscall" | |
| 12 | 13 | ||
| @@ -23,7 +24,7 @@ func New(env []string, log zerowrap.Logger) *Launcher { | |||
| 23 | 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 | 28 | values := map[string]string{} | |
| 28 | 29 | for _, entry := range base { | |
| 29 | 30 | key, value, ok := strings.Cut(entry, "=") | |
| @@ -40,10 +41,8 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string { | |||
| 40 | 41 | values["XDG_CURRENT_DESKTOP"] = "nefertty" | |
| 41 | 42 | values["XDG_SESSION_TYPE"] = "wayland" | |
| 42 | 43 | // 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) | |
| 47 | 46 | keys := make([]string, 0, len(values)) | |
| 48 | 47 | for key := range values { | |
| 49 | 48 | 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 ( | |||
| 5 | 5 | "os" | |
| 6 | 6 | "path/filepath" | |
| 7 | 7 | "reflect" | |
| 8 | + | "slices" | |
| 8 | 9 | "testing" | |
| 9 | 10 | "time" | |
| 10 | 11 | ||
| @@ -13,11 +14,18 @@ import ( | |||
| 13 | 14 | ) | |
| 14 | 15 | ||
| 15 | 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 | - | 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"} | |
| 18 | 19 | if !reflect.DeepEqual(got, want) { | |
| 19 | 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 | 31 | 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) | |||
| 103 | 103 | return err | |
| 104 | 104 | } | |
| 105 | 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 | 107 | statePath, err := statefile.Path(runtimeDir, server.SocketName()) | |
| 108 | 108 | if err != nil { | |
| 109 | 109 | 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 err | 103 | 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") | ||