Files
latchd/src/session/scan.go
Zane Walker a911adf284 session: parse DesktopNames for XDG_CURRENT_DESKTOP, wire utmpx, fix shell
- .desktop files now parse DesktopNames: first ;-entry → XDG_SESSION_DESKTOP,
  full list with : separators → XDG_CURRENT_DESKTOP
- AddUtmpx/Close wired into spawnSession (logins appear in who/w/last)
- Credentials.Shell reads from /etc/passwd instead of hardcoded /bin/sh
- Added SetDesktopVars to env.go
2026-07-19 01:54:22 +05:30

150 lines
3.2 KiB
Go

// 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,
})
}
}