merge: xcursor-env (cursor size for child apps)
3 files · +17 -5
M internal/adapters/launcher/launcher.go +6 -2
| @@ -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, "=") | |
| @@ -31,7 +32,7 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string { | |||
| 31 | 32 | continue | |
| 32 | 33 | } | |
| 33 | 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 | 36 | values[key] = value | |
| 36 | 37 | } | |
| 37 | 38 | } | |
| @@ -39,6 +40,9 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string { | |||
| 39 | 40 | values["XDG_RUNTIME_DIR"] = runtimeDir | |
| 40 | 41 | values["XDG_CURRENT_DESKTOP"] = "nefertty" | |
| 41 | 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 | 46 | keys := make([]string, 0, len(values)) | |
| 43 | 47 | for key := range values { | |
| 44 | 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, "=") | ||
| @@ -31,7 +32,7 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string { | |||||
| 31 | continue | 32 | 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] = value | 36 | values[key] = value | ||
| 36 | } | 37 | } | ||
| 37 | } | 38 | } | ||
| @@ -39,6 +40,9 @@ func ChildEnv(base []string, waylandDisplay, runtimeDir string) []string { | |||||
| 39 | values["XDG_RUNTIME_DIR"] = runtimeDir | 40 | 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 ( | |||
| 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", "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"} | |
| 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", "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) | |||
| 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") | ||