Skip to main content

DeviceAction

Struct DeviceAction 

Source
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 str

Stable 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 str

What 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 str

What it does, in a sentence.

§keystroke: DeviceKeystroke

What 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

Source§

fn clone(&self) -> DeviceAction

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for DeviceAction

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl PartialEq for DeviceAction

Source§

fn eq(&self, other: &DeviceAction) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Copy for DeviceAction

Source§

impl Eq for DeviceAction

Source§

impl StructuralPartialEq for DeviceAction

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.