Files
mesh-catalog/modules/xorg/cmd/xorg/layout.go
T
jochen b2c170acda xorg: the display server holds node-display-server and writes the session's start (hq ADR 0208)
The X server, its start and its tools as one module. It provides x11-display with
the machine's reach, gated by the host's seat capability. It writes a block at the
start of ~/.xinitrc: the account's environment, an explicit import into the user
manager, the mesh's X resources merged without cpp, autorandr, the xinitrc slots,
~/.xinitrc.local, and the session's exec from the last slot.

Its Go tools serve the seat's displays and layout (autorandr profiles keyed by
EDID), and set-mode, primary, dpi, input devices and settings, keyboard, a
screenshot (xwd decoded in Go) and the server's log. internal/desktop is how
every desktop tool finds the operator's session from the runtime, which has none:
from the session's own processes, reading only its words, confirmed with logind.
2026-10-04 13:21:47 +02:00

106 lines
2.8 KiB
Go

package main
import (
"bufio"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
)
// Profile is one saved autorandr layout: the monitors it is for, by output and EDID fingerprint.
type Profile struct {
Name string `json:"name"`
Path string `json:"path"`
Monitors map[string]string `json:"monitors"`
Detected bool `json:"detected,omitempty"`
Current bool `json:"current,omitempty"`
}
// profileName is what a profile may be called: it becomes a directory name.
var profileName = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`)
// ProfileDirs are where autorandr keeps profiles, the account's first (autorandr(1)).
func ProfileDirs(home string) []string {
cfg := os.Getenv("XDG_CONFIG_HOME")
if cfg == "" {
cfg = filepath.Join(home, ".config")
}
return []string{filepath.Join(cfg, "autorandr"), "/etc/xdg/autorandr"}
}
// ReadProfiles lists the profiles in dirs; a name in an earlier directory hides a later one, as in
// autorandr itself. Each profile's monitors come from its `setup` file: `<output> <edid>` per line.
func ReadProfiles(dirs []string) []Profile {
seen := map[string]bool{}
var out []Profile
for _, dir := range dirs {
entries, err := os.ReadDir(dir)
if err != nil {
continue
}
for _, e := range entries {
if !e.IsDir() || seen[e.Name()] {
continue
}
setup := filepath.Join(dir, e.Name(), "setup")
f, err := os.Open(setup)
if err != nil {
continue
}
p := Profile{Name: e.Name(), Path: filepath.Join(dir, e.Name()), Monitors: map[string]string{}}
sc := bufio.NewScanner(f)
for sc.Scan() {
fields := strings.Fields(sc.Text())
if len(fields) == 2 {
p.Monitors[fields[0]] = shortEDID(fields[1])
}
}
f.Close()
seen[e.Name()] = true
out = append(out, p)
}
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out
}
// shortEDID is the fingerprint DecodeEDID gives for the same monitor, so a profile's monitors and the
// connected ones are compared by one value.
func shortEDID(hexEDID string) string {
b := make([]byte, 0, len(hexEDID)/2)
for i := 0; i+1 < len(hexEDID); i += 2 {
var v byte
for _, c := range hexEDID[i : i+2] {
v <<= 4
switch {
case c >= '0' && c <= '9':
v |= byte(c - '0')
case c >= 'a' && c <= 'f':
v |= byte(c - 'a' + 10)
case c >= 'A' && c <= 'F':
v |= byte(c - 'A' + 10)
default:
return hexEDID
}
}
b = append(b, v)
}
if id := DecodeEDID(b); id != nil {
return id.Fingerprint
}
return hexEDID
}
// names reads autorandr's one-name-per-line answers (--detected, --current).
func names(text string) map[string]bool {
out := map[string]bool{}
for _, l := range strings.Split(text, "\n") {
if l = strings.TrimSpace(l); l != "" {
out[strings.Fields(l)[0]] = true
}
}
return out
}