Skip to content

Instantly share code, notes, and snippets.

@crescentrose
Last active February 4, 2024 13:43
Show Gist options
  • Save crescentrose/cac9ed8823a675de2f635a124ee87dad to your computer and use it in GitHub Desktop.
Save crescentrose/cac9ed8823a675de2f635a124ee87dad to your computer and use it in GitHub Desktop.
Stream Deck plugin in Rust, proof of concept
refresh_rate: 2 seconds
appenders:
stdout:
kind: console
logfile:
kind: file
# create a logs directory in the .sdPlugin directory first
path: "logs/sonos-controller.log"
encoder:
pattern: "{d} - {m}{n}"
root:
level: debug
appenders:
- stdout
- logfile
use futures::{SinkExt, StreamExt};
use log::{debug, info};
use serde_json::json;
use std::env;
use tokio_tungstenite::tungstenite::Message;
#[tokio::main(flavor = "current_thread")] // no need for multithreading, keep it simple
async fn main() {
// Log directly to a file as we can't read stdout/stderr from the Stream Deck app
// log4rs.yml should be in the *.sdPlugin directory
log4rs::init_file("log4rs.yml", Default::default()).unwrap();
log_panics::init();
info!("starting plugin");
debug!("pid: {}", std::process::id()); // store pid so we can `kill` the process so the SD app restarts it
// really quick and dirty argument parsing - should probably use `clap` in a real plugin
let args: Vec<String> = env::args().collect();
let port: u16 = args[2].parse().unwrap();
let uuid = &args[4];
let register_event = &args[6];
debug!("port: {port}, uuid: {uuid}, registerEvent: {register_event}");
// set up websockets client
let (conn, _) = tokio_tungstenite::connect_async(format!("ws://localhost:{}", port))
.await
.expect("connection failed");
debug!("connection successful");
// this lets us have separate tasks to send and receive events
let (mut write, read) = conn.split();
// an example "send event" task - in a proper plugin you'd probably set it
// up to read from a channel and send messages to the socket. this one just
// sends the mandatory registration event.
let send = async {
write
.send(Message::Binary(
json!({"event": register_event, "uuid": uuid})
.to_string()
.into_bytes(),
))
.await
.expect("error sending registration message");
info!("sent registration message");
};
// an example "handler" - in a proper plugin this would take a channel and
// send received events through it. here we just log everything the SD sends
// us.
let handler = {
read.for_each(|message| async {
let data = message.unwrap().into_data();
let msg = String::from_utf8_lossy(&data);
debug!("received message: {:?}", msg);
})
};
// start the send and handler tasks and wait until they are done
// the handler shuts down when the Stream Deck closes the WebSockets connection
futures::pin_mut!(send, handler);
futures::join!(send, handler);
info!("plugin shutting down -- goodbye!");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment