Files
mesh-catalog/modules/xorg/cmd/xorg/input.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

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_:+(),.-]+$`)