pub struct DeviceAction {
pub id: &'static str,
pub name: &'static str,
pub description: &'static str,
pub keystroke: DeviceKeystroke,
pub prerequisite: Option<&'static str>,
}Expand description
One thing an addon names and the device does, with no addon running.
ADR-0021. Discord’s Toggle Mute is the case this exists for: it is a global keybind, so a device that sends it controls Discord from the background — at the login screen, inside a full-screen game, and with Nobble closed. That is Principle V and ADR-0007, and a stronger promise than any addon can otherwise make.
Binding one writes a HID binding. Addon::perform is never called, the
addon need not be running, and it need not ever have been allowed to run —
ADR-0021 exempts these from the permission grant, because the grant’s only
lever is do not start the process and a keystroke in flash starts none.
That is defensible only because what the device will send is disclosed when
the key is bound and frozen there.
§Why this is not AddonAction with more fields
Because the two are not the same shape, and one struct would carry fields that are load-bearing in one regime and meaningless in the other, with nothing but a doc comment saying which.
A device-resolved action has no Trigger: a keystroke has no position to
send, so continuous is incoherent by construction rather than merely
disallowed. It has no AddonParams: a resolved binding carries no
parameters at all, so a runtime-varying one is impossible here rather than
late. ADR-0021 calls both impossible, and this is the shape in which they
cannot be written down — which is the discipline Binding already states
for itself, that the kind and the payload cannot be constructed disagreeing.
The honest limit of that: an addon written in something other than Rust can
still put "trigger" and "params" in the JSON, because serde ignores
fields it was not asked about. What it cannot do is make them mean
anything — there is nowhere for the daemon to read them from, so there is no
check to forget. That is a weaker claim than “unrepresentable” and it is the
true one.
The cost of a second list is real and is paid in one place: ids share one
namespace with Addon::actions, because a binding stores one string and
cannot say which list it came from. A collision has no type to prevent it,
and the daemon must refuse it by name rather than guess — otherwise binding
the device-resolved twin resolves to the host-resolved one, which is the
“silently fall back to a host-resolved call when the daemon happens to be
running” the contract forbids outright. Worth knowing that ids within
Addon::actions are not checked for uniqueness today either, and
Registry::perform takes the first match; this makes an existing unenforced
invariant load-bearing rather than inventing a new hazard.
§A separate list here is not a separate group in the interface
ADR-0021 requires these to appear in the addon’s action list alongside the
host-resolved ones, reported per row rather than segregated, because grouping
by mechanism is what FR-024b forbids in the very addon that will hold both
families. Nothing here decides that: the daemon assembles one list for the
interface out of both, the way it already computes applies per row rather
than reading it from a declaration. What an author writes and what a user
reads have never been the same shape.
Fields§
§id: &'static strStable identifier, and what the binding records as provenance so that
removing the addon reports the binding broken rather than leaving a bare
keystroke nobody can explain (003-FR-066 with 003-FR-047). It must
not change once anyone could have bound it, and it must not collide with
an AddonAction::id on the same addon.
name: &'static strWhat to call it in a menu. The whole reason not to leave this as a
hand-configured HID binding: the daemon’s own summary of a raw tap is
Ctrl+Shift+usage 0x10, which is accurate and unexplainable.
description: &'static strWhat it does, in a sentence.
keystroke: DeviceKeystrokeWhat the device sends by default. The user can change it, and must be able to, because it has to match whatever they set in the other application and only they know what that is.
Read once, when the key is bound, and never re-resolved on load (ADR-0021). So a later version of the addon cannot change what an already-bound key types — and a wrong default is wrong for everyone who already bound it, with no upgrade path short of rebinding. That is the right way round: the alternative is an addon update silently altering a key the user authored.
prerequisite: Option<&'static str>The step outside Nobble that has to happen for this to work, if there is one: “Set this keybind in Discord: User Settings > Keybinds.”
Free text, shown at the moment of binding, and never parsed — which
is what lets it also carry a suggested chord in prose without that
suggestion becoming a grammar with a compatibility surface of its own.
The day something reads a keystroke out of this string, this field has
quietly become the interface DeviceKeystroke exists to be instead.
None where there is nothing to arrange elsewhere, which is the case a
future device-resolved play/pause is in. An Option rather than an
empty string, because the two states oblige the interface differently:
Some means it must not claim the key works until the user confirms the
step (FR-024), since Nobble cannot read another application’s settings
and must say so rather than pretend. A sentinel would make the author
left it blank and there is nothing to do the same value.
Trait Implementations§
Source§impl Clone for DeviceAction
impl Clone for DeviceAction
Source§fn clone(&self) -> DeviceAction
fn clone(&self) -> DeviceAction
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for DeviceAction
impl Debug for DeviceAction
Source§impl PartialEq for DeviceAction
impl PartialEq for DeviceAction
Source§fn eq(&self, other: &DeviceAction) -> bool
fn eq(&self, other: &DeviceAction) -> bool
self and other values to be equal, and is used by ==.