// session — environment scanning from xsessions, wayland-sessions, // and custom script directories. Includes desktop entry parser. // // Mirrors vendor/src/post_login/mod.rs:get_envs and parse_desktop_entry. package session import ( "bufio" "fmt" "os" "path/filepath" "strings" ) type DesktopEntry struct { Name string Exec string DesktopNames string Hidden bool NoDisplay bool } func parseDesktopEntry(path string) (*DesktopEntry, error) { f, err := os.Open(path) if err != nil { return nil, fmt.Errorf("open %s: %w", path, err) } defer f.Close() e := &DesktopEntry{} inSection := false sc := bufio.NewScanner(f) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || line[0] == '#' { continue } if line[0] == '[' && line[len(line)-1] == ']' { inSection = line == "[Desktop Entry]" continue } if !inSection { continue } eq := strings.IndexByte(line, '=') if eq < 0 { continue } k, v := line[:eq], line[eq+1:] switch k { case "Name": e.Name = v case "Exec": e.Exec = v case "DesktopNames": e.DesktopNames = v case "Hidden": e.Hidden = strings.EqualFold(v, "true") case "NoDisplay": e.NoDisplay = strings.EqualFold(v, "true") } } if sc.Err() != nil { return nil, sc.Err() } if e.Exec == "" { return nil, fmt.Errorf("no Exec in %s", path) } if e.Name == "" { e.Name = e.Exec } return e, nil } func ScanEnvs(xSessions, wlSessions, xScripts, wlScripts string, includeTTY bool) []PostLoginEnv { var envs []PostLoginEnv scanDesktopDir(xSessions, "x11", &envs) scanDesktopDir(wlSessions, "wayland", &envs) scanScriptDir(xScripts, "x11", &envs) scanScriptDir(wlScripts, "wayland", &envs) if len(envs) == 0 || includeTTY { envs = append(envs, PostLoginEnv{Kind: "tty", Title: "TTYSHELL"}) } return envs } func scanDesktopDir(dir, kind string, envs *[]PostLoginEnv) { entries, err := os.ReadDir(dir) if err != nil { return } for _, e := range entries { if e.IsDir() || filepath.Ext(e.Name()) != ".desktop" { continue } path := filepath.Join(dir, e.Name()) de, err := parseDesktopEntry(path) if err != nil || de.Hidden || de.NoDisplay { continue } desktopNames, sessionDesktop := splitDesktopNames(de.DesktopNames, e.Name()) *envs = append(*envs, PostLoginEnv{ Kind: kind, Title: de.Name, XinitrcPath: de.Exec, ScriptPath: de.Exec, DesktopNames: desktopNames, SessionDesktop: sessionDesktop, }) } } func splitDesktopNames(raw, fallback string) (currentDesktop, sessionDesktop string) { if raw == "" { return fallback, fallback } parts := strings.SplitN(raw, ";", 2) sessionDesktop = strings.TrimSpace(parts[0]) currentDesktop = strings.ReplaceAll(raw, ";", ":") return } func scanScriptDir(dir, kind string, envs *[]PostLoginEnv) { entries, err := os.ReadDir(dir) if err != nil { return } for _, e := range entries { if e.IsDir() { continue } info, err := e.Info() if err != nil { continue } if info.Mode()&0111 == 0 { continue } path := filepath.Join(dir, e.Name()) *envs = append(*envs, PostLoginEnv{ Kind: kind, Title: e.Name(), XinitrcPath: path, ScriptPath: path, }) } }