1 unstable release
Uses new Rust 2024
| 0.1.0 | Jan 26, 2026 |
|---|
#288 in Hardware support
160KB
2.5K
SLoC
GadgetDeck
A Rust library for emulating Elgato Stream Deck devices as USB gadgets on Linux.
Overview
GadgetDeck provides the core functionality for creating USB gadgets that appear to host computers as real Stream Deck devices. Applications like the Elgato Stream Deck software can communicate with the emulated device just like physical hardware.
- Multiple Device Models – Emulates Mini, MK.2, XL, Plus, and Pedal
- USB HID Implementation – Full HID protocol support for input/output reports
- Thread-Safe State Management – Concurrent access to button states and images
- Image Store – Receives and caches button images from host software
- Plus Support – Touchscreen and rotary encoder events for Stream Deck Plus
Quick Start
use gadgetdeck::{GadgetDeck, GadgetDeckConfig, StreamDeckModel};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create a Stream Deck Mini emulator
let config = GadgetDeckConfig::new(StreamDeckModel::Mini, "MYSERIAL123");
let mut deck = GadgetDeck::new(config)?;
// Start USB processing threads
deck.start()?;
// Access button state
let buttons = deck.button_state();
buttons.press(0); // Press button 0
buttons.release(0);
// Subscribe to image updates
let image_rx = deck.subscribe_images();
// Run until stopped
while deck.is_running() {
std::thread::sleep(std::time::Duration::from_millis(100));
}
deck.stop();
Ok(())
}
Supported Devices
| Model | Keys | Layout | Special Features |
|---|---|---|---|
Mini |
6 | 3×2 | – |
Mk2 |
15 | 5×3 | – |
Xl |
32 | 8×4 | – |
Plus |
8 | 4×2 | 4 rotary knobs, LCD touchscreen |
Pedal |
3 | 3×1 | Foot pedals (no display) |
API Reference
GadgetDeck
The main struct for managing USB gadget emulation.
use gadgetdeck::{GadgetDeck, GadgetDeckConfig, StreamDeckModel};
// Create with specific model
let config = GadgetDeckConfig::new(StreamDeckModel::Xl, "SERIAL");
let mut deck = GadgetDeck::new(config)?;
// Or use convenience constructors
let config = GadgetDeckConfig::mini("SERIAL");
let config = GadgetDeckConfig::plus("SERIAL");
let config = GadgetDeckConfig::pedal("SERIAL");
Methods
| Method | Description |
|---|---|
new(config) |
Create a new GadgetDeck instance |
start() |
Start USB processing threads |
stop() |
Stop and clean up |
is_running() |
Check if still running |
signal_stop() |
Signal stop (non-blocking) |
running_flag() |
Get Arc<AtomicBool> for signal handlers |
model() |
Get the Stream Deck model |
serial() |
Get the device serial number |
button_state() |
Get button state manager |
plus_state() |
Get Plus-specific state (if Plus model) |
image_store() |
Get image store |
subscribe_images() |
Subscribe to image update events |
ButtonState
Thread-safe button state manager.
let buttons = deck.button_state();
// Query state
let num = buttons.num_buttons(); // e.g., 6 for Mini
let pressed = buttons.is_pressed(0);
// Update state (automatically sent to host)
buttons.press(0);
buttons.release(0);
buttons.click(0); // Press, wait 50ms, release
ImageStore
Stores button images received from the host.
let images = deck.image_store();
// Get image for a button
if let Some(image) = images.get_image(0) {
let bytes: &[u8] = image.as_bytes();
// Process image data (JPEG or BMP depending on model)
}
// Get statistics
let stats = images.stats();
println!("Received {} images", stats.images_completed);
Image Events
Subscribe to real-time image updates.
use gadgetdeck::ImageEvent;
let rx = deck.subscribe_images();
loop {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(ImageEvent::Updated { key_index, image }) => {
println!("Button {} image updated", key_index);
}
Ok(ImageEvent::LcdUpdated { x_offset, y_offset, width, height, image }) => {
println!("LCD segment at ({}, {}) updated", x_offset, y_offset);
}
Err(_) => continue,
}
}
PlusInputState (Plus Only)
For Stream Deck Plus touchscreen and knob events.
use gadgetdeck::KnobIndex;
if let Some(plus) = deck.plus_state() {
// Touchscreen events
plus.tap(200, 50); // Tap at coordinates
plus.long_press(200, 50); // Long press
plus.swipe(100, 50, 700, 50); // Swipe gesture
plus.swipe_horizontal(100, 700); // Horizontal swipe (y=50)
// Knob events
plus.press_knob(KnobIndex::A);
plus.release_knob(KnobIndex::A);
plus.turn_knob(KnobIndex::B, 3); // Clockwise 3 steps
plus.turn_knob(KnobIndex::C, -2); // Counter-clockwise 2 steps
}
Module Structure
gadgetdeck/
├── lib.rs # Re-exports and crate documentation
├── gadgetdeck.rs # GadgetDeck main struct
├── device/
│ ├── buttons.rs # ButtonState management
│ ├── image.rs # ImageStore and image handling
│ └── plus.rs # PlusInputState (touch/knobs)
└── usb/
├── custom_hid.rs # HID endpoint handling
└── descriptors.rs # USB/HID descriptors per model
Requirements
- Linux with USB gadget support (ConfigFS)
- Root privileges or appropriate permissions
- USB OTG-capable port
Kernel Modules
sudo modprobe libcomposite
sudo modprobe dwc2
License
MIT OR Apache-2.0
Dependencies
~3.5MB
~70K SLoC