detached 59256c2

merge: xcursor-env (cursor size for child apps)

commit
59256c231372ffa2084e5de01cf5bed29585618d
author
brice · · 2026-09-25 15:00 UTC
merge of
88e2b23 + 21023fa · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
3 files · +17 -5
M internal/adapters/launcher/launcher.go +6 -2
@@ -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, "=")
@@ -31,7 +32,7 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
3132 continue
3233 }
3334 switch {
34- case key == "HOME", key == "USER", key == "LOGNAME", key == "SHELL", key == "PATH", key == "LANG", strings.HasPrefix(key, "LC_"), strings.HasPrefix(key, "XDG_"):
35+ case key == "HOME", key == "USER", key == "LOGNAME", key == "SHELL", key == "PATH", key == "LANG", strings.HasPrefix(key, "LC_"), strings.HasPrefix(key, "XDG_"), strings.HasPrefix(key, "XCURSOR_"):
3536 values[key] = value
3637 }
3738 }
@@ -39,6 +40,9 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
3940 values["XDG_RUNTIME_DIR"] = runtimeDir
4041 values["XDG_CURRENT_DESKTOP"] = "nefertty"
4142 values["XDG_SESSION_TYPE"] = "wayland"
43+ // X11 apps (through Xwayland) and toolkits pick their own cursor size:
44+ // give them the compositor's.
45+ values["XCURSOR_SIZE"] = strconv.Itoa(cursorSize)
4246 keys := make([]string, 0, len(values))
4347 for key := range values {
4448 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, "=")
@@ -31,7 +32,7 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
31 continue32 continue
32 }33 }
33 switch {34 switch {
34- case key == "HOME", key == "USER", key == "LOGNAME", key == "SHELL", key == "PATH", key == "LANG", strings.HasPrefix(key, "LC_"), strings.HasPrefix(key, "XDG_"):35+ case key == "HOME", key == "USER", key == "LOGNAME", key == "SHELL", key == "PATH", key == "LANG", strings.HasPrefix(key, "LC_"), strings.HasPrefix(key, "XDG_"), strings.HasPrefix(key, "XCURSOR_"):
35 values[key] = value36 values[key] = value
36 }37 }
37 }38 }
@@ -39,6 +40,9 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string {
39 values["XDG_RUNTIME_DIR"] = runtimeDir40 values["XDG_RUNTIME_DIR"] = runtimeDir
40 values["XDG_CURRENT_DESKTOP"] = "nefertty"41 values["XDG_CURRENT_DESKTOP"] = "nefertty"
41 values["XDG_SESSION_TYPE"] = "wayland"42 values["XDG_SESSION_TYPE"] = "wayland"
43+ // X11 apps (through Xwayland) and toolkits pick their own cursor size:
44+ // give them the compositor's.
45+ values["XCURSOR_SIZE"] = strconv.Itoa(cursorSize)
42 keys := make([]string, 0, len(values))46 keys := make([]string, 0, len(values))
43 for key := range values {47 for key := range values {
44 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", "SECRET=bad"}, "wayland-7", "/run/me")
17- want := []string{"HOME=/home/me", "LC_ALL=C", "PATH=/bin", "WAYLAND_DISPLAY=wayland-7", "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", "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", "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 23

Tags 10

↑↓ moveenter openesc close