Files
mesh-catalog/modules/xclip/cmd/xclip-tools/session.go
T
jochen b1b7e58e4b xclip: the package, and the operator's clipboard from the mesh (hq to-be 42 phase 2.7)
A package and nothing else, the tool the desktop's scripts depend on.
Four Go tools: copy, paste (text, base64 for other types, empty when
nothing), targets and session.

The runtime is given no session words, but runs as the account in the
machine's own namespace, so the session is found rather than configured:
the process's DISPLAY, else the account's processes' DISPLAY and XAUTHORITY
from /proc (the window manager's first), else the only X socket with
~/.Xauthority. Measured with both variables unset: :1 found through i3, the
server answered. With no session every tool says so and runs nothing.
2026-10-04 13:02:23 +02:00

178 lines
5.6 KiB
Go

package main
// session.go is the same file in the bundles whose tools act in the operator's graphical session
// (xclip, dmenu): how a process the node's tool runtime launched reaches that session.
//
// The runtime is a system service running as the operator account (novox/hq ADR 0175 §4), in the
// machine's own mount namespace, and is given no session words: no DISPLAY, no XAUTHORITY. An X
// server accepts a client that names its display and presents the cookie in the authority file, and
// both are the account's: the display's socket is in /tmp/.X11-unix, and the cookie file is
// readable by the account. So the session is found, not configured:
//
// 1. the process's own DISPLAY, when the runtime happens to have one;
// 2. else the DISPLAY and XAUTHORITY of the account's own running processes, read from
// /proc/<pid>/environ (the window manager's, by preference), whose socket exists;
// 3. else the only X socket there is, with the authority file in the account's home.
//
// When none is found the tool says that no graphical session of the account is running, and does
// nothing.
import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
)
// Session is the operator's X session as a tool reaches it.
type Session struct {
Display string `json:"display"`
XAuthority string `json:"xauthority,omitempty"`
// FoundBy says how: "environment", "process <pid> (<name>)" or "socket".
FoundBy string `json:"found_by"`
}
// Env is what a command needs to reach the session.
func (s Session) Env() []string {
env := []string{"DISPLAY=" + s.Display}
if s.XAuthority != "" {
env = append(env, "XAUTHORITY="+s.XAuthority)
}
return env
}
// Where the session is looked for. Tests point these at a tree of their own.
var (
procRoot = "/proc"
x11Sockets = "/tmp/.X11-unix"
getenv = os.Getenv
myUID = os.Getuid
)
// sessionWMs are the programs whose environment is the session's own, preferred over any other
// process's (a terminal's child may carry a stale or forwarded DISPLAY).
var sessionWMs = map[string]bool{"i3": true, "sway": true, "xinit": true, "i3bar": true, "picom": true, "dunst": true}
func accountHome() string {
if h := strings.TrimSpace(getenv("MESH_OPERATOR_HOME")); h != "" {
return h
}
if h := strings.TrimSpace(getenv("HOME")); h != "" {
return h
}
h, _ := os.UserHomeDir()
return h
}
// socketOf is the local socket of a display such as ":1" or ":1.0", or "" for a remote one.
func socketOf(display string) string {
if !strings.HasPrefix(display, ":") {
return ""
}
n := strings.TrimPrefix(display, ":")
if i := strings.IndexByte(n, '.'); i >= 0 {
n = n[:i]
}
if _, err := strconv.Atoi(n); err != nil {
return ""
}
return filepath.Join(x11Sockets, "X"+n)
}
func exists(p string) bool {
_, err := os.Stat(p)
return err == nil
}
// findSession answers the account's X session, or an error saying there is none.
func findSession() (Session, error) {
if d := strings.TrimSpace(getenv("DISPLAY")); d != "" {
if s := socketOf(d); s == "" || exists(s) {
return Session{Display: d, XAuthority: getenv("XAUTHORITY"), FoundBy: "environment"}, nil
}
}
type seen struct {
Session
wm bool
count int
}
found := map[string]*seen{}
entries, _ := os.ReadDir(procRoot)
for _, e := range entries {
pid, err := strconv.Atoi(e.Name())
if err != nil || !e.IsDir() {
continue
}
dir := filepath.Join(procRoot, e.Name())
info, err := os.Stat(dir)
if err != nil {
continue
}
if st, ok := info.Sys().(*syscall.Stat_t); !ok || int(st.Uid) != myUID() {
continue
}
raw, err := os.ReadFile(filepath.Join(dir, "environ"))
if err != nil {
continue
}
var display, auth string
for _, kv := range strings.Split(string(raw), "\x00") {
switch {
case strings.HasPrefix(kv, "DISPLAY="):
display = strings.TrimPrefix(kv, "DISPLAY=")
case strings.HasPrefix(kv, "XAUTHORITY="):
auth = strings.TrimPrefix(kv, "XAUTHORITY=")
}
}
if display == "" {
continue
}
if s := socketOf(display); s == "" || !exists(s) {
continue
}
comm, _ := os.ReadFile(filepath.Join(dir, "comm"))
name := strings.TrimSpace(string(comm))
key := display + "\x00" + auth
if found[key] == nil {
found[key] = &seen{Session: Session{Display: display, XAuthority: auth, FoundBy: fmt.Sprintf("process %d (%s)", pid, name)}}
}
f := found[key]
f.count++
if sessionWMs[name] && !f.wm {
f.wm = true
f.FoundBy = fmt.Sprintf("process %d (%s)", pid, name)
}
}
if len(found) > 0 {
all := make([]*seen, 0, len(found))
for _, f := range found {
all = append(all, f)
}
sort.Slice(all, func(i, k int) bool {
if all[i].wm != all[k].wm {
return all[i].wm
}
if all[i].count != all[k].count {
return all[i].count > all[k].count
}
return all[i].Display < all[k].Display
})
return all[0].Session, nil
}
sockets, _ := filepath.Glob(filepath.Join(x11Sockets, "X*"))
if len(sockets) == 1 {
s := Session{Display: ":" + strings.TrimPrefix(filepath.Base(sockets[0]), "X"), FoundBy: "socket"}
if a := filepath.Join(accountHome(), ".Xauthority"); exists(a) {
s.XAuthority = a
}
return s, nil
}
if len(sockets) > 1 {
return Session{}, fmt.Errorf("no process of this account names its X display, and there are %d X sockets in %s: which one is the operator's session cannot be told", len(sockets), x11Sockets)
}
return Session{}, fmt.Errorf("no graphical session of this account is running on this machine: no process of the account has DISPLAY set, and there is no X socket in %s. A desktop tool acts only while the operator is logged in to the graphical session", x11Sockets)
}