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.
168 lines
8.0 KiB
Go
168 lines
8.0 KiB
Go
// xorg's tools (novox/hq ADR 0208, research 026/05): node-display-server's verbs `displays` and
|
|
// `layout`, and the module's own tools for one output's mode, the primary output, DPI, input devices,
|
|
// the keyboard map, a screenshot and the server's log.
|
|
//
|
|
// Every tool that touches the screen acts in the operator's running session, which it finds through
|
|
// internal/desktop: the node's runtime has none of its own. With no session, the answer says so as
|
|
// structured data. A tool that changes the running server changes it until the next login; the
|
|
// answer says what makes it last.
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
|
|
"xorg/internal/desktop"
|
|
)
|
|
|
|
// The session's holders this module prefers as the session's own process.
|
|
var sessionHolders = []string{"i3", "sway", "openbox", "bspwm", "awesome", "xmonad"}
|
|
|
|
func main() {
|
|
if err := stdio.Serve("", tools(desktop.Machine(sessionHolders...))); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
// call bounds one tool call below the runtime's 30 s limit.
|
|
func call(run func(ctx context.Context, a desktop.Args) (any, error)) func(map[string]any) (any, error) {
|
|
return func(args map[string]any) (any, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
|
|
defer cancel()
|
|
return run(ctx, desktop.Args(args))
|
|
}
|
|
}
|
|
|
|
func tools(d desktop.Desk) []stdio.Tool {
|
|
x := xorg{d: d}
|
|
return []stdio.Tool{
|
|
{
|
|
Name: "node-display-server.displays",
|
|
Description: "The X screen and every output: connected or not, primary, where it sits, its rotation, " +
|
|
"physical size and DPI, the monitor's identity from its EDID (manufacturer, product, serial, name, " +
|
|
"fingerprint), the current and preferred mode, and every mode with its rates. Disconnected outputs " +
|
|
"are listed by name only unless all is true. Also the autorandr profile in force, when one matches.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"all": desktop.Flag("list disconnected outputs in full (default false)"),
|
|
"modes": desktop.Flag("list every mode of each output (default true)"),
|
|
}),
|
|
Run: call(x.displays),
|
|
},
|
|
{
|
|
Name: "node-display-server.layout",
|
|
Description: "Monitor layout profiles (autorandr), keyed by the connected monitors' identities, so one " +
|
|
"profile needs no machine's name. list: every profile with the monitors it is for, which match " +
|
|
"the monitors connected now and which is in force. save: the current arrangement under a name " +
|
|
"(replace true to overwrite). apply: load one. The profiles are the operator's data, never the mesh's.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"action": desktop.Enum("list, save or apply", "list", "save", "apply"),
|
|
"name": desktop.Str("the profile to save or apply"),
|
|
"replace": desktop.Flag("save over an existing profile of that name (default false)"),
|
|
}, "action"),
|
|
Run: call(x.layout),
|
|
},
|
|
{
|
|
Name: "xorg_set_mode",
|
|
Description: "Change one output now, through xrandr: its mode (WxH, or auto for the preferred one), rate, " +
|
|
"rotation, scale, position (x/y, or beside another output), on or off, primary. dry_run answers " +
|
|
"the command without running it. Lasts until the next login or hotplug: save a layout profile to keep it.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"output": desktop.Str("the output, as displays names it (e.g. eDP-1)"),
|
|
"mode": desktop.Str("WxH, or auto for the monitor's preferred mode"),
|
|
"rate": desktop.Num("refresh rate in Hz"),
|
|
"rotate": desktop.Enum("rotation", "normal", "left", "right", "inverted"),
|
|
"scale": desktop.Num("scale factor, 0.25 to 4 (1 = none)"),
|
|
"x": desktop.Int("left edge on the screen, in pixels"),
|
|
"y": desktop.Int("top edge on the screen, in pixels"),
|
|
"relation": desktop.Enum("place it beside another output instead of at x/y", "right-of", "left-of", "above", "below", "same-as"),
|
|
"of": desktop.Str("the output the relation is to"),
|
|
"off": desktop.Flag("switch the output off"),
|
|
"primary": desktop.Flag("make it the primary output"),
|
|
"dry_run": desktop.Flag("answer the command, run nothing"),
|
|
}, "output"),
|
|
Run: call(x.setMode),
|
|
},
|
|
{
|
|
Name: "xorg_primary",
|
|
Description: "Which output is primary (the one the bar's tray and new windows prefer); with output, make it primary now.",
|
|
Input: desktop.Schema(map[string]any{"output": desktop.Str("the output to make primary (optional)")}),
|
|
Run: call(x.primary),
|
|
},
|
|
{
|
|
Name: "xorg_dpi",
|
|
Description: "The DPI X programs are told (Xft.dpi) beside each monitor's physical DPI. With value, set " +
|
|
"Xft.dpi and the screen's DPI now: programs started after it use it. The module's resources file " +
|
|
"sets it again at the next login (96 until it is a setting).",
|
|
Input: desktop.Schema(map[string]any{"value": desktop.Int("dots per inch, 48 to 480 (optional)")}),
|
|
Run: call(x.dpi),
|
|
},
|
|
{
|
|
Name: "xorg_input_devices",
|
|
Description: "The input devices the X server knows: id, name, pointer or keyboard, master or slave, and " +
|
|
"for each pointer its libinput settings (tap, natural scroll, acceleration, disable while typing, " +
|
|
"left handed, enabled).",
|
|
Input: desktop.Schema(map[string]any{"name": desktop.Str("only devices whose name contains this (optional)")}),
|
|
Run: call(x.inputDevices),
|
|
},
|
|
{
|
|
Name: "xorg_input_set",
|
|
Description: "Change libinput settings of a device now: tap, tap_drag, natural_scroll, disable_while_typing, " +
|
|
"left_handed, middle_emulation, enabled (true/false), accel_speed (-1 to 1). The device is an id or a " +
|
|
"name; a name applies to every device of that name that has the setting. Lasts until the device " +
|
|
"reconnects or the next login.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"device": desktop.Str("the device's id or exact name"),
|
|
"tap": desktop.Flag("tap to click"),
|
|
"tap_drag": desktop.Flag("tap and drag"),
|
|
"natural_scroll": desktop.Flag("natural (reversed) scrolling"),
|
|
"disable_while_typing": desktop.Flag("ignore the touchpad while typing"),
|
|
"left_handed": desktop.Flag("swap the buttons"),
|
|
"middle_emulation": desktop.Flag("both buttons together are the middle one"),
|
|
"enabled": desktop.Flag("the device takes input at all"),
|
|
"accel_speed": desktop.Num("pointer acceleration, -1 to 1"),
|
|
}, "device"),
|
|
Run: call(x.inputSet),
|
|
},
|
|
{
|
|
Name: "xorg_keyboard",
|
|
Description: "The X keyboard map: rules, model, layout, variant and options. With layout, variant, model " +
|
|
"or options, set them now (options replace the current ones; an empty list clears them). Lasts " +
|
|
"until the next login.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"layout": desktop.Str("e.g. us, be, us,be"),
|
|
"variant": desktop.Str("e.g. intl"),
|
|
"model": desktop.Str("e.g. pc105"),
|
|
"options": desktop.List("e.g. [\"caps:escape\", \"compose:ralt\"]"),
|
|
}),
|
|
Run: call(x.keyboard),
|
|
},
|
|
{
|
|
Name: "xorg_screenshot",
|
|
Description: "Take a screenshot to a PNG file: the whole screen, one output, or one window by its X id. " +
|
|
"Written under the account's home (default ~/Pictures/Screenshots/screenshot-<time>.png). Answers " +
|
|
"the path, size and bytes, never the image.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"output": desktop.Str("only this output (optional)"),
|
|
"window": desktop.Str("only this window, by its X id, e.g. 0x3a00007 (optional)"),
|
|
"path": desktop.Str("where to write it, inside the home (optional)"),
|
|
}),
|
|
Run: call(x.screenshot),
|
|
},
|
|
{
|
|
Name: "xorg_x_log",
|
|
Description: "The X server's log for the running session (or the newest one): its version, when it " +
|
|
"started, and its errors — with warnings when asked — newest last.",
|
|
Input: desktop.Schema(map[string]any{
|
|
"warnings": desktop.Flag("include warnings (default false)"),
|
|
"limit": desktop.Int("at most this many lines (default 100, at most 1000)"),
|
|
}),
|
|
Run: call(x.xlog),
|
|
},
|
|
}
|
|
}
|