1package harness
2
3import (
4 "context"
5 "fmt"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "syscall"
11)
12
13const NeferWLSHA = "1d16b83bf90f9e8189ad21b27772382c96b9f9a8"
14
15func NeferWL(ctx context.Context) (string, error) {
16 if err := ctx.Err(); err != nil {
17 return "", err
18 }
19 if path := os.Getenv("NEFERGUI_NEFERWL"); path != "" {
20 if fi, err := os.Stat(path); err != nil || fi.IsDir() || fi.Mode()&0111 == 0 {
21 return "", fmt.Errorf("NEFERGUI_NEFERWL is not executable: %s", path)
22 }
23 return path, nil
24 }
25 cache := os.Getenv("XDG_CACHE_HOME")
26 if cache == "" {
27 var err error
28 cache, err = os.UserCacheDir()
29 if err != nil {
30 return "", err
31 }
32 }
33 dir := filepath.Join(cache, "nefergui", "harness", "neferwl-"+NeferWLSHA)
34 bin := filepath.Join(dir, "neferwl")
35 if fi, err := os.Stat(bin); err == nil && fi.Mode().IsRegular() && fi.Mode()&0111 != 0 {
36 return bin, nil
37 }
38 if err := os.MkdirAll(dir, 0700); err != nil {
39 return "", err
40 }
41 if err := ctx.Err(); err != nil {
42 return "", err
43 }
44 stage, err := os.MkdirTemp(dir, "build-")
45 if err != nil {
46 return "", err
47 }
48 defer os.RemoveAll(stage)
49 cmd := exec.Command("go", "install", "github.com/bnema/neferwl/cmd/neferwl@"+NeferWLSHA)
50 cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
51 cmd.Env = withEnv(os.Environ(), map[string]string{"GOWORK": "off", "GOBIN": stage})
52 stop := context.AfterFunc(ctx, func() {
53 if cmd.Process != nil {
54 _ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
55 }
56 })
57 output, err := cmd.CombinedOutput()
58 stop()
59 if ctx.Err() != nil {
60 return "", ctx.Err()
61 }
62 if err != nil {
63 return "", fmt.Errorf("neferwl build: %w: %s", err, strings.TrimSpace(string(output)))
64 }
65 if err := os.Rename(filepath.Join(stage, "neferwl"), bin); err != nil {
66 return "", err
67 }
68 return bin, nil
69}
70
71// isolatedEnv drops host display credentials before session-specific values are added.
72func isolatedEnv(base []string) []string {
73 out := make([]string, 0, len(base))
74 for _, e := range base {
75 k, _, _ := strings.Cut(e, "=")
76 if k != "DISPLAY" && k != "WAYLAND_DISPLAY" && k != "WAYLAND_SOCKET" {
77 out = append(out, e)
78 }
79 }
80 return out
81}
82func withEnv(base []string, overrides map[string]string) []string {
83 out := make([]string, 0, len(base)+len(overrides))
84 for _, e := range base {
85 k, _, _ := strings.Cut(e, "=")
86 if _, ok := overrides[k]; ok || k == "WAYLAND_SOCKET" {
87 continue
88 }
89 out = append(out, e)
90 }
91 for k, v := range overrides {
92 out = append(out, k+"="+v)
93 }
94 return out
95}