Two questions that had been answered by one capability. graphical-session asks whether a session is running now; seat asks whether one could ever run here. Assignment needs the second -- a headless server can never have a display server, a workstation with nothing installed yet can, and until now those looked identical. The mesh would have assigned xorg to the server and found out at apply time. "seat" rather than "display", and the distinction matters most on a phone. An Android device plainly has a screen and has no seat: nothing there is going to take a DRM device and present an X or Wayland session on it. A capability called display would answer yes and be useless. This one answers no, which is true and useful. Read from what the kernel reports about its connectors, which distinguishes the two ways of not having one: a machine with a graphics card and nothing plugged in is a different thing from a machine with no graphics at all, and somebody deciding where a desktop goes wants to know which they are looking at. Verified against this workstation: it reports card1-DP-1 and card1-DP-2, which are the two monitors actually connected, and ignores the DisplayPort and HDMI that are not -- and the writeback connector reporting "unknown", which counting would have handed a seat to machines that have none.
91 lines
3.2 KiB
Go
91 lines
3.2 KiB
Go
package profile
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A seat is somewhere a display server could run: a graphics device with a display attached.
|
|
//
|
|
// **Not the same question as `graphical-session`**, and the difference is what makes it worth
|
|
// having. That one asks whether a session is running *now*, which is state; this asks whether one
|
|
// could ever run here, which is hardware. Deciding whether to assign a display server needs the
|
|
// second — a headless server can never have one, a workstation with nothing installed yet can,
|
|
// and until this existed those two looked identical. The mesh would have assigned xorg to the
|
|
// server and found out at apply time.
|
|
//
|
|
// **`seat` rather than `display`, and the distinction matters most on a phone.** An Android
|
|
// device plainly has a screen and has no seat: nothing there is going to take a DRM device and
|
|
// present an X or Wayland session on it. A capability called `display` would answer *yes* and be
|
|
// useless; `seat` answers *no*, which is the true and useful answer.
|
|
//
|
|
// The term is logind's, and it means what is wanted here: a set of hardware one person sits at.
|
|
|
|
// SeatSource is where the kernel reports what is attached. A variable so a test can point it at a
|
|
// directory it made, rather than at whatever this machine happens to have.
|
|
var SeatSource = "/sys/class/drm"
|
|
|
|
type seat struct{}
|
|
|
|
func (seat) Name() string { return CapSeat }
|
|
|
|
func (seat) Detect(context.Context) Verdict {
|
|
const how = "/sys/class/drm/*/status — a connector the kernel reports as connected"
|
|
|
|
entries, err := os.ReadDir(SeatSource)
|
|
if err != nil {
|
|
// No DRM subsystem at all: a container, a phone, a machine with no graphics stack. Said
|
|
// as what was looked for rather than as an error, because this is the ordinary answer on
|
|
// most nodes and not a fault on any of them.
|
|
return Verdict{
|
|
Name: CapSeat, Present: false,
|
|
Detail: "no graphics devices are present (" + SeatSource + " is not readable)",
|
|
How: how,
|
|
}
|
|
}
|
|
|
|
var connected, disconnected []string
|
|
for _, e := range entries {
|
|
status, err := os.ReadFile(filepath.Join(SeatSource, e.Name(), "status"))
|
|
if err != nil {
|
|
// Cards themselves have no status file; only connectors do. Skipping is right and
|
|
// not a failure to report.
|
|
continue
|
|
}
|
|
switch strings.TrimSpace(string(status)) {
|
|
case "connected":
|
|
connected = append(connected, e.Name())
|
|
default:
|
|
disconnected = append(disconnected, e.Name())
|
|
}
|
|
}
|
|
sort.Strings(connected)
|
|
|
|
if len(connected) > 0 {
|
|
return Verdict{
|
|
Name: CapSeat, Present: true,
|
|
Detail: strings.Join(connected, ", "),
|
|
How: how,
|
|
}
|
|
}
|
|
if len(disconnected) > 0 {
|
|
// The distinction worth drawing: this machine has graphics hardware and nothing plugged
|
|
// into it. A server with a GPU and no monitor, which is a different thing from a machine
|
|
// with no graphics at all — and a person deciding where a desktop goes wants to know
|
|
// which they are looking at.
|
|
return Verdict{
|
|
Name: CapSeat, Present: false,
|
|
Detail: "a graphics device with no display attached",
|
|
How: how,
|
|
}
|
|
}
|
|
return Verdict{
|
|
Name: CapSeat, Present: false,
|
|
Detail: "no display connectors are present",
|
|
How: how,
|
|
}
|
|
}
|