Author SHA1 Message Date
Codex 7927506e02 Research native Void Linux requirements for VoidKontrol 2026-09-21 05:08:36 +00:00
2 changed files with 384 additions and 278 deletions
-278
View File
@@ -1,278 +0,0 @@
# IBM Carbon desktop UI strategy for VoidKontrol
Research date: 2026-09-21. Decision input for
[Establish a faithful IBM Carbon desktop UI strategy for Void Linux](https://git.bongbetic.com/xavierk/voidkontrol/issues/4).
This report recommends a direction and records evidence; it does not select the
final stack, establish hardware support, or provide a prototype.
## Recommendation
Evaluate **official Carbon React in an unprivileged Tauri desktop application**
first. It combines Carbon's maintained component implementation with Void's
packaged GTK3/WebKitGTK stack and leaves the device service responsible for all
hardware control. Gate selection on a small native-Wayland, accessibility, and
packaging proof on the actual supported Void targets. This is an architectural
recommendation, not a completed compatibility test. [C1][T1][T2][V1]
Electron remains a viable Carbon rendering alternative if the project accepts
owning a maintained Electron distribution/update route. Do not base that choice
on the mere existence of Void's `electron35` source template: the inspected
template is explicitly marked broken. A QML main application can implement Carbon,
but it would own a new Carbon implementation instead of consuming IBM's maintained
React or Web Components libraries. Reserve that extra cost for a demonstrated
requirement that the webview approach cannot satisfy. [C1][E1][E2][V2][Q1]
A Noctalia plugin and the full editor have different responsibilities. The full
editor should use Carbon; the shell plugin should use Noctalia's supported API and
components and expose a small entry point/status surface. They can share the
device service without sharing a rendering toolkit. Exact Noctalia API choices
belong to the separate desktop-integration research.
## Existing behavior and boundaries
The supplied [control specification](../../KREO-SWARM75-LINUX-CONTROL-SPEC.md)
requires an unprivileged UI, a single service owner for discovery/authentication/
serialization/readback, a complete backup before the first write, and comparison
of readback after every update. It prohibits grabbing input devices, detaching
the kernel driver, and automatic writes on startup/reconnect/resume. Protocol
payloads and persistence semantics still require authenticated hardware capture.
The user has chosen verification of the observed USB presentation first and has
kept firmware flashing and the factory-reset UI outside v1. A GUI library cannot
close any of those protocol evidence gaps. The display should initially use the
specification's `Kreo Swarm 75 (signature match)` identification until successful
authentication; the observed VID/PID does not establish physical layout or
connection-mode compatibility.
The repository contains a specification and agent documentation, with no existing
UI implementation or architecture decision to extend at this research checkout.
Consequently, the comparisons below do not propose replacing a working stack.
## Maintained Carbon support and obligations
1. Carbon's official developer guide lists **React and Web Components** as
officially supported. Other web frameworks have community implementations.
The maintained monorepo includes component styles, design tokens, icons, grid,
spacing, motion, and typography packages. It does not list an official Qt/QML
implementation. This is evidence about the supported offerings, not a claim
that no third-party QML project exists. [C1][C2]
2. `@carbon/react` supplies components, styles, and icons and requires a frontend
build pipeline. A static bundled frontend is sufficient for this desktop
editor; a web server framework is not required by Carbon. Official Web
Components are a legitimate alternative if the architecture decision finds a
concrete reason to avoid React; they still require a web rendering engine.
[C1][C2]
3. Carbon's themes are `white`, `g10`, `g90`, and `g100`. Use semantic theme,
spacing, typography, focus, and layer tokens instead of approximate custom
colors. Offer a supported light/dark choice; use Carbon's layering mechanism
for nested surfaces. Shell theme synchronization can select a Carbon theme,
but arbitrary wallpaper colors should not silently replace its contrast and
semantic color relationships. The latter is a product recommendation. [C3]
4. Carbon uses IBM Plex. Its current guide recommends per-family packages such
as `@ibm/plex-sans` and `@ibm/plex-mono`; the legacy monolithic `@ibm/plex`
package is no longer updated. Bundle the required fonts locally so installed
operation does not depend on a font CDN. Carbon is Apache-2.0; Plex is SIL
OFL-1.1 with reserved name `Plex`. Include the applicable notices and license
texts in the distribution. [C2][C4][C5]
5. Carbon components follow IBM's accessibility checklist, based on WCAG AA,
Section 508, and European standards. Carbon explicitly says accessible
components are only part of an accessible product. Test the assembled product
in the selected Linux renderer, including screen-reader integration, rather
than treating component adoption as application certification. [C6]
## Desktop choices
| Choice | Carbon fidelity and ownership | Linux/Void evidence | Decision cost or gate |
| --- | --- | --- | --- |
| Carbon React + Tauri | Reuses official components; small native IPC bridge can call the service. | Tauri uses WebKitGTK on Linux; its windowing stack uses GTK3. Wry documents the GTK route for Wayland. Void packages the required GTK3/libsoup3 WebKit 4.1 variant with Wayland enabled. [T1][T2][V1] | Build/runtime dependencies and WebKit behavior depend on the distribution. Test Carbon rendering, AT-SPI/screen reader operation, dialogs, scaling, and window activation on niri. |
| Carbon React + Electron | Reuses the same official components in bundled Chromium. | Upstream Linux binaries are built on Ubuntu; Electron documents native Wayland selection from version 38 when `XDG_SESSION_TYPE=wayland`. Void's inspected `electron35` template has musl build paths but is marked broken. [E1][E3][V2] | Ships Chromium/Node/Electron updates with the application. Establish a supported runtime distribution, musl route if needed, and package maintenance; the distro template is insufficient proof. |
| Qt Quick/QML with a custom Carbon style | Uses Qt controls and explicitly recreates Carbon appearance, layout, states, and interactions. | Qt provides native controls, accessibility metadata, and custom styling. Its listed styles do not include Carbon. [Q1][Q2] | Own the custom Carbon component set, visual parity, accessibility behavior, token mapping, and upstream design changes. Similarity to Noctalia's toolkit does not remove this work. |
A smaller Tauri application bundle would **not** prove a smaller total installed
system or better memory/startup performance: GTK and WebKit dependencies still
exist. No benchmark was run. Conversely, Electron's bundled renderer can reduce
renderer-version variability but creates direct runtime update responsibility;
Electron supports the latest three stable major releases. [T1][E2]
### glibc and musl
- Upstream Tauri now includes Alpine/musl prerequisites, warning that some static
dependencies may need source builds. Therefore, a blanket claim that Tauri
cannot run on musl would be incorrect. Those Alpine instructions are **not a
Void compatibility result**. [T3]
- Void's `libwebkit2gtk41` template at
`954278b83979958bcfdff05f22cf699c9dc20346` is version `2.50.4_1`, enables
Wayland, defaults to a bubblewrap sandbox, and depends on `xdg-dbus-proxy` when
that option is enabled. It includes musl-compatible build paths rather than a
blanket musl prohibition. `libwebkitgtk60` is the GTK4 subpackage; it is not a
drop-in substitute for Tauri's GTK3 WebKit 4.1 requirement. Package source
availability alone does not prove a successful application build. [V1][T2]
- Void's `electron35` template at the same revision is `35.7.2_1`, has
`x86_64* aarch64*` architecture patterns and `musl-legacy-compat` paths, but is
marked `broken` because its configure tooling uses `FancyURLopener` removed in
Python 3.14. This disproves neither all Electron-on-musl possibilities nor the
possible existence of an older binary package; it does mean that this template
cannot establish a reproducible, maintained current runtime. [V2]
- Treat glibc and musl as separate build and runtime targets. A glibc binary or
AppImage must not be presented as proof of native musl support. Tauri's own
Linux distribution guidance warns that the build system's libc baseline affects
compatibility. Which libc and architectures v1 promises remains an architecture
decision informed by the Void research. [T4]
### Security and process boundary
Both wrappers can preserve the service boundary in the supplied specification.
Keep packaged UI assets local, render imported names as text, and expose only
typed domain operations through the desktop bridge. Neither JavaScript nor a QML
plugin should accept arbitrary HID frames, open `/dev/hidraw*`, run arbitrary shell
commands, or gain service credentials. The service must independently authorize
and validate requests; hiding a control is not authorization.
For Tauri, capability/permission configuration constrains webview-to-core calls,
while core/plugin code itself has the application's OS privileges. Command
implementations remain responsible for enforcing scopes. This is a separate
boundary from the service's own peer authentication and device policy. [T5]
For Electron, retain renderer sandboxing and context isolation, disable Node
integration in the renderer, validate IPC sender and arguments, constrain
navigation, and use a restrictive content-security policy. Its security guidance
also explicitly assigns responsibility for updating Electron, Chromium, Node, and
dependencies to the application maintainer. [E4]
## Proposed interaction requirements
These are design recommendations derived from the supplied protocol invariants,
not additional asserted keyboard capabilities. The service remains authoritative
for advertised features, verified ranges, physical key identifiers, raw-value
translation, and transaction state.
### Main organization and keyboard accessibility
Use a compact Carbon application shell with Device, Lighting, Key mapping,
Macros, Typing and power, and Backups as ordinary navigation destinations. Use
standard forms for editors and a restrained amount of custom rendering for the
physical keyboard preview. This is information architecture for later review,
not a locked wireframe.
The layout preview should use the authenticated physical layout map and identify
each key by its position, legend, selected layer, and current assignment. A
keyboard-accessible list/form view must offer the same selection and editing
operations. The preview cannot be the only means of choosing a key, and color
cannot be the only indication of selection, assignment, or errors. A labelled
hex/RGB input or equivalent text entry should accompany any color picker.
For layers, Carbon Tabs can expose Base, Fn, Fn1, and Fn2. Carbon supplies arrow-key
navigation and distinguishes automatic versus manual activation. Choose manual
activation if changing tabs causes a perceptible read delay; switching the editor
layer must not itself write settings or claim to change the keyboard's active
hardware layer. [C7]
All functionality must be reachable without dragging or mouse-only gestures:
visible focus, logical tab order, standard activation keys, Escape for transient
surfaces, and focus restoration after dialogs. Preserve a path out of every custom
control. Provide move-up/down controls if macro actions are reorderable. Avoid
stealing ordinary typing or compositor shortcuts just to select physical keys.
Carbon's accessibility guidance explicitly requires full keyboard access and
testing beyond automated checks. [C6][C8]
### Readback, changes, and failures
Keep three concepts separate: the last configuration read from the device, the
user's unapplied edits, and the service's current transaction status. Recommended
visible states are:
| Condition | UI behavior |
| --- | --- |
| No device, unsupported signature, unavailable service, or permission failure | Show a persistent, specific status with the relevant recovery action. Do not present cached state as live device state. |
| Authenticating or reading | Identify the operation and withhold hardware writes until the service declares them safe. |
| Edits pending | Clearly show unapplied changes and an explicit Apply action. Updating a form or selecting an effect changes local intent only. |
| Backing up, writing, or verifying | Show those actual stages. Prevent duplicate submission; do not call a sent command “Saved.” |
| Readback matches | Show “Applied and verified.” Claim persistence across power cycles only if the protocol/hardware evidence establishes it. |
| Timeout, disconnect, malformed reply, or mismatch | Preserve useful edit context, mark device state uncertain, and show a persistent error. Reconnection/readback precedes an explicit retry; no blind automatic replay. |
Carbon InlineLoading supplies active/finished/error states and recommends
disabling associated actions during submission. Inline notifications persist,
and error messages should state the user action that resolves the problem. Use
these patterns for operation status; a fleeting toast is insufficient for a failed
hardware update. [C9][C10]
### Feature editors
| Area | v1 editor implications from the supplied specification |
| --- | --- |
| Lighting | Only capability-backed effects; five brightness/speed levels; monochrome/colourful; LED enable; one custom per-key frame. Use protocol-owned mappings for self-defined mode and colour-mode raw IDs. Do not invent direction controls or multiple onboard lighting profiles. |
| Key mapping | Four layers; physical keys only; keyboard/modifier/combination/media/mouse/disabled/macro choices where verified. Show current and proposed assignments; flag unknown readback without silently replacing it. |
| Macros | List the 32 slots; edit ordered press/release/delay actions with visible bounds and slot usage. Limit to 112 actions, 0–65,535 ms delays, and 127 encoded name bytes. Confirm the name encoding before implementing byte counting; “127 characters” is not equivalent. Offer only verified execution modes and validated parameter ranges. |
| Typing | Debounce choices 0/10/20/30/40 ms. Tap-delay UI must await its verified range/units. Do not expose unsupported NKRO, report-rate, OS-mode, Win-lock, or Hall-effect controls. |
| Power | Display battery/charging only when valid data is available; unknown is not zero. Sleep uses the stated discrete choices, not a free-form unbounded duration. |
| Backups and restore | Export a versioned, protocol-independent document; show source identity, timestamp, compatibility checks, and a change preview before explicit restore. Validation and a fresh pre-write backup belong to the service. Do not promise atomic restore unless evidence establishes it; identify partial/interrupted outcomes. |
A macro action form is sufficient for complete authoring. If a later decision adds
recording, it must remain an explicit focused interaction with an obvious stop
path and must obey the specification's ban on input-device grabs. Global keyboard
recording is not required by the supplied capabilities and should not be added as
an implicit interpretation of “complete control.” Never execute a macro merely
because a user previews its steps. Layer reset, if included, needs separate
verified protocol behavior and explicit user intent; factory reset and firmware
flashing remain excluded.
## Smallest sufficient proof before choosing the stack
The later architecture/prototype decision should evaluate one packaged Carbon
form, layer tabs, a modal, a representative long macro list, the labelled keyboard
selector, and simulated service states. It does not need hardware writes.
1. Build/install/uninstall on the intended Void libc/architecture targets with
declared runtime dependencies. Confirm local fonts/assets work without network
access and record exact dependency versions.
2. Launch as a native Wayland window on niri; verify app identity, keyboard focus,
normal resizing, fractional scaling, dialogs/file selection, and behavior when
launched from the shell integration. A successful XWayland fallback does not
satisfy a native-Wayland claim.
3. Operate every representative control by keyboard; test meaningful reading and
actions with the target Linux accessibility stack, including modal focus and
operation-status announcements. Carbon's upstream tabs screen-reader evidence
names JAWS, VoiceOver, and NVDA, so it is not a substitute for a Linux/Orca test.
[C7]
4. Exercise disconnected, unauthenticated, pending, verifying, and failed states
against a fake service; prove no simulated write occurs on launch, navigation,
refresh, or reconnect. Confirm duplicate Apply is blocked and stale drafts
cannot overwrite a newly read configuration without review.
Unresolved product decisions are the supported libc/architecture matrix, wrapper
selection, theme synchronization policy, and the preferred editor interaction.
Unresolved hardware facts include the layout, name encoding, tap-delay parameters,
transaction persistence, and individual capability read/write validation. These
are explicit decision/proof obligations, not missing Carbon documentation.
## Sources and retrieval
Context7 was used first for Carbon, Tauri, Electron, and Qt (`library` then
`docs`, no more than three commands for each question). Findings were checked
against the following primary documentation/source. No application was built,
no packages were installed, no host configuration was changed, and no device I/O
was performed for this report.
- [C1] [Carbon developer introduction](https://carbondesignsystem.com/developing/get-started/) and [React guide](https://carbondesignsystem.com/developing/frameworks/react/).
- [C2] [Carbon official repository, packages and license](https://github.com/carbon-design-system/carbon/blob/main/README.md).
- [C3] [Carbon themes](https://github.com/carbon-design-system/carbon/blob/main/packages/themes/README.md), [Theme component](https://github.com/carbon-design-system/carbon/blob/main/packages/react/src/components/Theme/Theme.mdx), and [Layer component](https://github.com/carbon-design-system/carbon/blob/main/packages/react/src/components/Layer/Layer.mdx).
- [C4] [Carbon IBM Plex guide](https://github.com/carbon-design-system/carbon/blob/main/docs/guides/ibm-plex.md).
- [C5] [IBM Plex license](https://github.com/IBM/plex/blob/master/LICENSE.txt).
- [C6] [Carbon accessibility overview](https://carbondesignsystem.com/guidelines/accessibility/overview/).
- [C7] [Carbon Tabs accessibility](https://carbondesignsystem.com/components/tabs/accessibility/).
- [C8] [Carbon accessibility verification guide](https://github.com/carbon-design-system/carbon/blob/main/docs/guides/accessibility.md). Its old IBM-internal DAP setup instructions are not adopted as a project tool requirement.
- [C9] [Carbon InlineLoading usage](https://carbondesignsystem.com/components/inline-loading/usage/).
- [C10] [Carbon notification usage](https://carbondesignsystem.com/components/notification/usage/) and [Form usage](https://carbondesignsystem.com/components/form/usage/).
- [T1] [Tauri webview versions](https://v2.tauri.app/reference/webview-versions/).
- [T2] [Wry Wayland/GTK integration](https://github.com/tauri-apps/wry/blob/dev/README.md) and [Tao Linux GTK3 dependency](https://github.com/tauri-apps/tao/blob/dev/README.md).
- [T3] [Tauri prerequisites, including Alpine](https://github.com/tauri-apps/tauri-docs/blob/v2/src/content/docs/start/prerequisites.mdx).
- [T4] [Tauri Linux distribution/libc baseline guidance](https://github.com/tauri-apps/tauri-docs/blob/v2/src/content/docs/distribute/debian.mdx).
- [T5] [Tauri security boundaries](https://github.com/tauri-apps/tauri-docs/blob/v2/src/content/docs/security/index.mdx) and [capabilities](https://github.com/tauri-apps/tauri-docs/blob/v2/src/content/docs/security/capabilities.mdx).
- [E1] [Electron platform support](https://github.com/electron/electron/blob/main/README.md).
- [E2] [Electron version support policy and cadence](https://github.com/electron/electron/blob/main/docs/tutorial/electron-timelines.md).
- [E3] [Electron breaking changes, v38 Wayland default](https://github.com/electron/electron/blob/main/docs/breaking-changes.md).
- [E4] [Electron security guidance](https://github.com/electron/electron/blob/main/docs/tutorial/security.md).
- [V1] [Void WebKitGTK package source at inspected revision](https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/libwebkit2gtk41/template).
- [V2] [Void Electron package source at inspected revision](https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/electron35/template).
- [Q1] [Qt Quick Controls available styles](https://doc.qt.io/qt-6/qtquickcontrols-styles.html).
- [Q2] [Qt Quick accessibility](https://doc.qt.io/qt-6/accessible-qtquick.html).
+384
View File
@@ -0,0 +1,384 @@
# VoidKontrol: Void Linux runtime, access, and packaging research
Research date: 2026-09-21. Decision ticket: [Establish the Void Linux runtime,
permissions, and packaging requirements](https://git.bongbetic.com/xavierk/voidkontrol/issues/2).
This is a researched platform contract and a set of recommendations, not an
implemented or hardware-tested controller. The user's accepted boundary is the
currently observed USB presentation first, with other modes admitted only after
separate validation; firmware flashing and factory-reset UI remain outside v1.
The supplied [control specification](../../KREO-SWARM75-LINUX-CONTROL-SPEC.md)
owns protocol requirements. No packages were installed, services restarted,
udev rules changed, or device reports exchanged during this research.
## Findings that change the starting assumptions
1. **Hidraw support is already present on this host.** The running kernel reports
`CONFIG_HIDRAW=y`; the Swarm has `/dev/hidraw4` and `/dev/hidraw5`, with interface
1 at the latter path. Both are currently `0600 root:root`. The present barrier
is access policy and protocol evidence, not a demonstrated need to replace the
kernel. Node numbers are observations, never identifiers to hard-code.
See the local evidence below and [Linux hidraw documentation][hidraw].
2. **`CONFIG_HIDRAW=m` is not a valid alternative.** `HIDRAW` is a Boolean Kconfig
option; `hidraw.o` is linked into the HID core, which itself can be a module.
Consequently, failure of `modprobe hidraw` does not diagnose missing support.
The supplied spec's earlier inference required a dated correction, now
recorded in the [device evidence](../evidence/2026-09-21-swarm75.md).
[Linux Kconfig][kconfig], [HID Makefile][hidmake]
3. **Void-native does not require a permanently root daemon.** A dedicated
system account can receive access to the matching hidraw node and run under
runit's `chpst`. This preserves the spec's privileged service boundary while
restricting its privilege to device access and its private data directory.
It is a design recommendation grounded in [Void service packaging][manual]
and [chpst][chpst], not a claim that the daemon has been built.
4. **Distro availability and upstream buildability differ.** WebKitGTK 4.1 and
Qt 6 have relevant current Void templates, including musl handling. The
current `electron35` template is explicitly marked broken, even though the
host's cached repository index lists an Electron binary. Noctalia installed
here is locally packaged; the inspected upstream Void tree contains no
Noctalia template. [Package inventory](#ui-runtime-and-libc-options), local
evidence below.
## Evidence and method
The required Context7 lookup resolved `Void Linux` to the official
`/void-linux/void-docs` collection, then fetched runit service and session/seat
management documentation. Those results were checked against complete relevant
Void Handbook pages, the Void packaging manual, package source, Linux source,
and upstream eudev/runit/polkit specifications. There was no Context7 quota
failure or fallback to uncited recollection.
Package claims below refer to upstream `void-linux/void-packages` commit
[`954278b83979958bcfdff05f22cf699c9dc20346`][vp] retrieved on the research date.
The recursive tree response was not truncated. This is a source snapshot, not
a promise that every architecture's current binary mirror is synchronized.
Read-only local evidence, collected in the active session:
| Check | Observed result | Implication |
| --- | --- | --- |
| `/etc/os-release`, `uname -a`, `ps -p 1 -o comm=` | Void; x86_64; Linux `7.2.6_1`; PID 1 `runit` | This machine can validate x86_64 glibc/runit, not other targets. |
| `xbps-query -p pkgver glibc` / installed package listing | `glibc-2.41_1` | Do not describe this as a musl test. |
| `zcat /proc/config.gz` | `CONFIG_HIDRAW=y`, `CONFIG_HID=m`, `CONFIG_HID_GENERIC=m`, `CONFIG_USB_HID=m` | Hidraw is compiled into the loaded HID core. |
| `lsusb -d 258a:010c`; `/sys/class/hidraw/*/device/uevent` | `258a:010c`, `BY Tech Kreo Swarm`; hidraw4 interface 0; hidraw5 interface 1; `hid-generic` | Normal kernel HID ownership is intact. No feature ioctl was performed. |
| `ls -l /dev/hidraw4 /dev/hidraw5` | Both `crw------- root root`, no ACL indicator | The ordinary desktop user has no device access through these nodes. |
| `udevadm info --query=property --name=/dev/hidraw5` | Only `DEVNAME`, `DEVPATH`, `MAJOR`, `MINOR`, `SUBSYSTEM` | Do not assume `ID_VENDOR_ID` or `ID_USB_INTERFACE_NUM` is already populated on this hidraw node. |
| `udevadm info --attribute-walk --name=/dev/hidraw5` | `bInterfaceNumber=01` on the USB interface; VID/PID/product on the higher USB device | A single rule combining all those `ATTRS` matches cannot work; use separate parent-matching stages. |
| `loginctl show-session 2` | Local, active, `seat0`, Wayland; `LockedHint=no` | Active-session authorization can be tested here. This is not a seatd-only setup. |
| Selected environment | `XDG_CURRENT_DESKTOP=niri`, `XDG_RUNTIME_DIR=/run/user/1000`, `WAYLAND_DISPLAY=wayland-1` | UI runs in a graphical user session; system daemon must not inherit these assumptions. |
| Installed package listing / `/var/service/` | runit `2.3.1_1`, eudev `3.2.14_2`, elogind `252.39_1`, D-Bus `1.16.2_2`, polkit `127_1`; dbus and udevd enabled | Elogind is operational despite no explicit elogind service symlink, consistent with D-Bus activation. |
| Installed UI packages | GTK3 `3.24.52_1`, GTK4 `4.22.4_1`, Qt6 `6.11.2_1`, `libwebkitgtk60-2.50.4_1`, IBM Plex fonts | GTK4 WebKit being installed does not satisfy a GTK3 WebKit 4.1 dependency. |
| Installed desktop packages | niri `26.04_1`, noctalia `5.1.0_1` | Version-specific desktop integration remains a separate research decision. |
| `xbps-query -p repository noctalia` | Local `void-packages/hostdir/binpkgs` repository | This installation is not proof of an official Noctalia package. |
| `xbps-query -Rs webkit` / `electron35` | Cached index lists `libwebkit2gtk41-2.50.4_1` and `electron35-35.7.2_1` | Index metadata only; no installation or fresh mirror synchronization was performed. |
## Service lifetime and privilege
Void uses runit; packaged service directories live in `/etc/sv/`, and linking a
directory into `/var/service/` enables immediate start and subsequent supervision.
The service's executable `run` script must `exec` a foreground process. A `check`
script reports readiness, `finish` runs after termination, and a `log` subservice
can receive output. An administrative `down` file prevents automatic start.
[Void services][services]
Recommended service contract:
- Keep one system service, provisionally `voidkontrold` (the spec calls the
responsibility `swarm75d`). The UI and CLI are ordinary users. The service
runs as `_voidkontrol:_voidkontrol`, with no membership in `input`, `video`,
`wheel`, or a general hardware group. `chpst -u` removes inherited
supplementary groups. A root supervisor creating a runtime directory does
not require the long-lived service process to be root. [chpst][chpst]
- Have the `run` script create `/run/voidkontrol` with explicit owner and mode,
then change credentials and `exec` the daemon. Permanent private state belongs
under `/var/lib/voidkontrol`, created by packaging. This follows Void's
distinction between service-created volatile directories and `make_dirs`
for persistent directories. [Void packaging manual][manual]
- With D-Bus IPC, the package's system-bus policy should grant only the service
account ownership of the selected bus name. Startup waits for/retries the
system bus; do not add a second D-Bus process or depend on a user's session
bus. With a Unix socket instead, use kernel-authenticated peer credentials
and an equivalent per-call authorization check. The final IPC choice is a
decision, not a requirement imposed by Void. [Void sessions][sessions]
- Readiness means the daemon can serve a diagnostic/capability request even
when the keyboard is unplugged. Device absence should be an application
state, not a restart loop. A system service must work without niri, Noctalia,
`DISPLAY`, or `XDG_RUNTIME_DIR`; runit's clean environment makes this
separation particularly important. [Void services][services]
- Handle `SIGTERM`: stop accepting mutations, bound the wait for an outstanding
transaction, durably record any uncertain outcome, close the device and IPC,
and exit. `sv down` sends TERM and CONT; `sv stop` waits by default for seven
seconds, and force variants may kill on timeout. Do not rely on `finish` to
restore hardware or guarantee completion after power loss. [sv manual][sv]
- Let runit restart a crashed daemon, but obey the spec's no-auto-write rule:
an interrupted transaction becomes uncertain and requires fresh readback.
Restart, hotplug, login, wake, and service upgrade must never replay saved
frames or apply a profile automatically. This is a project invariant from
the supplied control spec, not distro behavior.
- Use bounded logs with an explicit logging setup. Void installs no syslog
daemon by default, while the packaging `vsv` helper creates a `vlogger`
subservice by default. Decide between that documented syslog prerequisite
and an explicit `svlogd` log subservice; do not silently depend on journald.
Logs should contain transaction metadata/errors, not keystrokes, macro
contents, complete backups, or raw transport buffers. [Void logging][logging],
[vsv documentation][manual]
## Device discovery and narrow udev access
Linux recommends discovering hidraw nodes through libudev instead of assuming a
stable `/dev/hidrawN`; hidraw feature-report ioctls use the control endpoint.
For the spec's report ID 6, a 519-byte payload requires a **520-byte ioctl
buffer**, whose first byte is report ID 6. This is a framing distinction, not
permission to send the currently unverified command payloads. Plain hidraw
`read()` receives interrupt input reports, and plain `write()` is not a
substitute for the specified feature-report exchange. [Linux hidraw][hidraw]
Required project invariants remain: no input-device grabs, no kernel HID
detach/unbind, no access to the unrelated 5-byte feature report, no arbitrary
raw-report RPC, and validation of VID/PID, product, interface, usage collection,
report IDs/sizes, and authenticated device identity before writes. A udev rule
can reduce access, but cannot replace that protocol gate or physical-layout
validation. Interface 1 also contains keyboard input reports; daemon code must
not turn device access into a general key-event collector.
**Recommended access model:** give the service account access to only the
matching interface and mediate all users through IPC. Do not grant ordinary
users membership in the service account's group or install `MODE="0666"`.
Do not add `TAG+="uaccess"` for this controller: elogind processes that tag to
grant active-user device access, which would let a GUI or unrelated user
process bypass the policy-owning daemon. Existing administrator-added device
ACLs/rules must be diagnosed; this package cannot prevent root from opening
the device. [elogind seat rule][seat-rule], [eudev rule language][udev]
The following is a **candidate rule design for later isolated testing**, not a
rule installed or verified with udev on this host:
```udev
# Example package path: /usr/lib/udev/rules.d/70-voidkontrol.rules
SUBSYSTEM!="hidraw", GOTO="voidkontrol_end"
ACTION=="remove", GOTO="voidkontrol_end"
ENV{.VOIDKONTROL_MATCH}=""
ATTRS{idVendor}=="258a", ATTRS{idProduct}=="010c", ATTRS{product}=="Kreo Swarm", ENV{.VOIDKONTROL_MATCH}="1"
ENV{.VOIDKONTROL_MATCH}=="1", ATTRS{bInterfaceNumber}=="01", GROUP="_voidkontrol", MODE="0660"
LABEL="voidkontrol_end"
```
The first matching stage evaluates attributes on the USB device; the second
evaluates the interface ancestor. Eudev requires all parent matches within a
single rule to match the same ancestor. Dot-prefixed temporary properties are
not stored in the database or exported. The rule intentionally does not assume
`ID_USB_INTERFACE_NUM` is populated, since it is absent in this host's hidraw
properties. [eudev rule language][udev], local attribute walk above.
A service-held lock prevents two VoidKontrol instances from controlling the
same keyboard, but is not a claim of kernel-enforced exclusivity against root
or another driver. Root/administrator access remains outside the ordinary-user
isolation boundary. Reconnect must rediscover and revalidate the descriptor;
USB topology and a hidraw index are not durable identity. The lack of a serial
number makes multiple identical devices a separate identity decision.
## Active-user policy, elogind, and seatd
Void distinguishes the system and per-session D-Bus buses. Elogind manages
login sessions, device access, and power; it needs system D-Bus and may activate
on demand. Seatd only manages seats. Seatd does not replace elogind's login
session information, its uaccess processing, or `XDG_RUNTIME_DIR` creation.
Void recommends elogind or turnstile for runtime-directory management and
documents a seatd plus turnstile alternative. [Void session management][sessions]
Void's current polkit template explicitly builds with
`-Dsession_tracking=elogind`. Polkit defines separate defaults for any caller,
inactive local callers, and active local callers, and provides a system-bus
unique-name subject. These are usable on Void without systemd.
[Void polkit template][polkit-pkg], [polkit policy][polkit], [polkit subject][subject]
Recommended v1 policy, pending the explicit security decision:
- Use the actual authenticated IPC sender, never a UID/PID/username supplied
in request JSON. With system D-Bus, authorize the caller's unique bus name.
The UI, CLI, and Noctalia integration use the same policy.
- Allow customization only from the active local session associated with the
keyboard's seat; deny remote, inactive, and unidentifiable callers. Recheck
authorization when a queued mutation begins, and invalidate it on session
or seat change. Read-only diagnostics may be less restricted; configuration
exports/macros contain user data and need separate privacy treatment.
- If the product must deny writes while locked, check the session lock state
explicitly and test the actual locker integration. `Active=yes` alone does
not express the desired locked-screen policy. This report's host was
unlocked; it did not validate lock-state propagation.
- Do not use a silent "any local group member" fallback when elogind or polkit
is unavailable. Report authorization unavailable, or implement an explicit
administrator-approved alternate mode after a separate security decision.
A group alone does not prove active seat ownership.
An elogind-based session is the smallest evidenced release baseline here.
Supporting a seatd-only session with identical active-user guarantees is an
additional feature requiring an independently specified authority; it is not
an automatic consequence of running on Void. This baseline recommendation does
not require replacing a working user's seat manager or altering the host.
## Storage and XBPS lifecycle
The XDG specification separates config, data, state, cache, and runtime files.
Defaults are `~/.config`, `~/.local/share`, `~/.local/state`, and `~/.cache`;
runtime storage must be user-owned mode 0700, local, and tied to login lifetime.
Only absolute XDG paths are valid. [XDG Base Directory specification][xdg]
Recommended ownership:
| Data | Location/owner recommendation | Reason |
| --- | --- | --- |
| UI preferences, selected theme | `$XDG_CONFIG_HOME/voidkontrol`, desktop user | User preferences follow XDG config semantics. |
| User-created named configurations/exports | `$XDG_DATA_HOME/voidkontrol` or explicitly chosen export path, user | Portable user data; writes performed by the client. |
| UI session/history state | `$XDG_STATE_HOME/voidkontrol`, user | Persisted application state, independent of device transactions. |
| Service backup and pending-transaction journal | `/var/lib/voidkontrol`, `_voidkontrol`, private | The service must not depend on a logged-in user's environment or accept an arbitrary root-write path over IPC. |
| Service locks/socket, if used | `/run/voidkontrol`, explicit private/service policy | Recreated each service start; no backups here. |
| User-only IPC/instance state, if needed | `$XDG_RUNTIME_DIR/voidkontrol`, user, private | Session lifetime and ownership required by XDG. |
Import validates a bounded document supplied by the client; export returns data
to the authorized client, which writes the selected user path. Do not make the
service open arbitrary paths supplied by an unprivileged caller. Restore must
validate schema, device/firmware compatibility, and capability bounds, preserve
a pre-change backup, then use the normal serialized/readback transaction path.
User data must survive an uninstall; a separate explicitly requested purge can
be considered later. These are proposed application rules supporting the
supplied spec's backup/restore requirement.
For native packaging, provide an `xbps-src` template with release source and
checksum, correct `hostmakedepends`/`makedepends`/runtime dependencies, licenses,
desktop entry/icon, daemon/CLI, service directory, udev rule, and chosen IPC
policy. Void templates are Bash descriptions compiled into XBPS packages;
`vsv` installs a service directory and supervision links, not the running
`/var/service` enablement link. Use `system_accounts="_voidkontrol"` with a
dynamically allocated UID/GID, a non-login shell, `make_dirs` for persistent
directories, and `conf_files` for administrator configuration. New system
accounts must use an underscore prefix. [Void packaging manual][manual]
The native build path is a separate `xbps-src` masterdir. The upstream README
documents `./xbps-src pkg <name>` and same-architecture musl builds via
`./xbps-src -A x86_64-musl pkg <name>`. Build as an ordinary user with the
documented chroot method. Pin source and application dependency lockfiles;
record the package tree revision and resolved dependency versions. Do not
claim bit-for-bit reproducibility merely because a template builds.
[void-packages README][vp-readme], [Cargo build style][cargo-style]
Lifecycle requirements and native behavior:
- Install must not implicitly enable hardware writes, start a GUI as root,
enroll all users in a group, or change desktop configuration. Document the
separate service-enable step. Verify account and rule availability before
starting the daemon; bootstrap the current attached device permissions by a
controlled replug or targeted administrator action during installation QA.
- XBPS **does not automatically restart updated services**. Preserve that
behavior and document the required controlled daemon restart; negotiate
IPC version compatibility so a newly installed UI can explain an old
running service. [Void XBPS handbook][xbps]
- Package removal needs an explicit tested service-stop/disable procedure,
including in-flight transaction handling and stale enablement links.
`INSTALL`/`REMOVE` scripts must distinguish real removal from upgrade and
work with alternate installation roots. Avoid unrelated host mutations or
deleting backups. [Void packaging manual][manual]
- Void's system-account trigger disables rather than deletes the service
account on real removal and does not do that for upgrades. The `mkdirs`
trigger uses `rmdir`, not recursive deletion, for package-created
directories. Preserve that non-destructive model. [Account trigger][accounts],
[mkdirs documentation][manual]
- A locally built XBPS repository can be used for initial distribution; a
remotely shared repository and packages must be signed. Official Void
repository acceptance is a separate process, not required for being a
native XBPS package. [Void repositories][repos], [signing documentation][vp-readme]
## UI runtime and libc options
Void officially supports musl as well as glibc, but software portability and
binary availability vary; i686 musl binaries and musl multilib are not offered.
Source-level compatibility is not release qualification. [Void musl handbook][musl]
| Candidate component | Current source evidence | Consequence for VoidKontrol |
| --- | --- | --- |
| Rust daemon | `rust`/`cargo` 1.98.0 templates, explicit musl handling; Void has a Cargo build style | Viable native backend candidate; build dependencies must come from the selected target toolchain. This is not a language decision. [Rust][rust], [Cargo][cargo], [build style][cargo-style] |
| Direct hidraw or HIDAPI hidraw backend | `hidapi` 0.14.0 template uses eudev/libusb build dependencies | If HIDAPI is selected, choose its hidraw backend explicitly and prove no kernel detach path exists. Direct Linux ioctls are also a viable small implementation. [HIDAPI package][hidapi-pkg], [Linux API][hidraw] |
| GTK3/WebKitGTK 4.1 web UI host | `gtk+3` 3.24.52 and `libwebkit2gtk41` 2.50.4; `libwebkit2gtk41-devel` exports `webkitgtk-4.1` headers/pkg-config; Wayland enabled | Available foundation for the Carbon researcher's Tauri option. GTK4's `libwebkitgtk60` is a different ABI, not a substitute. [GTK3][gtk3], [WebKit][webkit] |
| WebKit on musl | x86_64-musl/aarch64-musl target patterns enable JIT; sampling profiler is glibc-only; bubblewrap and xdg-dbus-proxy enabled by default | Musl is not ruled out. Validate the chosen wrapper/build, webview sandbox, accessibility, portal dialogs, and rendered Carbon components on actual Void musl before promising support. Do not disable the webview sandbox to pass tests. [WebKit][webkit] |
| Qt 6 native UI | `qt6-base` 6.11.2, Wayland enabled; separate `qt6-wayland-client`; musl-aware checks; declarative/QML packages present | Technically viable native UI, but Carbon widget fidelity/accessibility implementation cost belongs to the design decision. [Qt base][qt] |
| Qt 6 WebEngine host | Built from `qt6-pdf` 6.11.2; musl compatibility dependency; Widevine disabled on musl; word-size/architecture restrictions | Technically possible web host, but Chromium build/runtime surface is substantially larger; do not assume every Void architecture. [Qt WebEngine source][qtweb] |
| Electron | `electron35` 35.7.2 permits x86_64*/aarch64*, includes musl compatibility, but explicitly `broken` with Python >=3.14 | Existing binary availability is not a reproducible native build path. Resolve packaging and maintenance before choosing it for a native Void v1. [Electron template][electron] |
| niri and Noctalia | Upstream niri 26.04 template enables `dbus,xdp-gnome-screencast`, no systemd feature; Noctalia absent from this source snapshot, locally packaged here | Do not hard-require systemd activation or claim Noctalia is in official Void repositories. Version the supported integration separately. [niri template][niri], [source snapshot][vp], local evidence above |
Recommended release-matrix decision: qualify **x86_64 glibc + runit +
eudev + elogind + system D-Bus + the accepted niri/Noctalia versions** first,
because that is the actual available hardware environment. Keep musl support
architecturally possible and add an x86_64-musl build/smoke lane early. Whether
musl must also block the v1 release is a user-facing support promise still to
settle, not a fact inferred from "Void Linux." Other CPU architectures likewise
require an explicit matrix and evidence; none was tested here.
## Requirements and acceptance matrix
"Required" below means required by the supplied spec/user boundary or necessary
to support the selected native platform contract. "Recommended" marks a design
choice that the decision map must adopt or replace explicitly.
| Area | Requirement or recommendation | Smallest sufficient release proof | Current status |
| --- | --- | --- | --- |
| Kernel | Required: effective HIDRAW support, intact kernel typing | Running config and matched descriptor; normal typing/media/LED behavior before/after control | Config/nodes confirmed; no control test performed |
| Device access | Required: service can open only intended interface under ordinary service credentials | Install rule in disposable setup; interface 1 accessible, interface 0/unrelated devices denied; replug and node renumbering | Two-stage candidate researched, not installed/tested |
| Ownership | Required: one controller transaction at a time | Concurrent UI/CLI clients and attempted second daemon; no interleaving | Not implemented |
| Authorization | Recommended: active local caller/seat policy, fail closed | Active, inactive, remote, locked, multi-seat, denied, and missing authority cases; repeat with queued requests | Elogind active session observed only |
| Service lifecycle | Required: runit foreground process and bounded shutdown; no automatic writes | `sv` start/stop/restart; crash in each transaction phase; no-device boot; reconnect/resume | Native requirements researched |
| Backup/restore | Required: durable complete backup before first write; no uninstall data loss | Interrupted write/restart followed by validated explicit restore; install/upgrade/remove/reinstall | Awaiting protocol and implementation |
| GUI native behavior | Required for promised matrix: Wayland launch as user, no root dialog loop | Clean target installation, app launcher, scaling/input/accessibility, portal file dialogs, service-unavailable state | Runtime packages inventoried only |
| Packaging | Required: self-contained XBPS dependency declaration and reproducible recipe | Clean `xbps-src` build; clean-machine install; upgrade/old-daemon handshake; removal preserving data | No application/template exists yet |
| glibc | Recommended primary release target | Build/package/UI/service tests on x86_64 glibc and real keyboard | Host available |
| musl | Decide release promise; never label supported from compilation alone | Separate musl package build plus real graphical/service/control tests | Source plausibility established; no musl environment tested |
| Noctalia | Required integration for accepted version, independent daemon | Locally reproducible package/runtime provenance and versioned client integration | Locally packaged version observed; desktop researcher owns integration |
## Remaining decisions and implementation evidence
- Adopt the primary glibc/elogind matrix or make musl and/or seatd-only support
release blockers. The research identifies consequences; it cannot choose
this support promise on behalf of the user.
- Choose system D-Bus with existing Void polkit/elogind versus a Unix socket
with equivalent session authorization. Define locked-screen, multiple-session,
same-seat, and administrator override policy explicitly.
- Choose the UI runtime after the Carbon and desktop findings are combined.
Package source evidence supports several options; no GUI stack was built.
- Prove the proposed udev rule against real eudev events in an isolated test
setup, and specify safe package install/update/remove handling. Read-only
inspection was deliberately insufficient to claim that operational proof.
- Set durable transaction/backup format and compatibility/recovery rules;
decide identity behavior for identical no-serial devices. The protocol
capture and authenticated-device evidence remain prerequisites for writes.
[vp]: https://github.com/void-linux/void-packages/tree/954278b83979958bcfdff05f22cf699c9dc20346
[services]: https://docs.voidlinux.org/config/services/index.html
[logging]: https://docs.voidlinux.org/config/services/logging.html
[sessions]: https://docs.voidlinux.org/config/session-management.html
[xbps]: https://docs.voidlinux.org/xbps/index.html
[repos]: https://docs.voidlinux.org/xbps/repositories/index.html
[musl]: https://docs.voidlinux.org/installation/musl.html
[manual]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/Manual.md
[vp-readme]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/README.md
[accounts]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/xbps-triggers/files/system-accounts
[kconfig]: https://github.com/torvalds/linux/blob/master/drivers/hid/Kconfig
[hidmake]: https://github.com/torvalds/linux/blob/master/drivers/hid/Makefile
[hidraw]: https://docs.kernel.org/hid/hidraw.html
[udev]: https://github.com/eudev-project/eudev/blob/master/man/udev.xml
[seat-rule]: https://github.com/elogind/elogind/blob/d7956ed0b1496db7ff404d1765c31cbd9f6c7aab/rules.d/73-seat-late.rules.in
[sv]: https://smarden.org/runit/sv.8.html
[chpst]: https://smarden.org/runit/chpst.8.html
[xdg]: https://specifications.freedesktop.org/basedir/latest/
[polkit]: https://www.freedesktop.org/software/polkit/docs/latest/polkit.8.html
[subject]: https://www.freedesktop.org/software/polkit/docs/latest/PolkitSystemBusName.html
[polkit-pkg]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/polkit/template
[rust]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/rust/template
[cargo]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/cargo/template
[cargo-style]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/common/build-style/cargo.sh
[hidapi-pkg]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/hidapi/template
[gtk3]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/gtk+3/template
[webkit]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/libwebkit2gtk41/template
[qt]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/qt6-base/template
[qtweb]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/qt6-pdf/template
[electron]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/electron35/template
[niri]: https://github.com/void-linux/void-packages/blob/954278b83979958bcfdff05f22cf699c9dc20346/srcpkgs/niri/template
{"content_type":"terminal","tokens_before":7229,"tokens_after":7229,"token_count_basis":"o200k_base","ratio":0,"basis":"inferred"}