Register plugins and use Tokio for async

This commit is contained in:
ninjadev64 committed 2023-11-25 18:32:59 +00:00
1 parent 4e7be5f1d9
commit 913951a096
13 files changed
+902 -165

No files matched your search

+241 -10
View File
@@ -38,6 +38,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "aho-corasick"
version = "1.1.2"
@@ -83,6 +89,12 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]]
name = "async-broadcast"
version = "0.5.1"
@@ -379,6 +391,16 @@ dependencies = [
"serde",
]
[[package]]
name = "buf_redux"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
dependencies = [
"memchr",
"safemem",
]
[[package]]
name = "bumpalo"
version = "3.14.0"
@@ -504,6 +526,12 @@ dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "chunked_transfer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -724,6 +752,22 @@ dependencies = [
"syn 2.0.38",
]
[[package]]
name = "data-encoding"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]]
name = "deflate"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
dependencies = [
"adler32",
"gzip-header",
]
[[package]]
name = "deranged"
version = "0.3.9"
@@ -977,9 +1021,9 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
dependencies = [
"percent-encoding",
]
@@ -1387,6 +1431,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "gzip-header"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2"
dependencies = [
"crc32fast",
]
[[package]]
name = "h2"
version = "0.3.21"
@@ -1591,9 +1644,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@@ -2035,6 +2088,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"
@@ -2056,6 +2119,24 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "multipart"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
dependencies = [
"buf_redux",
"httparse",
"log",
"mime",
"mime_guess",
"quick-error",
"rand 0.8.5",
"safemem",
"tempfile",
"twoway",
]
[[package]]
name = "native-tls"
version = "0.2.11"
@@ -2210,6 +2291,15 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "objc"
version = "0.2.7"
@@ -2278,11 +2368,19 @@ dependencies = [
name = "opendeck"
version = "2.0.0"
dependencies = [
"anyhow",
"futures-util",
"lazy_static",
"os_info",
"rouille",
"serde",
"serde-inline-default",
"serde_json",
"serialport",
"tauri",
"tauri-build",
"tokio",
"tokio-tungstenite",
]
[[package]]
@@ -2428,9 +2526,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
[[package]]
name = "percent-encoding"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "phf"
@@ -2693,6 +2791,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.30.0"
@@ -2944,6 +3048,30 @@ dependencies = [
"windows 0.37.0",
]
[[package]]
name = "rouille"
version = "3.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921"
dependencies = [
"base64 0.13.1",
"brotli",
"chrono",
"deflate",
"filetime",
"multipart",
"percent-encoding",
"rand 0.8.5",
"serde",
"serde_derive",
"serde_json",
"sha1_smol",
"threadpool",
"time",
"tiny_http",
"url",
]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
@@ -3095,6 +3223,17 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-inline-default"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa824cde50b5f01ff28a955114d8152a07cd62d81f53459dad0f2610136be844"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "serde_derive"
version = "1.0.190"
@@ -3239,6 +3378,12 @@ dependencies = [
"digest",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "sha2"
version = "0.10.8"
@@ -3830,6 +3975,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "threadpool"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
"num_cpus",
]
[[package]]
name = "time"
version = "0.3.30"
@@ -3838,6 +3992,8 @@ checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
dependencies = [
"deranged",
"itoa 1.0.9",
"libc",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -3859,6 +4015,18 @@ dependencies = [
"time-core",
]
[[package]]
name = "tiny_http"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
dependencies = [
"ascii",
"chunked_transfer",
"httpdate",
"log",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@@ -3876,20 +4044,34 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.33.0"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.5.5",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
@@ -3900,6 +4082,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
dependencies = [
"futures-util",
"log",
"tokio",
"tungstenite",
]
[[package]]
name = "tokio-util"
version = "0.7.10"
@@ -4064,6 +4258,34 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "tungstenite"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"rand 0.8.5",
"sha1",
"thiserror",
"url",
"utf-8",
]
[[package]]
name = "twoway"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
dependencies = [
"memchr",
]
[[package]]
name = "typenum"
version = "1.17.0"
@@ -4080,6 +4302,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "unicase"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.13"
@@ -4109,9 +4340,9 @@ checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "url"
version = "2.4.1"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
dependencies = [
"form_urlencoded",
"idna",
+12 -2
View File
@@ -13,10 +13,20 @@ rust-version = "1.60"
tauri-build = { version = "1.5.0", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
# Large, fundamental libraries
tauri = { version = "1.5.2", features = [ "system-tray", "api-all" ] }
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
serialport = "4.2.2"
tokio = { version = "1.34.0", features = [ "full" ] }
tokio-tungstenite = "0.20.1"
rouille = "3.6.2"
# Smaller utility libraries
futures-util = "0.3.29"
anyhow = "1.0.75"
serde-inline-default = "0.1.1"
lazy_static = "1.4.0"
os_info = "3.7.0"
[features]
custom-protocol = [ "tauri/custom-protocol" ]
-146
View File
@@ -1,146 +0,0 @@
use std::thread;
use std::time::Duration;
use serde_json::Value;
/// Implemented by all supported devices.
trait BaseDevice {
fn num_keys(&self) -> u8;
fn num_dials(&self) -> u8;
fn key_down(&self, key: u8);
fn key_up(&self, key: u8);
fn dial_rotate(&self, dial: u8, ticks: i16);
}
/// A representation of a ProntoKey device.
struct ProntoKeyDevice {
address: String
}
impl BaseDevice for ProntoKeyDevice {
fn num_keys(&self) -> u8 { 9 }
fn num_dials(&self) -> u8 { 2 }
fn key_down(&self, key: u8) {
println!("{} down {}", &self.address, key);
}
fn key_up(&self, key: u8) {
println!("{} up {}", &self.address, key);
}
fn dial_rotate(&self, dial: u8, ticks: i16) {
println!("{} rotate {} by {}", &self.address, dial, ticks);
}
}
impl ProntoKeyDevice {
/// Attempt to open a serial connection with the device and handle incoming data.
fn init(port: String) {
thread::spawn(|| {
let mut device: Option<ProntoKeyDevice> = Option::None;
let mut last_key: u8 = 0;
let mut last_sliders: Vec<i16> = vec![0; 2];
let mut port = match
serialport::new(port, 57600)
.timeout(Duration::from_millis(10))
.open()
{
Ok(p) => p,
Err(e) => panic!("Failed to open serial port: {}", e.description)
};
let _ = port.write("register".as_bytes());
let mut serial_buf: Vec<u8> = vec![0; 1024];
let mut holding_string: String = "".to_owned();
loop {
match port.read(serial_buf.as_mut_slice()) {
Ok(t) => {
match std::str::from_utf8(&serial_buf[..t]) {
Ok(data) => holding_string += data,
Err(_) => break
}
// Iterate through JSON objects received from device that are being held in the buffer.
while holding_string.contains("}") {
let index = holding_string.find("}").unwrap_or_default();
let chunk = holding_string[..=index].trim();
let j: Value = match serde_json::from_str(chunk) {
Ok(data) => data,
Err(_) => continue
};
holding_string = holding_string[(index + 1)..].to_owned();
// If the device is uninitialised, attempt to read its MAC address and initialise.
if device.is_none() {
match &j["address"] {
Value::String(address) => {
device = Some(ProntoKeyDevice { address: address.clone() });
},
_ => {}
}
continue;
}
let device = device.as_ref().unwrap();
// Handle key presses and releases.
match &j["key"] {
Value::Number(num) => {
match num.as_u64().unwrap_or_default() as u8 {
0 => device.key_up(last_key),
val => {
device.key_down(val);
last_key = val;
}
}
},
_ => {}
}
// Handle slider value changes.
match &j["slider0"] {
Value::Number(val) => {
let val: i16 = match val.as_i64() {
Some(v) => v as i16,
_ => last_sliders[0]
};
device.dial_rotate(0, val - last_sliders[0]);
last_sliders[0] = val;
},
_ => ()
}
match &j["slider1"] {
Value::Number(val) => {
let val: i16 = match val.as_i64() {
Some(v) => v as i16,
_ => last_sliders[1]
};
device.dial_rotate(1, val - last_sliders[1]);
last_sliders[1] = val;
},
_ => {}
}
}
},
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e)
}
thread::sleep(Duration::from_millis(10));
}
});
}
}
/// Attempt to initialise all connected devices.
pub fn initialise_devices() {
// Iterate through available serial ports and attempt to register them as ProntoKey devices.
for port in serialport::available_ports().unwrap_or_default() {
match port.port_type {
serialport::SerialPortType::UsbPort(info) => {
if info.vid == 0x10c4 && info.pid == 0xea60 {
ProntoKeyDevice::init(port.port_name);
}
}
_ => {}
}
}
}
+74
View File
@@ -0,0 +1,74 @@
mod prontokey;
use lazy_static::lazy_static;
use std::sync::Mutex;
use serde::Serialize;
/// A trait implemented by all supported devices.
pub trait BaseDevice {
fn num_dials(&self) -> u8;
fn num_rows(&self) -> u8;
fn num_columns(&self) -> u8;
fn num_keys(&self) -> u8 {
self.num_rows() * self.num_columns()
}
fn id(&self) -> String;
fn name(&self) -> String;
fn r#type(&self) -> u8;
fn key_down(&self, key: u8);
fn key_up(&self, key: u8);
fn dial_rotate(&self, dial: u8, ticks: i16);
}
/// The dimensions of a device.
#[derive(Clone, Serialize)]
pub struct DeviceSize {
pub rows: u8,
pub columns: u8
}
/// Metadata of a device.
#[derive(Clone, Serialize)]
pub struct DeviceInfo {
pub id: String,
pub name: String,
pub size: DeviceSize,
pub r#type: u8
}
impl DeviceInfo {
fn new(device: &impl BaseDevice) -> DeviceInfo {
DeviceInfo {
id: device.id(),
name: device.name(),
size: DeviceSize {
rows: device.num_rows(),
columns: device.num_columns()
},
r#type: device.r#type()
}
}
}
lazy_static! {
pub static ref DEVICES: Mutex<Vec<DeviceInfo>> = Mutex::new(vec![]);
}
/// Attempt to initialise all connected devices.
pub fn initialise_devices() {
// Iterate through available serial ports and attempt to register them as ProntoKey devices.
for port in serialport::available_ports().unwrap_or_default() {
match port.port_type {
serialport::SerialPortType::UsbPort(info) => {
if info.vid == 0x10c4 && info.pid == 0xea60 {
tokio::spawn(prontokey::ProntoKeyDevice::init(port.port_name));
}
}
_ => {}
}
}
}
+136
View File
@@ -0,0 +1,136 @@
use super::BaseDevice;
use std::thread;
use std::time::Duration;
use serde_json::Value;
/// A representation of a ProntoKey device.
pub struct ProntoKeyDevice {
address: String
}
impl BaseDevice for ProntoKeyDevice {
fn num_dials(&self) -> u8 { 2 }
fn num_rows(&self) -> u8 { 3 }
fn num_columns(&self) -> u8 { 3 }
fn id(&self) -> String {
format!("pk-{}", self.address)
}
fn name(&self) -> String {
String::from("ProntoKey")
}
fn r#type(&self) -> u8 { 7 }
fn key_down(&self, key: u8) {
println!("{} down {}", &self.address, key);
}
fn key_up(&self, key: u8) {
println!("{} up {}", &self.address, key);
}
fn dial_rotate(&self, dial: u8, ticks: i16) {
println!("{} rotate {} by {}", &self.address, dial, ticks);
}
}
impl ProntoKeyDevice {
/// Attempt to open a serial connection with the device and handle incoming data.
pub async fn init(port: String) {
let mut device: Option<ProntoKeyDevice> = Option::None;
let mut last_key: u8 = 0;
let mut last_sliders: Vec<i16> = vec![0; 2];
let mut port = match
serialport::new(port, 57600)
.timeout(Duration::from_millis(10))
.open()
{
Ok(p) => p,
Err(e) => panic!("Failed to open serial port: {}", e.description)
};
let _ = port.write("register".as_bytes());
let mut serial_buf: Vec<u8> = vec![0; 1024];
let mut holding_string = String::from("");
loop {
match port.read(serial_buf.as_mut_slice()) {
Ok(t) => {
match std::str::from_utf8(&serial_buf[..t]) {
Ok(data) => holding_string += data,
Err(_) => break
}
// Iterate through JSON objects received from device that are being held in the buffer.
while holding_string.contains("}") {
let index = holding_string.find("}").unwrap_or_default();
let chunk = holding_string[..=index].trim();
let j: Value = match serde_json::from_str(chunk) {
Ok(data) => data,
Err(_) => continue
};
holding_string = holding_string[(index + 1)..].to_owned();
// If the device is uninitialised, attempt to read its MAC address and initialise.
if device.is_none() {
match &j["address"] {
Value::String(address) => {
device = Some(ProntoKeyDevice { address: address.clone() });
match &device {
Some(device) => super::DEVICES.lock().unwrap().push(super::DeviceInfo::new(device)),
_ => {}
}
},
_ => {}
}
continue;
}
let device = device.as_ref().unwrap();
// Handle key presses and releases.
match &j["key"] {
Value::Number(num) => {
match num.as_u64().unwrap_or_default() as u8 {
0 => device.key_up(last_key),
val => {
device.key_down(val);
last_key = val;
}
}
},
_ => {}
}
// Handle slider value changes.
match &j["slider0"] {
Value::Number(val) => {
let val: i16 = match val.as_i64() {
Some(v) => v as i16,
_ => last_sliders[0]
};
device.dial_rotate(0, val - last_sliders[0]);
last_sliders[0] = val;
},
_ => ()
}
match &j["slider1"] {
Value::Number(val) => {
let val: i16 = match val.as_i64() {
Some(v) => v as i16,
_ => last_sliders[1]
};
device.dial_rotate(1, val - last_sliders[1]);
last_sliders[1] = val;
},
_ => {}
}
}
},
Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => (),
Err(e) => eprintln!("{:?}", e)
}
thread::sleep(Duration::from_millis(10));
}
}
}
+6
View File
@@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct RegisterEvent {
pub uuid: String
}
+26
View File
@@ -0,0 +1,26 @@
mod inbound;
use std::collections::HashMap;
use tokio::sync::Mutex;
use tokio::net::TcpStream;
use tokio_tungstenite::WebSocketStream;
use futures_util::StreamExt;
use lazy_static::lazy_static;
lazy_static! {
static ref SOCKETS: Mutex<HashMap<String, WebSocketStream<TcpStream>>> = Mutex::new(HashMap::new());
}
/// Register a plugin to send and receive events with its WebSocket.
pub async fn register_plugin(data: inbound::RegisterEvent, stream: WebSocketStream<TcpStream>) {
SOCKETS.lock().await.insert(data.uuid.clone(), stream);
tokio::spawn(handle_stream(data.uuid));
}
async fn handle_stream(uuid: String) {
while let Some(data) = SOCKETS.lock().await.get_mut(&uuid).unwrap().next().await {
println!("{}", data.unwrap());
}
}
+13 -7
View File
@@ -1,13 +1,19 @@
// Prevents additional console window on Windows in release
// Prevents additional console window on Windows in release.
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod shared;
mod events;
mod plugins;
mod devices;
fn main() {
#[tokio::main]
async fn main() {
let app = match tauri::Builder::default().build(tauri::generate_context!()) {
Ok(app) => app,
Err(e) => panic!("Failed to create Tauri application: {}", e.to_string())
};
devices::initialise_devices();
match tauri::Builder::default().run(tauri::generate_context!()) {
Err(e) => panic!("Error while running Tauri application: {}", e.to_string()),
_ => {}
}
plugins::initialise_plugins(app.handle());
app.run(|_, _| {});
}
+40
View File
@@ -0,0 +1,40 @@
use serde::Serialize;
// Structs that make up the Info parameter passed to plugins during the registration procedure.
#[allow(non_snake_case)]
#[derive(Serialize)]
pub(super) struct ApplicationInfo {
pub font: String,
pub language: String,
pub platform: String,
pub platformVersion: String,
pub version: String
}
#[derive(Serialize)]
pub(super) struct PluginInfo {
pub uuid: String,
pub version: String
}
#[allow(non_snake_case)]
#[derive(Serialize)]
pub(super) struct ColoursInfo {
pub buttonPressedBackgroundColor: String,
pub buttonPressedBorderColor: String,
pub buttonPressedTextColor: String,
pub disabledColor: String,
pub highlightColor: String,
pub mouseDownColor: String
}
#[allow(non_snake_case)]
#[derive(Serialize)]
pub(super) struct Info {
pub application: ApplicationInfo,
pub plugin: PluginInfo,
pub devicePixelRatio: u8,
pub colors: ColoursInfo,
pub devices: Vec<crate::devices::DeviceInfo>
}
+34
View File
@@ -0,0 +1,34 @@
use crate::shared::Action;
use serde::Deserialize;
#[derive(Deserialize)]
pub(super) struct OS {
#[serde(alias = "Platform")]
pub platform: String
}
#[allow(dead_code)]
#[derive(Deserialize)]
pub(super) struct PluginManifest {
#[serde(alias = "Version")]
pub version: String,
#[serde(alias = "Actions")]
pub actions: Vec<Action>,
#[serde(alias = "OS")]
pub os: Vec<OS>,
#[serde(alias = "CodePath")]
pub code_path: Option<String>,
#[serde(alias = "CodePathWin")]
pub code_path_windows: Option<String>,
#[serde(alias = "CodePathMac")]
pub code_path_macos: Option<String>,
#[serde(alias = "CodePathLin")]
pub code_path_linux: Option<String>
}
+217
View File
@@ -0,0 +1,217 @@
mod manifest;
mod info_param;
use std::{fs, path};
use std::process::{Command, Stdio};
use tauri::AppHandle;
use tokio::net::{TcpListener, TcpStream};
use futures_util::StreamExt;
use anyhow::{Context, anyhow};
/// Initialise a plugin from a given directory.
fn initialise_plugin(path: path::PathBuf, app: &AppHandle) -> anyhow::Result<()> {
let plugin_uuid = path.file_name().unwrap().to_str().unwrap();
let mut manifest_path = path.clone();
manifest_path.push("manifest.json");
let manifest =
fs::read(&manifest_path)
.with_context(|| { format!("Failed to read manifest of plugin at {}", manifest_path.display()) })?;
let mut manifest: manifest::PluginManifest =
serde_json::from_slice(&manifest)
.with_context(|| { format!("Failed to parse manifest of plugin at {}", manifest_path.display()) })?;
for action in &mut manifest.actions {
action.plugin = plugin_uuid.to_string();
}
#[cfg(target_os = "windows")]
let platform = "windows";
#[cfg(target_os = "macos")]
let platform = "mac";
#[cfg(target_os = "linux")]
let platform = "linux";
let mut code_path = manifest.code_path;
let mut use_browser = false;
let mut use_wine = false;
let mut supported = false;
// Determine the method used to run the plugin based on its supported operating systems and the current operating system.
for os in manifest.os {
if os.platform == platform {
#[cfg(target_os = "windows")]
if let Some(_) = &manifest.code_path_windows { code_path = manifest.code_path_windows; }
#[cfg(target_os = "macos")]
if let Some(_) = &manifest.code_path_macos { code_path = manifest.code_path_macos; }
#[cfg(target_os = "linux")]
if let Some(_) = &manifest.code_path_linux { code_path = manifest.code_path_linux; }
if let Some(code_path) = &code_path {
use_browser = code_path.ends_with(".html");
}
use_wine = false;
supported = true;
break;
} else if os.platform == "windows" {
use_wine = true;
supported = true;
}
}
if !supported || code_path.is_none() {
return Err(anyhow!("Failed to load plugin with ID {}: unsupported on platform {}", plugin_uuid, platform));
}
let info = info_param::Info {
application: info_param::ApplicationInfo {
font: String::from("Rubik"),
language: String::from("en"),
platform: platform.to_owned(),
platformVersion: os_info::get().version().to_string(),
version: env!("CARGO_PKG_VERSION").to_owned()
},
plugin: info_param::PluginInfo {
uuid: plugin_uuid.to_string(),
version: manifest.version
},
devicePixelRatio: 0,
colors: info_param::ColoursInfo {
buttonPressedBackgroundColor: String::from("#303030FF"),
buttonPressedBorderColor: String::from("#646464FF"),
buttonPressedTextColor: String::from("#969696FF"),
disabledColor: String::from("#F7821B59"),
highlightColor: String::from("#F7821BFF"),
mouseDownColor: String::from("#CF6304FF")
},
devices: crate::devices::DEVICES.lock().unwrap().to_vec()
};
let code_path = code_path.unwrap();
if use_browser {
// Create a webview window for the plugin and call its registration function.
let url = String::from("http://localhost:57118/") + &path.join(code_path).to_str().unwrap();
let window = tauri::WindowBuilder::new(
app,
plugin_uuid.replace(".", "_"),
tauri::WindowUrl::External(url.parse().unwrap())
)
.visible(false)
.build()
.with_context(|| { format!("Failed to initialise plugin with ID {}", plugin_uuid) })?;
window.eval(&format!(
"let a = setInterval(() => {{
try {{
connectElgatoStreamDeckSocket({}, \"{}\", \"{}\", {});
clearInterval(a);
}} catch {{}}
}}, 10);",
57116, plugin_uuid, "register", serde_json::to_string(&info).unwrap()
))?;
} else if use_wine {
// Start Wine with the appropriate arguments.
Command::new("wine")
.current_dir(&path)
.args([
code_path,
String::from("-port"), 57116.to_string(),
String::from("-pluginUUID"), plugin_uuid.to_string(),
String::from("-registerEvent"), String::from("register"),
String::from("-info"), serde_json::to_string(&info).unwrap()
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.with_context(|| { format!("Failed to initialise plugin with ID {}", plugin_uuid) })?;
} else {
// Run the plugin's executable natively.
Command::new(code_path)
.current_dir(&path)
.args([
String::from("-port"), 57116.to_string(),
String::from("-pluginUUID"), plugin_uuid.to_string(),
String::from("-registerEvent"), String::from("register"),
String::from("-info"), serde_json::to_string(&info).unwrap()
])
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.with_context(|| { format!("Failed to initialise plugin with ID {}", path.file_name().unwrap().to_str().unwrap()) })?;
}
Ok(())
}
/// Initialise plugins from the plugins directory.
pub fn initialise_plugins(app: AppHandle) {
tokio::spawn(init_websocket_server());
tokio::spawn(init_browser_server());
let mut plugin_dir = app.path_resolver().app_config_dir().unwrap();
plugin_dir.push("plugins/");
let _ = fs::create_dir_all(&plugin_dir);
let entries = match fs::read_dir(&plugin_dir) {
Ok(p) => p,
Err(e) => panic!("Failed to read plugins directory at {}: {}", plugin_dir.display(), e.to_string())
};
// Iterate through all directory entries in the plugins folder and initialise them as plugins if appropriate
for entry in entries {
if let Ok(entry) = entry {
if entry.metadata().unwrap().is_dir() {
if let Err(e) = initialise_plugin(entry.path(), &app) {
eprintln!("{}\n\tCaused by: {}", e.to_string(), e.root_cause());
}
} else {
eprintln!("Failed to initialise plugin at {}: is a file", entry.path().display());
}
} else if let Err(e) = entry {
eprintln!("Failed to read plugin directory: {}", e.to_string())
}
}
}
/// Start the WebSocket server that plugins communicate with.
async fn init_websocket_server() {
let listener = match TcpListener::bind("localhost:57116").await {
Ok(listener) => listener,
Err(error) => {
eprintln!("Failed to bind to socket: {}", error.to_string());
return;
}
};
while let Ok((stream, _)) = listener.accept().await {
accept_connection(stream).await;
}
}
/// Handle incoming data from a WebSocket connection.
async fn accept_connection(stream: TcpStream) {
let mut socket = match tokio_tungstenite::accept_async(stream).await {
Ok(socket) => socket,
Err(error) => {
eprintln!("Failed to complete WebSocket handshake: {}", error.to_string());
return;
}
};
while let Some(data) = socket.next().await {
crate::events::register_plugin(serde_json::from_str(&data.unwrap().into_text().unwrap()).unwrap(), socket).await;
break;
}
}
/// Start a simple webserver to serve files of plugins that run in a browser environment.
async fn init_browser_server() {
rouille::start_server("localhost:57118", move |request| {
rouille::Response::html(std::fs::read_to_string(request.url()).unwrap())
});
}
+103
View File
@@ -0,0 +1,103 @@
use serde::{Serialize, Deserialize};
use serde_inline_default::serde_inline_default;
/// A state of an action.
#[serde_inline_default]
#[derive(Serialize, Deserialize)]
pub struct ActionState {
#[serde(alias = "Image")]
pub image: String,
#[serde_inline_default(String::from(""))]
#[serde(alias = "Name")]
pub name: String,
#[serde_inline_default(String::from(""))]
#[serde(alias = "Title")]
pub text: String,
#[serde_inline_default(true)]
#[serde(alias = "ShowTitle")]
pub show: bool,
#[serde_inline_default(String::from("#f2f2f2"))]
#[serde(alias = "TitleColor")]
pub colour: String,
#[serde_inline_default(String::from("middle"))]
#[serde(alias = "TitleAlignment")]
pub alignment: String,
#[serde_inline_default(String::from("Regular"))]
#[serde(alias = "FontStyle")]
pub style: String,
#[serde_inline_default(String::from("16"))]
#[serde(alias = "FontSize")]
pub size: String,
#[serde_inline_default(false)]
#[serde(alias = "FontUnderline")]
pub underline: bool
}
/// An action, deserialised from the plugin manifest.
#[serde_inline_default]
#[derive(Serialize, Deserialize)]
pub struct Action {
#[serde(alias = "Name")]
pub name: String,
#[serde(alias = "UUID")]
pub uuid: String,
#[serde_inline_default(String::from(""))]
pub plugin: String,
#[serde(alias = "Tooltip")]
pub tooltip: String,
#[serde(alias = "Icon")]
pub icon: String,
#[serde_inline_default(true)]
#[serde(alias = "VisibleInActionsList")]
pub visible_in_action_list: bool,
#[serde_inline_default(String::from(""))]
#[serde(alias = "PropertyInspectorPath")]
pub property_inspector: String,
#[serde_inline_default(vec![String::from("Keypad")])]
#[serde(alias = "Controllers")]
pub controllers: Vec<String>,
#[serde(alias = "States")]
pub states: Vec<ActionState>
}
/// Information about the slot an instance is located in.
#[derive(Serialize, Deserialize)]
pub struct ActionContext {
pub device: String,
pub profile: String,
pub controller: String,
pub position: u8,
pub index: u16
}
impl ToString for ActionContext {
fn to_string(&self) -> String {
format!("{}.{}.{}.{}.{}", self.device, self.profile, self.controller, self.position, self.index)
}
}
/// An instance of an action.
#[derive(Serialize, Deserialize)]
pub struct ActionInstance {
pub action: Action,
pub context: ActionContext,
pub states: Vec<ActionState>,
pub current_state: u16,
pub settings: String
}
File renamed without changes.