StreamDeck Oxide
A high-level framework for creating Stream Deck applications in Rust.
Features
- Button rendering with text, icons, and custom images
- View system for organizing buttons into screens
- Navigation between views
- Event handling for button presses
- Async support for fetching state and handling actions
Installation
Add this to your Cargo.toml:
[dependencies]
streamdeck-oxide = "0.1.0"
Other dependencies
libudev is required for HID support. You can install it using your package
manager. flake.nix with a dev shell is provided for development.
Usage
use std::sync::Arc;
use streamdeck_oxide::{
button::RenderConfig,
navigation::NavigationEntry,
run,
theme::Theme,
view::{
customizable::{ClickButton, CustomizableView, ToggleButton},
View,
},
elgato_streamdeck,
generic_array,
md_icons,
};
#[derive(Debug, Clone)]
struct AppContext {
message: String,
}
#[derive(Debug, Clone, Default)]
enum Navigation {
#[default]
Main,
Settings,
}
impl NavigationEntry<generic_array::typenum::U5, generic_array::typenum::U3, AppContext>
for Navigation
{
fn get_view(
&self,
) -> Result<
Box<
dyn View<
generic_array::typenum::U5,
generic_array::typenum::U3,
AppContext,
Navigation,
>,
>,
Box<dyn std::error::Error>,
> {
match self {
Navigation::Main => {
let mut view = CustomizableView::default();
view.set_button(
0,
0,
ToggleButton::new(
"Toggle",
Some(md_icons::filled::ICON_HOME),
|_ctx: AppContext| async move {
println!("Fetching toggle state");
Ok(false)
},
|ctx: AppContext, state: bool| async move {
println!("Toggled state: {}", state);
println!("Message: {}", ctx.message);
Ok(())
},
),
)?;
view.set_button(
1,
0,
ClickButton::new(
"Click",
Some(md_icons::filled::ICON_TOUCH_APP),
|ctx: AppContext| async move {
println!("Button clicked!");
println!("Message: {}", ctx.message);
Ok(())
},
),
)?;
view.set_navigation(
0,
2,
Navigation::Settings,
"Settings",
Some(md_icons::sharp::ICON_SETTINGS)
)?;
Ok(Box::new(view))
}
Navigation::Settings => {
let mut view = CustomizableView::default();
view.set_button(
0,
0,
ClickButton::new(
"Option 1",
Some(md_icons::filled::ICON_BRIGHTNESS_5),
|_ctx| async move {
println!("Option 1 selected");
Ok(())
},
)
)?;
view.set_button(
1,
0,
ClickButton::new(
"Option 2",
Some(md_icons::filled::ICON_VOLUME_UP),
|_ctx| async move {
println!("Option 2 selected");
Ok(())
},
)
)?;
view.set_button(
2,
0,
ClickButton::new(
"Fail",
Some(md_icons::filled::ICON_ERROR),
|_ctx| async move {
println!("This button will fail");
Err("Failed to fetch data".into())
},
)
)?;
view.set_navigation(
4,
2,
Navigation::Main,
"Back",
Some(md_icons::sharp::ICON_ARROW_BACK)
)?;
Ok(Box::new(view))
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("StreamDeck Example Application");
println!("------------------------------");
let hid = elgato_streamdeck::new_hidapi()?;
let devices = elgato_streamdeck::list_devices(&hid);
println!("Looking for Stream Deck devices...");
let (kind, serial) = devices
.into_iter()
.find(|(kind, _)| *kind == elgato_streamdeck::info::Kind::Mk2)
.ok_or("No Stream Deck found")?;
println!("Found Stream Deck: {:?} ({})", kind, serial);
let deck = Arc::new(elgato_streamdeck::AsyncStreamDeck::connect(&hid, kind, &serial)?);
println!("Connected to Stream Deck successfully!");
let config = RenderConfig::default();
let theme = Theme::light();
let context = AppContext {
message: "Hello from StreamDeck Example!".to_string(),
};
println!("Starting Stream Deck application...");
println!("Press Ctrl+C to exit");
run::<Navigation, generic_array::typenum::U5, generic_array::typenum::U3, AppContext>(
theme, config, deck, context,
)
.await?;
Ok(())
}
Documentation
For more detailed documentation, see the
API documentation.
License
This project is licensed under the MIT License. See the LICENSE file
for details.
This project is using Roboto font for rendering text. The font files are
included in the fonts directory. Roboto is licensed under the Apache License.
See the
LICENSE-ROBOTO file
for details.