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.
138 lines
3.9 KiB
Go
138 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Device is one input device the X server knows.
|
|
type Device struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
Role string `json:"role"` // master or slave
|
|
Kind string `json:"kind"` // pointer or keyboard
|
|
Settings map[string]any `json:"settings,omitempty"`
|
|
}
|
|
|
|
var deviceLine = regexp.MustCompile(`^[^A-Za-z0-9]*(.+?)\s+id=(\d+)\s+\[(master|slave|floating)\s+(pointer|keyboard)?`)
|
|
|
|
// ParseDevices reads `xinput list`, the tree's drawing characters stripped.
|
|
func ParseDevices(text string) []Device {
|
|
var out []Device
|
|
sc := bufio.NewScanner(strings.NewReader(text))
|
|
for sc.Scan() {
|
|
m := deviceLine.FindStringSubmatch(sc.Text())
|
|
if m == nil {
|
|
continue
|
|
}
|
|
id, _ := strconv.Atoi(m[2])
|
|
kind := m[4]
|
|
if kind == "" {
|
|
kind = "floating"
|
|
}
|
|
out = append(out, Device{ID: id, Name: strings.TrimSpace(m[1]), Role: m[3], Kind: kind})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Knob is one setting input-set changes, and the libinput property that holds it.
|
|
type Knob struct {
|
|
Arg, Property, Kind string // Kind: bool or float
|
|
}
|
|
|
|
// Knobs are what input-set may change, by libinput's own property names (libinput(4)).
|
|
var Knobs = []Knob{
|
|
{"enabled", "Device Enabled", "bool"},
|
|
{"tap", "libinput Tapping Enabled", "bool"},
|
|
{"tap_drag", "libinput Tapping Drag Enabled", "bool"},
|
|
{"natural_scroll", "libinput Natural Scrolling Enabled", "bool"},
|
|
{"disable_while_typing", "libinput Disable While Typing Enabled", "bool"},
|
|
{"left_handed", "libinput Left Handed Enabled", "bool"},
|
|
{"middle_emulation", "libinput Middle Emulation Enabled", "bool"},
|
|
{"accel_speed", "libinput Accel Speed", "float"},
|
|
}
|
|
|
|
var propLine = regexp.MustCompile(`^\s+(.+?) \(\d+\):\s*(.*)$`)
|
|
|
|
// ParseProps reads `xinput list-props` into the settings Knobs name, as booleans and numbers. A
|
|
// device without a property (a keyboard has no tapping) simply lacks the setting.
|
|
func ParseProps(text string) map[string]any {
|
|
byProp := map[string]string{}
|
|
sc := bufio.NewScanner(strings.NewReader(text))
|
|
for sc.Scan() {
|
|
if m := propLine.FindStringSubmatch(sc.Text()); m != nil {
|
|
byProp[m[1]] = strings.TrimSpace(m[2])
|
|
}
|
|
}
|
|
out := map[string]any{}
|
|
for _, k := range Knobs {
|
|
v, ok := byProp[k.Property]
|
|
if !ok {
|
|
continue
|
|
}
|
|
first := strings.TrimSpace(strings.Split(v, ",")[0])
|
|
switch k.Kind {
|
|
case "bool":
|
|
out[k.Arg] = first == "1"
|
|
case "float":
|
|
if f, err := strconv.ParseFloat(first, 64); err == nil {
|
|
out[k.Arg] = f
|
|
}
|
|
}
|
|
}
|
|
if v, ok := byProp["libinput Accel Profile Enabled"]; ok {
|
|
profiles := []string{"adaptive", "flat", "custom"}
|
|
for i, bit := range strings.Split(v, ",") {
|
|
if strings.TrimSpace(bit) == "1" && i < len(profiles) {
|
|
out["accel_profile"] = profiles[i]
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// Keyboard is the X keyboard map as setxkbmap reports it.
|
|
type Keyboard struct {
|
|
Rules string `json:"rules,omitempty"`
|
|
Model string `json:"model,omitempty"`
|
|
Layout string `json:"layout,omitempty"`
|
|
Variant string `json:"variant,omitempty"`
|
|
Options []string `json:"options"`
|
|
}
|
|
|
|
// ParseKeyboard reads `setxkbmap -query`.
|
|
func ParseKeyboard(text string) Keyboard {
|
|
k := Keyboard{Options: []string{}}
|
|
sc := bufio.NewScanner(strings.NewReader(text))
|
|
for sc.Scan() {
|
|
key, value, ok := strings.Cut(sc.Text(), ":")
|
|
if !ok {
|
|
continue
|
|
}
|
|
value = strings.TrimSpace(value)
|
|
switch strings.TrimSpace(key) {
|
|
case "rules":
|
|
k.Rules = value
|
|
case "model":
|
|
k.Model = value
|
|
case "layout":
|
|
k.Layout = value
|
|
case "variant":
|
|
k.Variant = value
|
|
case "options":
|
|
for _, o := range strings.Split(value, ",") {
|
|
if o = strings.TrimSpace(o); o != "" {
|
|
k.Options = append(k.Options, o)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return k
|
|
}
|
|
|
|
// xkbName is what setxkbmap accepts as a layout, variant, model or option: letters, digits and the
|
|
// punctuation XKB names use. Anything else is refused before it reaches a command line.
|
|
var xkbName = regexp.MustCompile(`^[A-Za-z0-9_:+(),.-]+$`)
|