Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions 40-streamdeck.rules
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="0063", MODE="0660",
SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="006c", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="006d", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="0090", MODE="0660", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="0fd9", ATTR{idProduct}=="0084", MODE="0660", GROUP="plugdev"
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ edition = "2018"

[features]
util = [ "structopt", "simplelog", "humantime" ]
input-manager = []
default = [ "util" ]

[dependencies]
Expand Down
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ Features:
- [x] Stream Deck Original (untested)
- [x] Stream Deck Original V2
- [x] Stream Deck XL
- [x] Stream Deck Plus (dials and touchscreen require the `input-manager` feature; setting the touchscreen image is not yet supported)
- [x] Stream Deck Module 6Keys
- [x] Stream Deck Module 15Keys
- [x] Stream Deck Module 32Keys (untested)

- [x] Optional `input-manager` as a higher level wrapper for translating raw HID reports into high-level input events. Needs to be enabled using the `input-manager` feature flag.

## Getting started

Expand Down Expand Up @@ -81,6 +82,56 @@ SUBCOMMANDS:

```


### Using the input manager

The `InputManager` provides a stateful, high-level interface for reading input from Stream Deck devices. Instead of interpreting raw HID reports yourself, it tracks button, dial, and touchscreen state and emits discrete `InputEvent` values with press/release semantics.

Enable it via the `input-manager` feature (on by default):

```toml
streamdeck = { version = "0.10", features = ["input-manager"] }
```

The `InputManager` takes ownership of the `StreamDeck` instance:

```rust
use streamdeck::{StreamDeck, InputManager};

let vendor_id = 0x0fd9; // Elgato
let product_id = 0x0084; // Stream Deck Plus

let deck = StreamDeck::connect(vendor_id, product_id, None)?;
let mut manager = InputManager::new(deck);

loop {
let events = manager.handle_input(None)?;
for event in events {
match event {
InputEvent::Button { index, action } => {
println!("Button {index}: {action:?}");
}
InputEvent::Dial { index, action } => {
// Stream Deck Plus only
println!("Dial {index}: {action:?}");
}
InputEvent::Touch { x, y, action } => {
// Stream Deck Plus only
println!("Touch at ({x}, {y}): {action:?}");
}
}
}
}
```

`handle_input` accepts an optional `Duration` timeout. It returns a `Vec<InputEvent>` which can contain any combination of:

- **`InputEvent::Button`** — a button was pressed or released (all devices)
- **`InputEvent::Dial`** — a dial was pressed, released, or turned with a signed delta (Stream Deck Plus only)
- **`InputEvent::Touch`** — a short tap, long press, or drag on the touchscreen (Stream Deck Plus only)

The manager internally tracks which buttons and dials are currently held down, so each physical press and release produces exactly one event.

## Related Works

This library stands on the shoulders of giants (who had already done all the reversing work)...
Expand Down
3 changes: 1 addition & 2 deletions src/images.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use std::str::FromStr;

use image::codecs::jpeg::JpegEncoder;
use image::ImageReader;
use image::{imageops::FilterType, Pixel, Rgba};
use image::{DynamicImage, ExtendedColorType};
use image::{DynamicImage, ExtendedColorType, ImageReader};

use crate::info::{ColourOrder, Mirroring, Rotation};
use crate::{rgb_to_bgr, Error};
Expand Down
59 changes: 59 additions & 0 deletions src/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum Kind {
Module32Keys,
}


/// Stream Deck key layout direction
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum KeyDirection {
Expand Down Expand Up @@ -52,7 +53,23 @@ pub enum Mirroring {
Both,
}

#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg(feature = "input-manager")]
pub(crate) struct TouchDataIndices {
pub event_type_index: usize,
pub x_low: usize,
pub x_high: usize,
pub y_low: usize,
pub y_high: usize,
pub drag_x_low: usize,
pub drag_x_high: usize,
pub drag_y: usize,
}

impl Kind {
///Different types of dial events that can be generated by streamdecks with dials

pub fn keys(&self) -> u8 {
match self {
Kind::Original | Kind::OriginalV2 | Kind::Mk2 => 15,
Expand Down Expand Up @@ -102,6 +119,30 @@ impl Kind {
}
}

#[cfg(feature = "input-manager")]
pub(crate) fn dials(&self) -> u8 {
match self {
Kind::Plus => 4,
_ => 0,
}
}

#[cfg(feature = "input-manager")]
pub(crate) fn dial_data_offset(&self) -> usize {
match self {
Kind::Plus => 5,
_ => 0,
}
}

#[cfg(feature = "input-manager")]
pub(crate) fn dial_press_flag_index(&self) -> usize {
match self {
Kind::Plus => 4,
_ => 0,
}
}

pub fn image_mode(&self) -> ImageMode {
match self {
Kind::Original | Kind::Mini | Kind::RevisedMini | Kind::Module6Keys => ImageMode::Bmp,
Expand Down Expand Up @@ -180,6 +221,24 @@ impl Kind {
}
}

#[cfg(feature = "input-manager")]
pub(crate) fn touch_data_indices(&self) -> Option<TouchDataIndices> {
match self {
Kind::Plus => Some(TouchDataIndices {
event_type_index: 4,
x_low: 6,
x_high: 7,
y_low: 8,
y_high: 9, //Irrelevant for SD Plus, as the touch area is only 100px high, but here for future proofing
drag_x_low: 10,
drag_x_high: 11,
drag_y: 12,
}),
_ => None,
}
}


pub(crate) fn is_module(&self) -> bool {
match self {
Kind::Module6Keys | Kind::Module15Keys | Kind::Module32Keys => true,
Expand Down
Loading