Skip to main content

DeviceKeystroke

Enum DeviceKeystroke 

Source
pub enum DeviceKeystroke {
    Tap {
        key: u8,
        ctrl: bool,
        shift: bool,
        alt: bool,
        gui: bool,
    },
    Consumer {
        usage: u16,
    },
}
Expand description

One keystroke, exactly as the device will send it.

§Why this crate spells the HID vocabulary again

It has no choice. Nothing here may depend on another Nobble crate — the rule at the top of this module, and FR-044 behind it — so nobble_core::HidAction is unreachable from an addon author’s build, and Constitution VI’s preferred answer, generated bindings, does not cross a repository boundary that exists on purpose (FR-043, FR-049). The alternative to restating the shape is not restating it somewhere better; it is being unable to declare a keystroke at all, and then FR-024 has no answer.

What is a choice is how much gets restated and how the copy is held in step. These variants, their field names and their serde spelling in KeystrokeDecl match nobble_rpc::ActionDto::HidTap and HidConsumer one for one, so the object crossing this pipe is the same JSON text a binding is saved as, and the daemon’s conversion is a rename-free match a reader can check by eye. A test in nobble-service — the only crate that can see both — pins that, and stands in for the generated binding.

That is weaker than one definition and is recorded as such rather than argued away. Nothing makes a new HidAction variant a compile error here, because Cargo cannot see across the boundary. Two things bound the damage: the drift is asymmetric — a variant added there narrows what this can say, a variant added here breaks the daemon’s exhaustive bridge — and what is copied is USB HID’s vocabulary rather than Nobble’s, so it is not a format anyone here is free to change.

§Why exactly these two, and why not MIDI

The cut is made by an existing function rather than by judgement. ActionDto::from_binding has arms for HidTap and HidConsumer, and its _ => return None covers key sequences and mouse movement, which have no on-disk form — so an addon declaring one would declare a binding check_supported refuses to save, and it fails the whole file rather than the one key.

The narrower reading matters as much. One chord cannot type a string, and a sequence is precisely the mechanism the contract rules out when it says an addon able to ask the device to send anything at any time “would be a keylogger with extra steps”. Excluding it in the type leaves no check for anyone to forget.

MIDI is left out on different grounds, and the omission is not a claim that device-resolved means keyboard. Binding::Midi is device-resolved too and a control change is inherently continuous, so resolution and trigger are genuinely independent axes. But midi_note and midi_cc are already first-class binding kinds with their own editors, so there is no discoverability gap for an addon to close. Adding a variant later is additive; adding one now is speculative.

Deliberately not #[non_exhaustive] — the opposite choice from ParamKind, for the reason that separates them: an unfamiliar parameter kind has a useful fallback, a text box, and an unfamiliar keystroke has none. A wildcard arm here is how a device silently sends nothing. Adding a variant should fail to compile in the daemon that has to translate it, which is what Request says for itself.

Variants§

§

Tap

Press and release one key, with modifiers held.

What is declared here is a suggestion the user is expected to change, and an addon that treats it as a contract has misunderstood the field. Discord ships no keybind for Toggle Mute — the user invents one — so whatever is declared has to be transcribed into Discord by hand anyway. Self::Consumer is the opposite case: there the addon knows the answer.

Fields

§key: u8

A HID usage code, not a character — the same distinction HidKey makes on the daemon side, for the same reason: a keystroke authored on a QWERTZ layout and pressed on a QWERTY host produces a different character, and hiding that makes it invisible until somebody complains. Usage 0x10 is the key labelled M on ANSI and the key labelled , on AZERTY, so an addon’s choice of chord can collide with something on a layout its author never saw. That is what the editor is for.

§ctrl: bool

Held with it.

§shift: bool

Held with it.

§alt: bool

Held with it.

§gui: bool

Held with it — Windows, Command, Super.

§

Consumer

A Consumer Control usage — the media keys.

Here because ADR-0021 intends media to gain a device-resolved play/pause as a new action, and calls its case stronger than Discord’s: a consumer usage is layout-free and needs nothing arranged elsewhere, so the addon genuinely knows the number and the declaration is a fact rather than a suggestion. A type that could not say so would have made the adoption impossible.

Fields

§usage: u16

The usage.

Trait Implementations§

Source§

impl Clone for DeviceKeystroke

Source§

fn clone(&self) -> DeviceKeystroke

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 DeviceKeystroke

Source§

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

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

impl PartialEq for DeviceKeystroke

Source§

fn eq(&self, other: &DeviceKeystroke) -> 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 DeviceKeystroke

Source§

impl Eq for DeviceKeystroke

Source§

impl StructuralPartialEq for DeviceKeystroke

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.