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 194 deletions
-194
View File
@@ -1,194 +0,0 @@
# Native niri and Noctalia integration for VoidKontrol v1
Research date: 2026-09-21. Research question: [Establish the native niri and Noctalia integration contracts](https://git.bongbetic.com/xavierk/voidkontrol/issues/3).
## Result
The requested integration is feasible without systemd. The installed and current stable baselines are **niri 26.04** and **Noctalia 5.1.0**. Noctalia 5 is the native Wayland/C++ shell with **Luau plugins**, not the older Quickshell/QML implementation. A first-class v1 can ship a normal Wayland Carbon application plus a small Noctalia plugin exposing a bar widget, quick panel, and optional control-center shortcut. All hardware requests go through the same unprivileged CLI/service API as the main application. A tray icon is an optional compatibility surface, not a substitute for that plugin. [N1][N2][S1][S2][S3]
This report resolves the available interfaces and recommends a concrete scope; it does not claim a working implementation, verified keyboard writes, or user acceptance of a UI prototype. The existing hardware specification's authentication, backup, serialization, readback, and no-auto-write invariants remain binding. The user has selected the verified USB presentation first and retained the v1 exclusions for firmware flashing and factory-reset UI.
## Evidence and version limits
Current documentation was discovered with Context7 (`library` before `docs`, three commands for Noctalia and two for niri). Claims below were checked against upstream sources, mostly pinned to the installed release tags. Noctalia documentation is also present in its own release tree; that is more reliable for this baseline than old links referring to `noctalia-shell`. The niri 26.04 documentation still mentions the older Quickshell Noctalia in its desktop-shell examples, so the Noctalia repository itself owns the current Noctalia interface facts. [N1][S1]
Read-only host observations:
| Observation | Result and implication |
| --- | --- |
| `niri --version`, `niri msg version`, XBPS package version | Binary and running compositor both report `26.04 (unknown commit)`; installed package is `niri-26.04_1`. CLI/compositor versions currently agree. |
| `noctalia --version`, XBPS package version | `noctalia v5.1.0`; installed package `noctalia-5.1.0_1`. The package repository is a local `void-packages/hostdir/binpkgs` directory, so this is not evidence of an official Void repository package. |
| Noctalia command discovery | `noctalia msg --help` exposes `plugin`, `plugins`, `panel-toggle`, `settings-open-plugin`, `status`, and `theme-mode-get`. `noctalia plugins --help` exposes offline plugin linting. |
| Session environment | Wayland session, current desktop `niri`, session D-Bus address and `NIRI_SOCKET` present. Exact socket paths are deliberately omitted. |
| Relevant niri configuration | Included configuration files; Noctalia starts with `spawn-at-startup "noctalia"`. Existing bindings use `noctalia msg ...`. No host files were changed. |
| Relevant session launcher | Existing user session wrapper uses `dbus-run-session -- niri --session` under a session supervisor and validates the login/runtime directory. It is a local wrapper, not evidence that upstream `niri-session` supports runit. |
| Relevant Noctalia preferences | Built-in/community application-template rewriting is disabled; the shell's polkit agent is enabled. Do not assume application color-file generation is configured. |
| Notification D-Bus reads | `GetServerInformation` reports Noctalia `5.1.0`, protocol `1.2`; `GetCapabilities` advertises `actions`, `activation-token`, `body`, `persistence`, and `inline-reply`. No test notification was sent. |
| Portal D-Bus reads | `org.freedesktop.portal.Settings` version `2`; `ReadOne("org.freedesktop.appearance", "color-scheme")` returns `1` (dark). `noctalia msg theme-mode-get` also returns `dark`. This verifies the present setting, not live change propagation. |
| Upstream release metadata | Latest stable releases queried during research are Noctalia `v5.1.0` (2026-09-10) and niri `v26.04` (2026-04-25). [N1][S1] |
No user configuration, package installation, theme, panel, compositor, notification, or hardware setting was modified. No keyboard input, window titles, clipboard, notification history, or unrelated personal settings were collected into this report.
## Session and lifecycle contract
1. **Keep the device owner independent of the desktop.** The system device service belongs to the Void/runit packaging decision. It must not require `WAYLAND_DISPLAY`, a user's D-Bus session, niri IPC, or Noctalia. Desktop components are clients; restarting the shell cannot start a second hardware owner or implicitly apply settings. This preserves the supplied specification's ownership invariant.
2. **Use the existing user's Wayland session.** Void documents `dbus-run-session` when a session bus does not already exist, and elogind/turnstile for `XDG_RUNTIME_DIR`; do not nest a new session bus inside VoidKontrol. Niri documents `niri --session` for init systems other than systemd/dinit. Upstream's `niri-session` adds systemd/dinit supervision and must not be assumed to be the Void/runit entry point. [V1][N2]
3. **Noctalia already has a supported startup path:** `spawn-at-startup "noctalia"`. Its own documentation recommends compositor autostart. The plugin loads inside that process; it needs no separate runit or systemd user unit. [S4]
4. **The main GUI opens on demand.** A desktop entry, launcher search, niri binding, and plugin Open action should converge on the same single-instance `open` operation. Do not start a hidden full GUI just to display the native Noctalia widget. A daemon already running or a GUI being closed is not a reason to reapply a desired keyboard configuration.
5. **Do not claim XDG autostart alone works on stock niri/runit.** Niri's automatic XDG autostart documentation is specifically tied to its systemd session targets. Provide an optional, validated niri configuration snippet when a session component actually needs startup. Do not install systemd units as a v1 requirement. [N3]
The session must supply a working session D-Bus bus and valid user-owned runtime directory. Missing prerequisites yield a readable desktop diagnostic; they do not prevent headless CLI/device-service use. Void's Wayland guide documents toolkit-specific backends: GTK normally chooses Wayland, while Qt requires its Wayland package/backend. The chosen GUI toolkit must be proven native on this Void baseline during the architecture/UI ticket. [V1][V2]
## niri application and keybinding contract
### Identity and launch
Choose one stable reverse-DNS application ID before publishing packages. Use it consistently as the Wayland `xdg_toplevel` app ID, desktop-entry basename, icon identity, and notification `desktop-entry` hint. Wayland recommends matching the app ID to the `.desktop` basename; Noctalia resolves icons by desktop ID and then `StartupWMClass`. Do not attempt to identify the application by localized window titles. [W1][S6]
The final public ID is an architecture/package-identity decision, not established by this report. A real candidate under a maintainer-controlled namespace can be selected there; this research does not invent ownership of an `org.voidkontrol` domain.
Proposed example binding, with the key itself left to the user's configured choice:
```kdl
binds {
Mod+K hotkey-overlay-title="Open VoidKontrol" {
spawn "voidkontrol" "open";
}
}
```
This is an example, not an edit or a claim that `Mod+K` is unoccupied. Niri's `spawn` takes separate arguments and does not invoke a shell. Use it for fixed commands. Do not set `allow-when-locked=true` for device-editing or UI shortcuts. Provide a configuration snippet for review and validate the resulting configuration instead of replacing the user's files. [N4]
Niri matches `app-id` with regular expressions; exact sample window rules must escape dots and anchor both ends. The app should tile normally by default, resize sensibly, and retain usable controls at narrow column sizes; forcing floating, global blur, or a workspace assignment is not required for native support. Niri exposes the observed app ID with its window queries. [N5]
### Activation and IPC
Use the chosen GUI toolkit's normal single-instance/Wayland activation path. `XDG_ACTIVATION_TOKEN` is a supported token handoff for a newly launched process; consume it correctly and do not propagate it indefinitely. The compositor may reject an ineffective token, so never steal focus in response to a background device event. [W2]
Niri IPC is a Unix socket named by `NIRI_SOCKET`; requests/replies use newline-delimited JSON. `niri msg --json` is a supported thin wrapper. Human-readable output is explicitly unstable; JSON is intended to remain compatible while adding fields/variants. The Rust `niri-ipc` crate does not promise semver API stability independent of niri. [N6]
No ongoing niri event stream is necessary for a keyboard manager. If standard activation cannot focus an existing window on the baseline, a narrowly scoped **user-initiated** niri adapter can find the exact app ID with JSON windows and request `focus-window --id`; missing/stale windows must degrade to opening the application. This fallback must never become a prerequisite for hardware access or be used after an unsolicited reconnect. Noctalia's niri guide suggests `honor-xdg-activation-with-invalid-serial` for shell activation, but VoidKontrol should not automatically change this global compositor setting. Validate the normal launch/focus path before deciding whether a fallback is needed. [N6][S4]
Firmware Base/Fn/Fn1/Fn2 layers are keyboard state, not niri/XKB layout groups. A hardware mapping to a key chord is not itself a new niri action. Keep firmware key assignments, compositor shortcuts, and the host's language layout clearly distinguished; no background edits of niri input configuration are necessary.
## Native Noctalia plugin contract
### Supported implementation surface
Noctalia 5 plugins use `plugin.toml` and isolated Luau entry scripts. Each entry runs off the UI thread with per-call budgets; plugins remain trusted user code, not a security sandbox. The manifest supports `[[widget]]`, `[[shortcut]]`, `[[panel]]`, `[[service]]`, `[[launcher_provider]]`, and `[[desktop_widget]]`. A plugin entry is addressed as `author/plugin:entry`. [S2][S3]
Recommend these four entries, sharing a single state source:
| Entry | Minimal v1 responsibility |
| --- | --- |
| Bar widget | Show keyboard availability/busy/error and battery only when actually available. Click opens the quick panel; tooltip explains unauthenticated, unsupported, disconnected, permission-denied, or stale state. |
| Quick panel | Read-only device/connection/status summary; verified LED enable and five-level brightness controls; a small verified effect selector; Open VoidKontrol action for the full editor. All mutating controls are capability-gated. |
| Control-center shortcut | Optional user-placeable tile opening the same quick panel. Do not duplicate a second implementation of the controls or overwrite existing shortcuts. |
| Background service entry | Obtain one cached CLI/service snapshot and publish it with `noctalia.state` to all visible entries. Multiple monitors/widgets must not multiply USB transactions. |
The bar widget uses `barWidget.*`; a control-center tile uses `shortcut.*`; the panel uses native `ui.button`, `ui.toggle`, `ui.slider`, `ui.select`, and `panel.render`. `noctalia.togglePanel(id)` opens it in-process. The public `noctalia msg panel-toggle author/plugin:quick` command makes the same panel bindable from niri. Services have no output and are targeted with `all` for plugin IPC. [S3][S5][S6][S7]
Panel buttons/toggles must not optimistically report a hardware success. Show pending state until the common service reports a successful readback. Disable writes when offline, unauthenticated, unsupported, lacking a required initial backup, unauthorized, or busy. Commit a brightness slider once per deliberate selection/drag completion; never send one hardware write per pixel of pointer motion. Present the protocol's five levels, not a falsely continuous 0–100 hardware scale. Unknown battery is “Unavailable”, not 0%.
Keep full mapping, macro authoring, import/restore, and detailed conflict/error recovery in the main Carbon application, backed by the same API. The quick panel is a convenient subset; its lack of a macro editor does not remove that feature from v1.
### Version and execution contract
Noctalia 5.1.0 source supports plugin API levels **3–30**. API levels are cumulative and independent of the shell release number. Recommend `plugin_api = 24`: it includes direct argv execution (`noctalia.runAsync({ ... }, callback)`), service lifecycle support, relative modules, and the panel controls required here. Raise it only if the final plugin actually uses newer features. Mainline examples using `plugin_api = 3` do not authorize calling later APIs under that declaration. [S8]
Use direct argv for CLI requests; never interpolate a device ID, effect, path, setting, or user text into a shell command. Check both the immediate launch boolean and the callback result: `exitCode`, `timedOut`, stdout/stderr truncation, bounded JSON parsing, and protocol compatibility. A manifest `dependencies` entry is descriptive metadata and does not prevent enabling a plugin with a missing executable; the plugin must detect that state itself. [S3][S6]
A small initial implementation can poll a **cached** `status --json` snapshot once every two seconds through the singleton service entry, at most one request in flight; polling that endpoint must not force a new USB read. Refresh immediately after a completed explicit command. This is a proposed implementation choice, not a measured latency claim. It avoids another long-lived session process and requires no new shell-service dependency. If the shared service already supplies a bounded event/watch CLI, reuse that instead. Noctalia supports `runStream()` and terminates streams when the entry stops/reloads, but the documented line callback alone does not establish an EOF/reconnect contract; do not assume one without testing/source verification. [S6]
An absent CLI, stopped daemon, incompatible API/schema, timeout, malformed response, failed authentication, unplug, and suspend/resume must become explicit non-writable states. Retry **reads** with a bounded cadence; do not retry uncertain writes, queue offline writes, or apply a saved setting when Noctalia restarts. Stale snapshots must carry age/validity so a cached “connected” icon cannot persist indefinitely.
### Installation and lifecycle
Noctalia can load plugins from a packaged read-only **path source**, from a user data-directory plugin, or from a git source. Installed is not enabled; enabling also does not place a bar widget automatically. This fits a separate optional VoidKontrol Noctalia integration package plus documented explicit enable/place steps, preserving the user's bar and shortcut layout. The exact XBPS split is owned by the Void packaging ticket. [S9]
Prefer the packaged path source for the v1 release baseline so plugin and CLI compatibility track the same package update. Do not silently opt the user's shell into a new remote code source or change its global plugin auto-update preference. Plugin state is process-lifetime data, not the authoritative keyboard configuration; desired hardware state, backups, and restore stay with the core application/service. `noctalia.pluginDataDir()` is appropriate only if the plugin needs its own presentation settings/cache; runtime code directories are not writable state. [S6][S9]
Noctalia's `onEnable()` is an explicit enable hook, not an ordinary startup hook. Do not start/stop the system device daemon from it or from plugin teardown. A shell crash or plugin disable must not affect normal keyboard input or abort the service's consistency handling. [S3]
### Existing lookalike features do not own Swarm75 control
Noctalia's standard `keyboard-backlight-*` commands operate its UPower keyboard-backlight devices; they do not implement the Swarm75 authenticated vendor HID protocol. Its `keyboard-layout` entry concerns the host layout. Its `power-*` commands concern system power profiles. These cannot replace the VoidKontrol adapter, firmware layers, or keyboard idle-sleep settings. Noctalia's tray widget does support StatusNotifierItem, but that provides an icon/menu surface rather than the native plugin panel and controls requested here. [S10][S11]
## Theme, notifications, and accessibility
**Main application:** retain Carbon's component/tokens/accessibility system and map the desktop's light/dark preference to its supported themes. The standard Settings portal reports `0` no preference, `1` dark, `2` light and emits `SettingChanged`. Prefer the toolkit's standard integration; an explicit bridge should use `ReadOne` on portal version 2, with documented version-1 compatibility if supported. Unknown values become no preference. A deterministic application default and manual preference keep the UI usable without a portal. The final Carbon theme pair and GUI technology belong to the design/architecture ticket. [W3]
**Noctalia plugin:** use the shell's native controls, fonts, sizing, and semantic palette roles so the bar/panel remain native. Roles resolve live, including a dark shell while apps are light. `noctalia.isDarkMode()` reports the **shell** mode; `noctalia msg theme-mode-get` reports the **app-facing** mode. Noctalia deliberately separates `[theme].mode` and `shell_mode`; treating them as identical is incorrect. Do not copy a wallpaper palette over Carbon's semantic color values automatically or rewrite global GTK/Qt themes. The host has application-template generation disabled. [S5][S6][S12]
Noctalia exposes `theme_mode_changed` and `colors_changed` hooks if a later explicit integration needs them. They are not required when the toolkit/portal preference works and must not overwrite the user's existing hooks. A theme event must only change presentation; it must never recolor the keyboard automatically. [S13]
**Notifications:** ordinary application notifications use the session's `org.freedesktop.Notifications`; Noctalia is already the provider on this host. Probe server capabilities before relying on actions. Treat a missing daemon, DND, suppressed toast, or unsupported action as a normal condition; show errors/transaction results in the originating UI as well. The root device service must not guess which user's session bus should receive a notification. [S14]
Plugin `noctalia.notify()` / `notifyError()` are suitable for brief plugin feedback. They are internal toasts, **never retained in notification history**; standard external notifications can be retained. Avoid duplicate GUI-plus-plugin toasts for one command, and avoid sending macro contents or complete exported settings into notification history. Noctalia does not advertise body-markup support. [S6][S14]
**Accessibility:** the native panel has standard controls, keyboard navigation, Escape dismissal, host UI scaling and high-contrast settings. Use visible labels and text status alongside icons/color. Do not intercept broad keys or enable a persistent focus-grabbing panel. Noctalia's documented plugin surface does not by itself prove screen-reader semantics for the proposed controls; validate that separately and keep all operations available in the accessible Carbon GUI and CLI. Screen-reader support must not be claimed merely because a native widget rendered. [S5][S15]
## Proposed shared CLI boundary
Names below are **proposals for the architecture ticket**, not implemented commands or a final published API. Keep one versioned machine-readable contract for the main GUI and shell plugin; do not expose raw HID frames or a bypass flag through it.
| Operation | Proposed surface | Required semantics |
| --- | --- | --- |
| Open or focus editor | `voidkontrol open` | Unprivileged, single-instance, no hardware write; works without Noctalia. |
| Cached state | `voidkontrolctl status --json` | Schema version, device token, capability/readiness state, snapshot age/revision, verified current values, optional battery; distinguish missing values from zero. |
| Brightness | `voidkontrolctl lighting brightness 0..4 --device <token> --json` | Exact device, locally validated discrete value; service enforces authorization, backup, serialization, and readback. |
| LED enable | `voidkontrolctl lighting enabled true\|false --device <token> --json` | Explicit desired state instead of a racy client-side toggle. |
| Effect | `voidkontrolctl lighting effect <capability-key> --device <token> --json` | Service-owned logical capability identifier; no guessed raw self-defined/colour-mode values. |
Mutation requests need a device/session or state revision precondition defined by the core API, so a stale control cannot target a newly connected identical-looking keyboard. If discovery returns multiple indistinguishable devices, show an explicit device choice and disable mutation until selected. Shell widgets must not silently select the first device. Error responses should distinguish unsupported transport/firmware, authentication, permission, missing backup, busy/conflict, disconnected, and uncertain transaction outcome; process exit success must mean the specified verification completed.
The plugin controls are clients of the service's policy, not a second policy implementation. Full CLI mutation semantics, request identifiers, IPC authentication and restore reconciliation remain core architecture decisions.
## Recommended v1 release acceptance
1. On the supported Void/runit session, launch the GUI from a desktop entry, Noctalia launcher, niri binding and native plugin. Observe the same app ID/icon, a native Wayland surface, one application instance, usable narrow-column/fractional-scale layout, and reliable user-initiated focus. Repeat with Noctalia absent: GUI and CLI remain functional.
2. Enable the bundled plugin on Noctalia 5.1.0 without a Quickshell dependency or systemd commands. Add its bar widget and control-center shortcut through normal shell settings. Open/close the quick panel with both pointer and `noctalia msg panel-toggle ...`; test multi-monitor placement and keyboard navigation. Run `noctalia plugins lint` against the package.
3. Widget and panel reflect **service-verified** device status and supported battery/lighting data. Multiple widget instances share one snapshot source. All controls accurately reflect missing capabilities, permission errors, disconnected devices, no initial backup and busy transactions.
4. Exercise one verified lighting action through each frontend and show consistent readback state. Brightness has exactly five hardware levels. Noctalia's host keyboard-backlight, language-layout and host power-profile features remain distinct from Swarm75 control.
5. With recorded transport fixtures and then validated hardware, prove no writes on GUI launch, plugin load/reload, shell restart, device reconnect, portal/theme change, suspend/resume, or passive polling. Uncertain writes require recovery/readback rather than blind replay. Typing remains available throughout.
6. Kill/restart the GUI and Noctalia independently; disable/re-enable the plugin; stop/restart the device service; unplug/reconnect the keyboard; deliver stale/truncated/incompatible CLI output. Display bounded, recoverable non-writable states, create no duplicate device owners, and never resurrect an old desired setting automatically.
7. Test light/dark changes and the deliberately different shell/app mode case. Main GUI retains Carbon semantics; plugin tracks Noctalia's live palette. Test high contrast, non-color status, keyboard access, scale and screen-reader behavior; record any native-shell limitation instead of declaring accessibility from appearance.
8. Test notifications with Noctalia available, unavailable and in DND. A suppressed notification cannot hide a transaction error from its originating UI. Inspect history behavior for external versus internal notifications, and ensure no sensitive macro/configuration payload is emitted.
9. Record exact niri, Noctalia, plugin API, CLI schema and Void package versions in release evidence. New major Noctalia/plugin API combinations are not automatically supported. Incompatibility must leave the standalone application/CLI usable.
## Decisions still requiring the wider map
- Adopt the recommended Carbon main application plus native Noctalia quick controls, and validate that interpretation through the requested UI/design decision. Forcing Carbon-rendered widgets inside Noctalia would be a different custom UI project; Noctalia's supported plugin API renders its native controls.
- Select GUI/runtime and public application identity, then prove Wayland launch, activation, resizing, portal theme propagation and accessibility on Void. Research proves the interfaces exist, not that every possible Carbon wrapper implements them correctly.
- Lock the shared authenticated IPC/CLI schema and device-selection/concurrency policy, then implement the plugin against it. Desktop research must not decide privileged authorization independently from the daemon architecture.
- Decide package/artifact ownership for the optional Noctalia plugin and document enable/place steps. No extra desktop-choice permission is needed to research or package the user's requested niri/Noctalia baseline; changing their running desktop configuration is a separate installation action.
## Primary sources
- [N1] [niri 26.04 release](https://github.com/niri-wm/niri/releases/tag/v26.04).
- [N2] [niri 26.04 Getting Started](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/Getting-Started.md).
- [N3] [niri 26.04 integration/autostart](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/Integrating-niri.md) and [startup configuration](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/Configuration%3A-Miscellaneous.md).
- [N4] [niri 26.04 keybindings and spawn semantics](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/Configuration%3A-Key-Bindings.md).
- [N5] [niri 26.04 window rules and app-ID matching](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/Configuration%3A-Window-Rules.md).
- [N6] [niri 26.04 IPC and compatibility](https://github.com/niri-wm/niri/blob/v26.04/docs/wiki/IPC.md); exact focus/spawn flags also checked with installed CLI help.
- [S1] [Noctalia 5.1.0 release](https://github.com/noctalia-dev/noctalia/releases/tag/v5.1.0) and [native shell architecture](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/README.md).
- [S2] [Noctalia 5.1.0 plugin development overview](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/index.mdx).
- [S3] [Noctalia 5.1.0 manifest](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/manifest.mdx) and [entry lifecycle/presentation APIs](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/entries.mdx).
- [S4] [Noctalia 5.1.0 compositor startup](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/getting-started/running-the-shell.mdx) and [niri integration](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/compositor-settings/niri.mdx).
- [S5] [Noctalia 5.1.0 declarative controls, panel focus and keyboard handling](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/declarative-ui.mdx).
- [S6] [Noctalia 5.1.0 runtime API](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/runtime-api.mdx).
- [S7] [Noctalia 5.1.0 plugin workflow and IPC targets](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/workflow.mdx).
- [S8] [Noctalia 5.1.0 API history](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/plugin-api.json), [API source bounds](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/src/scripting/plugin_api.h), and [compatibility rules](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/development/plugin-api.mdx).
- [S9] [Noctalia 5.1.0 plugin sources, enablement, updates and file locations](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/plugins/index.mdx).
- [S10] [Noctalia 5.1.0 UPower keyboard-backlight implementation](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/src/system/keyboard_backlight_service.cpp) and [control-center shortcuts](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/control-center/shortcuts.mdx).
- [S11] [Noctalia 5.1.0 StatusNotifierItem tray](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/bar/widgets/tray.mdx).
- [S12] [Noctalia 5.1.0 app versus shell mode](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/theming/index.mdx), [palette roles](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/theming/palette.mdx), and [theme IPC](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/ipc/media-and-ui.mdx).
- [S13] [Noctalia 5.1.0 event hooks](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/automation/hooks.mdx).
- [S14] [Noctalia 5.1.0 notification daemon, capabilities, filters and history](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/services/notifications.mdx).
- [S15] [Noctalia 5.1.0 shell accessibility and keyboard navigation](https://github.com/noctalia-dev/noctalia/blob/v5.1.0/docs/user/configuration/shell.mdx).
- [V1] [Void Handbook: session and seat management](https://docs.voidlinux.org/config/session-management.html) ([source](https://github.com/void-linux/void-docs/blob/master/src/config/session-management.md)).
- [V2] [Void Handbook: Wayland and native applications](https://docs.voidlinux.org/config/graphical-session/wayland.html) ([source](https://github.com/void-linux/void-docs/blob/master/src/config/graphical-session/wayland.md)).
- [W1] [Wayland xdg-shell app-ID protocol](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/stable/xdg-shell/xdg-shell.xml) ([readable mirror used](https://github.com/wayland-mirror/wayland-protocols/blob/main/stable/xdg-shell/xdg-shell.xml)).
- [W2] [Wayland xdg-activation protocol](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/blob/main/staging/xdg-activation/xdg-activation-v1.xml) ([readable mirror used](https://github.com/wayland-mirror/wayland-protocols/blob/main/staging/xdg-activation/xdg-activation-v1.xml)).
- [W3] [Settings portal source specification](https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.Settings.xml).
{"content_type":"terminal","tokens_before":6926,"tokens_after":6926,"token_count_basis":"o200k_base","ratio":0,"basis":"inferred"}
+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"}