Standalone architecture
Ownership
| Module | Responsibility |
|---|---|
device.py |
Verify USB identity, initialize interface 2, read hidraw, grab only native VO1060 input |
protocol.py |
Captured button and wheel pulse decoding |
pen.py |
Pen decoding, tablet-area transform, pressure-sensitive uinput pens |
proximity.py, detection_widgets.py |
Binary range transitions, packet timing, and hover diagnostics |
pressure.py, pressure_widgets.py |
Shared validated pressure response, live history, and curve preview |
displays.py |
COSMIC output geometry and app-owned per-monitor routing entries |
config.py |
Validated settings, atomic writes, version-1 migration |
output.py |
Global chords and scrolling with balanced key release |
daemon.py |
Connection lifecycle, initialization, output ownership, local IPC |
ipc.py |
Private Unix socket with same-user peer checks |
cli.py, gui.py |
Typer lifecycle/settings commands and the visual PySide6 editor |
widgets.py, ui_preferences.py |
Diagram hit areas, area dragging, shortcut picker, learned physical layout, and control names |
setup.py |
Explicit one-time device-scoped udev installation |
There is no OTD library or binary dependency. An optional handoff command can stop the existing OTD user service. Otherwise, detecting OTD prevents initialization so two drivers do not compete. Executable names are inspected without reading unrelated process command lines.
Startup and Shutdown
The daemon holds an OS file lock and owns the local socket. It discovers only 2feb:000b, checks
access, opens the vendor interface read/write, verifies identity again through HIDIOCGRAWINFO,
and sends two nine-byte output reports. Native evdev nodes are grabbed to prevent duplicate input;
no unrelated mouse or keyboard is grabbed. Initialization or grab failures close all opened handles.
Virtual keyboard/mouse creation and COSMIC pen setup run on a worker so control IPC stays responsive. A generation counter disposes stale setup results after reload/disconnect. Pen and held keyboard state are released on stop, disconnect, output replacement, and ambiguous button reports. Display changes cause output to be rebuilt. Settings UI work is also performed off the Qt UI thread.
Detached start uses the same process as foreground run in a new session, with no terminal file descriptors. Stop uses IPC rather than trusting a PID file. Ctrl+C/SIGTERM trigger cleanup. Closing the settings window leaves the daemon running. No persistent user service is installed.
Pen and COSMIC Mapping
Pen coordinates are 24-bit little-endian values; pressure is a 16-bit field with the configured maximum of 8191. Invalid out-of-range data is ignored. Native output carries tool proximity, touch, pressure, position, and two barrel buttons. A half-second report timeout clears stale pen contact. Stationary-contact behavior needs longer hardware testing; tilt is not implemented.
Single-monitor mode maps the selected tablet rectangle to that monitor. All-monitor mode maps it
to the desktop bounds and selects the virtual pen for the nearest output. The previous pen leaves
proximity before the next enters it. Each virtual device has a COSMIC map_to_output entry.
That preserves native tablet pressure rather than implementing pen movement as X11 mouse automation.
COSMIC configuration updates are atomic, backed up once, and limited to TabletKey's device-name
prefix. Existing unrelated entries are retained; malformed or concurrently edited files fail closed.
Connector geometry is queried through the installed cosmic-randr tool.
Defaults and Validation
New version-3 profiles enable pen and wheel output with linear pressure response. Shortcut buttons default to NONE. Version-1/2 profiles preserve mappings, pen options, and output pause state while adding linear pressure settings. The user-facing wheel codes do not claim calibrated clockwise/counterclockwise labels.
Automated tests use synthetic reports/fake devices and temporary settings. Real lifecycle subprocesses
use --no-device, so running tests cannot stop OTD, initialize the physical tablet, or grab its input.
Physical validation is documented separately in hardware evidence.
Visual Editor
The editor keeps hardware mappings in the version-3 settings schema, including all pen and pressure fields.
Display names, physical key order, and learned wheel orientation are UI metadata in interface.json.
Learning changes the picture/labels without changing which hardware event a binding belongs to.
Startup ignores buffered historical events when identifying controls; only new presses count.
The original MIT-licensed tablet schematic is rendered from a bundled SVG resource, with vector overlays for selection, activity, pen position, and the draggable tablet rectangle. The app does not fetch artwork at runtime. Action presets and Qt shortcut recording both produce validated bindings understood by the existing daemon. The save path validates a complete profile before updating the driver; it does not omit pen fields.
Pressure Response and Telemetry
Pressure is mapped as clamp((raw / 8191 - deadzone) / (full_input - deadzone), 0, 1) ** gamma,
then scaled back to the virtual device's 8191-count axis. Zero sensor pressure stays zero.
The same function drives native output and the response preview. Touch state uses mapped pressure,
so a deadzone cannot produce contact with zero output pressure.
The daemon retains 600 compact [sequence, timestamp, raw, mapped] samples, including short peaks
that could disappear between UI polls. The GUI keeps a bounded ten-second display history.
Payload-size tests keep snapshots beneath the IPC message limit. Raw values above the expected range
remain observable through diagnostic counters even though invalid pen reports are rejected.
The half-second pen timeout adds a zero-pressure sample and clears stale readout/contact state.
The sensor-time graph shows stored actual response; editing controls changes only the curve preview until Save. Neither graph establishes the physical force-to-sensor relationship without an external force reference. Defaults remain linear; sensitivity changes are explicit user choices.
Pen Detection
The observed exact thirteen-byte 09 41 C0 frame is a range exit. The daemon clears telemetry,
adds a zero-pressure sample, and releases virtual pen contact/proximity immediately. Valid decoded
pen reports restore detection. The shared 500 ms silence timeout remains a fallback when no exit
arrives. Disconnect releases output and marks detection unavailable.
Detection retains 128 transitions and up to 512 recent receipt times in memory. Monotonic timestamps support the binary timeline and last-report age; brief exits remain visible in counters between GUI polls. The max-gap statistic excludes deliberate out-of-range intervals. These are processing-time measurements, not hardware timestamps, distance estimates, or analogue signal measurements. No settings migration is needed; pressure responses and all existing options are preserved.
Desktop integration
Hardware decoding and keyboard/wheel output use Linux hidraw/uinput. The pen backend queries
cosmic-randr for monitor geometry and writes COSMIC-specific pen routing. With pen enabled,
a display-query failure prevents the complete output setup from finishing, including shortcuts
and scrolling. Disabling the pen avoids that backend, but use on other desktops is unverified.
Supporting GNOME, KDE, or X11 needs alternative monitor discovery/routing and physical validation. This is a desktop-integration dependency rather than a restriction of the tablet protocol.