package main import ( "bufio" "fmt" "os" "path/filepath" "strconv" "strings" i3 "go.i3wm.org/i3/v4" ) const maxPPIDDepth = 20 // X11PIDResolver abstrait la lecture de _NET_WM_PID pour testabilite. type X11PIDResolver interface { GetPID(windowID int64) (int, error) } // I3TreeProvider abstrait i3.GetTree() pour testabilite. type I3TreeProvider interface { GetTree() (i3.Tree, error) } // ReadPPID lit le PPid depuis /proc/PID/status. procDir injectable pour les tests. func ReadPPID(procDir string, pid int) (int, error) { statusPath := filepath.Join(procDir, strconv.Itoa(pid), "status") f, err := os.Open(statusPath) if err != nil { return 0, err } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "PPid:") { fields := strings.Fields(line) if len(fields) < 2 { return 0, fmt.Errorf("malformed PPid line: %q", line) } return strconv.Atoi(fields[1]) } } return 0, fmt.Errorf("PPid not found in %s", statusPath) } // ResolveWorkspace remonte la chaine PPID depuis claudePID jusqu'a trouver // un PID connu dans terminalWorkspaces. Max 20 niveaux pour eviter les boucles. func ResolveWorkspace(procDir string, claudePID int, terminalWorkspaces map[int]string) string { current := claudePID for depth := 0; depth < maxPPIDDepth; depth++ { ppid, err := ReadPPID(procDir, current) if err != nil || ppid <= 0 { return "" } if ws, ok := terminalWorkspaces[ppid]; ok { return ws } current = ppid } return "" } // BuildTerminalWorkspaceMap construit la map terminalPID -> workspaceName // en parcourant l'arbre i3 et en resolvant le PID via X11 _NET_WM_PID. func BuildTerminalWorkspaceMap(tree I3TreeProvider, x11 X11PIDResolver) (map[int]string, error) { t, err := tree.GetTree() if err != nil { return nil, err } result := make(map[int]string) walkI3Tree(t.Root, "", x11, result) return result, nil } func walkI3Tree(node *i3.Node, currentWorkspace string, x11 X11PIDResolver, result map[int]string) { if node == nil { return } if node.Type == i3.WorkspaceNode { currentWorkspace = node.Name } if node.Window > 0 && currentWorkspace != "" { pid, err := x11.GetPID(node.Window) if err == nil && pid > 0 { result[pid] = currentWorkspace } } for _, child := range node.Nodes { walkI3Tree(child, currentWorkspace, x11, result) } for _, child := range node.FloatingNodes { walkI3Tree(child, currentWorkspace, x11, result) } }