From 0c59291170490e7a10ceb72d055ec173ebd8c160 Mon Sep 17 00:00:00 2001 From: "Aode (Lion)" Date: Wed, 16 Mar 2022 19:13:56 -0500 Subject: [PATCH] BROKEN: aaaaa --- Cargo.lock | 62 +- streamdeck-daemon/src/dbus.rs | 41 +- streamdeck-gtk/Cargo.toml | 10 +- streamdeck-gtk/src/daemon.rs | 1141 ----------------- streamdeck-gtk/src/dbus.rs | 76 ++ .../src/dialogs/new_command_dialog.rs | 2 +- streamdeck-gtk/src/main.rs | 39 +- streamdeck-gtk/src/views/command_config.rs | 10 +- streamdeck-gtk/src/widgets/command_list.rs | 24 +- streamdeck-gtk/src/widgets/command_row.rs | 16 +- 10 files changed, 177 insertions(+), 1244 deletions(-) delete mode 100644 streamdeck-gtk/src/daemon.rs create mode 100644 streamdeck-gtk/src/dbus.rs diff --git a/Cargo.lock b/Cargo.lock index da3e31c..419ce03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,23 +184,18 @@ dependencies = [ "system-deps", ] -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + [[package]] name = "base64" version = "0.13.0" @@ -481,19 +476,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "event-listener" version = "2.5.2" @@ -917,12 +899,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "ident_case" version = "1.0.1" @@ -1746,7 +1722,6 @@ dependencies = [ "anyhow", "async-io", "async-stream", - "env_logger", "event-listener", "futures-channel", "futures-core", @@ -1758,12 +1733,15 @@ dependencies = [ "glib-sys", "gtk", "libhandy", - "log", "marble", "once_cell", "pango", "serde_json", "streamdeck-common", + "tracing", + "tracing-error 0.2.0", + "tracing-log", + "tracing-subscriber 0.3.9", "zbus", ] @@ -1791,7 +1769,7 @@ name = "streamdeck-daemon" version = "0.1.0" dependencies = [ "anyhow", - "base64", + "base16ct", "directories", "either", "futures-util", @@ -1855,15 +1833,6 @@ dependencies = [ "version-compare", ] -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.30" @@ -2213,15 +2182,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/streamdeck-daemon/src/dbus.rs b/streamdeck-daemon/src/dbus.rs index aa9ae4b..2672bb6 100644 --- a/streamdeck-daemon/src/dbus.rs +++ b/streamdeck-daemon/src/dbus.rs @@ -31,6 +31,7 @@ pub(crate) fn button_path(serial_number: &str, input: &Input) -> String { } struct Daemon { + discovery_enabled: bool, manager: Sender, decks: BTreeSet, } @@ -41,20 +42,23 @@ fn fail(e: impl std::fmt::Display) -> zbus::fdo::Error { #[zbus::dbus_interface(name = "dog.asonix.git.asonix.StreamdeckDaemon")] impl Daemon { - async fn enable_discovery(&self) -> zbus::fdo::Result<()> { - tracing::debug!("enable_discovery"); - self.manager - .send(ManagerMessage::EnableDiscovery) - .await - .map_err(fail) + #[dbus_interface(property)] + async fn discovery_enabled(&self) -> bool { + self.discovery_enabled } - async fn disable_discovery(&self) -> zbus::fdo::Result<()> { - tracing::debug!("disable_discovery"); - self.manager - .send(ManagerMessage::DisableDiscovery) - .await - .map_err(fail) + #[dbus_interface(property)] + async fn set_discovery_enabled(&mut self, enabled: bool) { + tracing::debug!("set_discovery_enabled, {}", enabled); + let cmd = if enabled { + ManagerMessage::EnableDiscovery + } else { + ManagerMessage::DisableDiscovery + }; + + if self.manager.send(cmd).await.is_ok() { + self.discovery_enabled = enabled; + } } async fn get_decks(&self) -> Vec { @@ -63,10 +67,10 @@ impl Daemon { } #[dbus_interface(signal)] - async fn deck_added(ctx: &SignalContext<'_>, serial_number: &str) -> zbus::Result<()>; + async fn deck_added(ctx: &SignalContext<'_>, path: &str) -> zbus::Result<()>; #[dbus_interface(signal)] - async fn deck_removed(ctx: &SignalContext<'_>, serial_number: &str) -> zbus::Result<()>; + async fn deck_removed(ctx: &SignalContext<'_>, path: &str) -> zbus::Result<()>; } #[tracing::instrument(skip_all)] @@ -78,6 +82,7 @@ pub(crate) async fn spawn( manager: Sender, ) -> anyhow::Result { let state = Daemon { + discovery_enabled: true, manager: manager.clone(), decks: BTreeSet::new(), }; @@ -115,9 +120,9 @@ pub(crate) async fn spawn( continue; } - daemon.decks.insert(config.serial_number.clone()); + daemon.decks.insert(deck_path(&config.serial_number)); let ctx = daemon_ref.signal_context(); - let _ = Daemon::deck_added(ctx, &config.serial_number).await; + let _ = Daemon::deck_added(ctx, &deck_path(&config.serial_number)).await; } } DeckMessage::Close(serial_number) => { @@ -131,9 +136,9 @@ pub(crate) async fn spawn( .await { let mut daemon = daemon_ref.get_mut().await; - daemon.decks.remove(&serial_number); + daemon.decks.remove(&deck_path(&serial_number)); let ctx = daemon_ref.signal_context(); - let _ = Daemon::deck_removed(ctx, &serial_number).await; + let _ = Daemon::deck_removed(ctx, &deck_path(&serial_number)).await; } } } diff --git a/streamdeck-gtk/Cargo.toml b/streamdeck-gtk/Cargo.toml index d0e6805..5d4ea70 100644 --- a/streamdeck-gtk/Cargo.toml +++ b/streamdeck-gtk/Cargo.toml @@ -11,7 +11,6 @@ anyhow = "1" async-io = "1.4.1" async-stream = "0.3.1" streamdeck-common = { path = "../streamdeck-common", features = ["ipc-dbus"] } -env_logger = "0.9.0" event-listener = "2.5.1" futures-channel = { version = "0.3.14", features = ["sink"] } futures-core = "0.3.14" @@ -23,9 +22,16 @@ gio = "0.15.1" glib = "0.15.3" glib-sys = "0.15.1" libhandy = "0.9.0" -log = "0.4" marble = { path = "../marble" } once_cell = "1.7.2" pango = "0.15.2" serde_json = "1" +tracing = "0.1.15" +tracing-error = "0.2.0" +tracing-log = "0.1.2" +tracing-subscriber = { version = "0.3.0", features = [ + "env-filter", + "fmt", + "tracing-log", +] } zbus = "2.1.1" diff --git a/streamdeck-gtk/src/daemon.rs b/streamdeck-gtk/src/daemon.rs deleted file mode 100644 index cde7444..0000000 --- a/streamdeck-gtk/src/daemon.rs +++ /dev/null @@ -1,1141 +0,0 @@ -use async_io::Timer; -use event_listener::{Event, EventListener}; -use futures_channel::{mpsc::Receiver, oneshot::Sender}; -use futures_core::stream::Stream; -use futures_util::{sink::SinkExt, stream::StreamExt}; -use once_cell::sync::Lazy; -use std::{ - collections::{HashMap, HashSet}, - future::Future, - pin::Pin, - sync::{Arc, Mutex, Weak}, - task::{Context, Poll}, - time::{Duration, Instant}, -}; -use streamdeck_common::{Command, ObsState, Query, QueryResponse, Scene, SceneItem}; -use zbus::{dbus_proxy, Connection, Result}; - -pub(crate) async fn state_management() { - let new_deck_stream = Handle::current().state().added_deck_stream(); - glib::MainContext::default().spawn_local(async move { - futures_util::pin_mut!(new_deck_stream); - while let Some(deck_state) = new_deck_stream.next().await { - glib::MainContext::default().spawn_local(deck_management(deck_state)); - } - }); - - let mut interval = Timer::interval(Duration::from_secs(3)); - loop { - cache_decks(); - cache_obs_state(); - - let _ = interval.next().await; - } -} - -async fn deck_management(deck_state: DeckState) { - let weak = Arc::downgrade(&deck_state.inner); - drop(deck_state); - - let mut interval = Timer::interval(Duration::from_secs(3)); - while let Some(inner) = weak.upgrade() { - let deck_state = DeckState { inner }; - - cache_deck_name(deck_state.serial_number()); - cache_commands(deck_state.serial_number()); - - let _ = interval.next().await; - } -} - -pub(crate) struct EventStream { - listener: Option, - event: Weak, -} - -#[derive(Clone)] -pub(crate) struct State { - inner: Arc>, -} - -#[derive(Default)] -struct StateInner { - state: ObsState, - state_event: Arc, - - decks: HashMap, - added_deck_txs: Vec>, - removed_deck_txs: Vec>, -} - -#[derive(Clone)] -pub(crate) struct DeckState { - inner: Arc>, -} - -struct DeckStateInner { - info: DeckInfo, - name_event: Arc, - - commands: HashMap, - added_command_txs: Vec>, - removed_command_txs: Vec>, -} - -#[derive(Clone)] -pub(crate) struct CommandState { - inner: Arc>, -} - -struct CommandStateInner { - key: u8, - - command: Command, - command_event: Arc, - - name: Option, - name_event: Arc, -} - -#[derive(Clone, Debug)] -pub(crate) struct DeckInfo { - pub(crate) serial_number: String, - pub(crate) device_name: String, - pub(crate) port_name: String, -} - -#[derive(Clone, Debug)] -pub(crate) struct CommandInfo { - pub(crate) key: u8, - pub(crate) command: Command, -} - -#[derive(Clone, Debug)] -pub(crate) struct ReadInput { - pub(crate) key: u8, - pub(crate) serial_number: String, -} - -#[derive(Clone, Debug)] -pub(crate) struct InputName { - pub(crate) key: u8, - pub(crate) name: String, -} - -#[derive(Clone, Debug)] -pub(crate) struct Handle { - _arc: Arc>, - tx: futures_channel::mpsc::Sender, -} - -#[derive(Clone, Debug)] -struct Daemon { - tx: futures_channel::mpsc::Sender, - state: State, -} - -#[derive(Clone, Debug)] -struct DropLog { - inner: T, - name: &'static str, -} - -/// Create an inner type T when it is asked for, no sooner, AND, if the type is no longer held by -/// any other parties, allow it to be dropped (deallocated) and next time T is asked for, create a -/// new one -#[derive(Clone)] -struct OnDemand { - inner: Arc>>, - init: Arc T + Send + Sync>, -} - -#[derive(Debug)] -enum DBusMessage { - Query(Sender, String), - Test(String), - EnableDiscovery, - DisableDiscovery, - GetDecks(Sender>), - Connect(String, u16, Sender), - Disconnect(Sender), - GetState(Sender), - Login(String, Sender), - GetCommands(String, Sender>), - ReadInput(Sender>), - SetInput(String, u8, String), - UnsetInput(String, u8), - SetInputName(String, u8, String), - GetInputNames(String, Sender>), - SetDeckName(String, String), - GetDeckName(String, Sender), -} - -#[dbus_proxy( - default_service = "dog.asonix.git.asonix.StreamdeckDaemon", - interface = "dog.asonix.git.asonix.StreamdeckDaemon", - default_path = "/dog/asonix/git/asonix/StreamdeckDaemon" -)] -trait StreamdeckDaemon { - fn query(&self, query: &str) -> Result; - fn test(&self, command: &str) -> Result<()>; - fn enable_discovery(&self) -> Result<()>; - fn disable_discovery(&self) -> Result<()>; - fn get_decks(&self) -> Result>; - fn connect(&self, host: &str, port: u16) -> Result; - fn disconnect(&self) -> Result; - fn get_state(&self) -> Result; - fn login(&self, password: &str) -> Result; - fn get_commands(&self, serial_number: &str) -> Result>; - fn read_input(&self) -> Result>; - fn set_input(&self, serial_number: &str, key: u8, command: &str) -> Result<()>; - fn unset_input(&self, serial_number: &str, key: u8) -> Result<()>; - fn set_input_name(&self, serial_number: &str, key: u8, name: &str) -> Result<()>; - fn get_input_names(&self, serial_number: &str) -> Result>; - fn set_deck_name(&self, serial_number: &str, name: &str) -> Result<()>; - fn get_deck_name(&self, serial_number: &str) -> Result; -} - -impl State { - pub(crate) fn new() -> Self { - State { - inner: Arc::new(Mutex::new(StateInner { - state: ObsState::Disconnected, - ..StateInner::default() - })), - } - } - - pub(crate) fn decks(&self) -> Vec { - self.inner.lock().unwrap().decks.values().cloned().collect() - } - - pub(crate) fn deck_state(&self, serial_number: &str) -> Option { - let inner = self.inner.lock().unwrap(); - inner.decks.get(serial_number).map(Clone::clone) - } - - pub(crate) fn obs_state(&self) -> ObsState { - self.inner.lock().unwrap().state - } - - pub(crate) fn obs_state_stream(self) -> impl Stream { - let mut event_stream = { - let inner = self.inner.lock().unwrap(); - EventStream::new(&inner.state_event) - }; - - async_stream::stream! { - while let Some(_) = event_stream.next().await { - yield self.obs_state(); - } - } - } - - pub(crate) fn added_deck_stream(self) -> impl Stream { - let (tx, mut rx) = futures_channel::mpsc::channel(16); - - { - let mut inner = self.inner.lock().unwrap(); - inner.added_deck_txs.push(tx); - } - - let weak = Arc::downgrade(&self.inner); - drop(self); - - async_stream::stream! { - while let Some(serial_number) = rx.next().await { - if let Some(inner) = weak.upgrade() { - let state = State { inner }; - - if let Some(deck_state) = state.deck_state(&serial_number) { - yield deck_state; - } - } else { - break; - } - } - } - } - - pub(crate) fn removed_deck_stream(self) -> impl Stream { - let (tx, rx) = futures_channel::mpsc::channel(16); - - let mut inner = self.inner.lock().unwrap(); - inner.removed_deck_txs.push(tx); - - rx - } - - fn update_obs_state(&self, state: ObsState) { - let mut inner = self.inner.lock().unwrap(); - if inner.state != state { - inner.state = state; - inner.state_event.notify(usize::MAX); - } - } - - fn update_decks(&self, decks: Vec) { - let mut decks = decks - .iter() - .map(|deck_info| (deck_info.serial_number.clone(), deck_info.clone())) - .collect::>(); - - let new_decks = decks.keys().cloned().collect::>(); - - let mut inner = self.inner.lock().unwrap(); - let current_decks = inner.decks.keys().cloned().collect::>(); - - for added_deck in new_decks.difference(¤t_decks) { - if let Some(deck) = decks.remove(added_deck) { - let deck_state = DeckState::from_deck_info(deck); - inner.decks.insert(added_deck.to_owned(), deck_state); - - let mut new_senders = Vec::new(); - for mut sender in inner.added_deck_txs.drain(..) { - if sender.is_closed() { - continue; - } - - let _ = sender.try_send(added_deck.to_owned()); - new_senders.push(sender); - } - inner.added_deck_txs = new_senders; - } - } - - for removed_deck in current_decks.difference(&new_decks) { - inner.decks.remove(removed_deck); - - let mut new_senders = Vec::new(); - for mut sender in inner.removed_deck_txs.drain(..) { - if sender.is_closed() { - continue; - } - - let _ = sender.try_send(removed_deck.to_owned()); - new_senders.push(sender); - } - inner.removed_deck_txs = new_senders; - } - } -} - -impl DeckState { - pub(crate) fn commands(&self) -> Vec { - self.inner - .lock() - .unwrap() - .commands - .values() - .cloned() - .collect() - } - - pub(crate) fn command_state(&self, key: u8) -> Option { - self.inner - .lock() - .unwrap() - .commands - .get(&key) - .map(Clone::clone) - } - - pub(crate) fn serial_number(&self) -> String { - self.inner.lock().unwrap().info.serial_number.clone() - } - - pub(crate) fn device_name(&self) -> String { - self.inner.lock().unwrap().info.device_name.clone() - } - - pub(crate) fn port_name(&self) -> String { - self.inner.lock().unwrap().info.port_name.clone() - } - - pub(crate) fn device_name_stream(self) -> impl Stream { - let mut event_stream = { - let inner = self.inner.lock().unwrap(); - EventStream::new(&inner.name_event) - }; - - let inner = Arc::downgrade(&self.inner); - - async_stream::stream! { - while let Some(_) = event_stream.next().await { - if let Some(inner) = inner.upgrade() { - let name = { - let inner = inner.lock().unwrap(); - inner.info.device_name.clone() - }; - - yield name; - } else { - break; - } - } - } - } - - pub(crate) fn added_command_stream(self) -> impl Stream { - let (tx, mut rx) = futures_channel::mpsc::channel(16); - - { - let mut inner = self.inner.lock().unwrap(); - inner.added_command_txs.push(tx); - } - - let weak = Arc::downgrade(&self.inner); - drop(self); - - async_stream::stream! { - while let Some(key) = rx.next().await { - if let Some(inner) = weak.upgrade() { - let deck_state = DeckState { inner }; - if let Some(command_state) = deck_state.command_state(key) { - yield command_state; - } - } else { - break; - } - } - } - } - - pub(crate) fn removed_command_stream(self) -> impl Stream { - let (tx, rx) = futures_channel::mpsc::channel(16); - - let mut inner = self.inner.lock().unwrap(); - inner.removed_command_txs.push(tx); - rx - } - - fn from_deck_info(deck_info: DeckInfo) -> Self { - DeckState { - inner: Arc::new(Mutex::new(DeckStateInner { - info: deck_info, - name_event: Arc::new(Event::new()), - commands: HashMap::new(), - added_command_txs: Vec::new(), - removed_command_txs: Vec::new(), - })), - } - } - - fn update_device_name(&self, name: String) { - let mut inner = self.inner.lock().unwrap(); - inner.info.device_name = name; - inner.name_event.notify(usize::MAX); - } - - fn update_commands(&self, commands: Vec) { - let new_keys = commands.iter().map(|ci| ci.key).collect::>(); - - let mut inner = self.inner.lock().unwrap(); - let current_keys = inner.commands.keys().cloned().collect::>(); - - let mut infos = commands - .into_iter() - .map(|ci| (ci.key, ci.command)) - .collect::>(); - - for added_key in new_keys.difference(¤t_keys) { - if let Some(command) = infos.remove(added_key) { - let command_state = CommandState::from_command(*added_key, command); - inner.commands.insert(*added_key, command_state); - - let mut new_senders = Vec::new(); - for mut sender in inner.added_command_txs.drain(..) { - if sender.is_closed() { - continue; - } - - let _ = sender.try_send(*added_key); - new_senders.push(sender); - } - inner.added_command_txs = new_senders; - } - } - - for removed_key in current_keys.difference(&new_keys) { - inner.commands.remove(removed_key); - - let mut new_senders = Vec::new(); - for mut sender in inner.removed_command_txs.drain(..) { - if sender.is_closed() { - continue; - } - - let _ = sender.try_send(*removed_key); - new_senders.push(sender); - } - inner.removed_command_txs = new_senders; - } - - for (key, command) in infos { - if let Some(command_state) = inner.commands.get(&key) { - command_state.update_command(command); - } - } - } -} - -impl CommandState { - pub(crate) fn key(&self) -> u8 { - self.inner.lock().unwrap().key - } - - pub(crate) fn command(&self) -> Command { - self.inner.lock().unwrap().command.clone() - } - - pub(crate) fn command_stream(self) -> impl Stream { - let mut event_stream = { - let inner = self.inner.lock().unwrap(); - EventStream::new(&inner.command_event) - }; - - let inner = Arc::downgrade(&self.inner); - - async_stream::stream! { - while let Some(_) = event_stream.next().await { - if let Some(inner) = inner.upgrade() { - let command = { - let inner = inner.lock().unwrap(); - inner.command.clone() - }; - - yield command; - } else { - break; - } - } - } - } - - pub(crate) fn name(&self) -> String { - let inner = self.inner.lock().unwrap(); - inner.name.clone().unwrap_or(format!("{}", inner.key)) - } - - pub(crate) fn name_stream(self) -> impl Stream { - let mut event_stream = { - let inner = self.inner.lock().unwrap(); - EventStream::new(&inner.name_event) - }; - - let inner = Arc::downgrade(&self.inner); - - async_stream::stream! { - while let Some(_) = event_stream.next().await { - if let Some(inner) = inner.upgrade() { - let name = { - let inner = inner.lock().unwrap(); - inner.name.clone() - }; - - if let Some(name) = name { - yield name; - } - } else { - break; - } - } - } - } - - fn from_command(key: u8, command: Command) -> Self { - CommandState { - inner: Arc::new(Mutex::new(CommandStateInner { - key, - command, - command_event: Arc::new(Event::new()), - name: None, - name_event: Arc::new(Event::new()), - })), - } - } - - fn update_command(&self, command: Command) { - let mut inner = self.inner.lock().unwrap(); - inner.command = command; - inner.command_event.notify(usize::MAX); - } - - fn update_name(&self, name: String) { - let mut inner = self.inner.lock().unwrap(); - inner.name = Some(name); - inner.name_event.notify(usize::MAX); - } -} - -impl Handle { - pub(crate) fn current() -> Self { - static DAEMON: Lazy>> = Lazy::new(|| { - OnDemand::new(|| { - DropLog::new( - Daemon { - tx: connect(), - state: State::new(), - }, - "Daemon", - ) - }) - }); - - let _arc = (&DAEMON).get(); - - Handle { - tx: _arc.tx.clone(), - _arc, - } - } - - pub(crate) async fn get_scenes(&mut self) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - let query = serde_json::to_string(&Query::GetScenes)?; - self.tx.send(DBusMessage::Query(tx, query)).await?; - - let response = rx.await?; - let response: QueryResponse = serde_json::from_str(&response)?; - - // TODO: update OBS related messages to update OBS state - match response { - QueryResponse::Scenes { scenes } => Ok(scenes), - _ => Err(anyhow::anyhow!("Wrong response type")), - } - } - - pub(crate) async fn get_scene_items( - &mut self, - scene_name: String, - ) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - let query = serde_json::to_string(&Query::GetSceneItems { scene_name })?; - self.tx.send(DBusMessage::Query(tx, query)).await?; - - let response = rx.await?; - let response: QueryResponse = serde_json::from_str(&response)?; - - // TODO: update OBS related messages to update OBS state - match response { - QueryResponse::SceneItems { items } => Ok(items), - _ => Err(anyhow::anyhow!("Wrong response type")), - } - } - - pub(crate) async fn get_scene_item( - &mut self, - scene_name: String, - item_id: i64, - ) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - let query = serde_json::to_string(&Query::GetSceneItem { - scene_name, - item_id, - })?; - self.tx.send(DBusMessage::Query(tx, query)).await?; - - let response = rx.await?; - let response: QueryResponse = serde_json::from_str(&response)?; - - // TODO: update OBS related messages to update OBS state - match response { - QueryResponse::SceneItem { item } => Ok(item), - _ => Err(anyhow::anyhow!("Wrong response type")), - } - } - - pub(crate) async fn test(&mut self, command: &Command) -> anyhow::Result<()> { - let command = serde_json::to_string(command)?; - self.tx.send(DBusMessage::Test(command)).await?; - Ok(()) - } - - pub(crate) async fn enable_discovery(&mut self) -> anyhow::Result<()> { - self.tx.send(DBusMessage::EnableDiscovery).await?; - Ok(()) - } - - pub(crate) async fn disable_discovery(&mut self) -> anyhow::Result<()> { - self.tx.send(DBusMessage::DisableDiscovery).await?; - Ok(()) - } - - pub(crate) fn state(&self) -> State { - self._arc.state.clone() - } - - async fn get_decks(&mut self) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::GetDecks(tx)).await?; - - Ok(rx - .await? - .into_iter() - .map(|(serial_number, device_name, port_name)| DeckInfo { - serial_number, - device_name, - port_name, - }) - .collect::>()) - } - - pub(crate) async fn connect(&mut self, host: String, port: u16) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::Connect(host, port, tx)).await?; - - let state = rx.await?.parse()?; - - self.state().update_obs_state(state); - - Ok(state) - } - - pub(crate) async fn disconnect(&mut self) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::Disconnect(tx)).await?; - - let state = rx.await?.parse()?; - - self.state().update_obs_state(state); - - Ok(state) - } - - async fn get_state(&mut self) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::GetState(tx)).await?; - - let state = rx.await?.parse()?; - - self.state().update_obs_state(state); - - Ok(state) - } - - pub(crate) async fn login(&mut self, password: String) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::Login(password, tx)).await?; - - let state = rx.await?.parse()?; - - self.state().update_obs_state(state); - - Ok(state) - } - - async fn get_commands(&mut self, serial_number: String) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx - .send(DBusMessage::GetCommands(serial_number, tx)) - .await?; - - Ok(rx - .await? - .into_iter() - .map(|(key, command)| { - Ok(CommandInfo { - key, - command: serde_json::from_str(&command)?, - }) - }) - .collect::>>()?) - } - - pub(crate) async fn read_input(&mut self) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx.send(DBusMessage::ReadInput(tx)).await?; - - Ok(rx - .await? - .into_iter() - .map(|(key, serial_number)| ReadInput { key, serial_number }) - .collect::>()) - } - - pub(crate) async fn set_input( - &mut self, - serial_number: String, - key: u8, - command: &Command, - ) -> anyhow::Result<()> { - let command = serde_json::to_string(command)?; - self.tx - .send(DBusMessage::SetInput(serial_number.clone(), key, command)) - .await?; - - cache_commands(serial_number); - - Ok(()) - } - - pub(crate) async fn unset_input( - &mut self, - serial_number: String, - key: u8, - ) -> anyhow::Result<()> { - self.tx - .send(DBusMessage::UnsetInput(serial_number.clone(), key)) - .await?; - - cache_commands(serial_number); - - Ok(()) - } - - pub(crate) async fn set_input_name( - &mut self, - serial_number: String, - key: u8, - name: String, - ) -> anyhow::Result<()> { - self.tx - .send(DBusMessage::SetInputName(serial_number.clone(), key, name)) - .await?; - - cache_input_names(serial_number); - - Ok(()) - } - - async fn get_input_names(&mut self, serial_number: String) -> anyhow::Result> { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx - .send(DBusMessage::GetInputNames(serial_number, tx)) - .await?; - - Ok(rx - .await? - .into_iter() - .map(|(key, name)| InputName { key, name }) - .collect::>()) - } - - pub(crate) async fn set_deck_name( - &mut self, - serial_number: String, - name: String, - ) -> anyhow::Result<()> { - self.tx - .send(DBusMessage::SetDeckName(serial_number.clone(), name)) - .await?; - - cache_deck_name(serial_number); - - Ok(()) - } - - async fn get_deck_name(&mut self, serial_number: String) -> anyhow::Result { - let (tx, rx) = futures_channel::oneshot::channel(); - - self.tx - .send(DBusMessage::GetDeckName(serial_number, tx)) - .await?; - - Ok(rx.await?) - } -} - -fn cache_decks() { - glib::MainContext::default().spawn_local(async move { - if let Ok(decks) = Handle::current().get_decks().await { - Handle::current().state().update_decks(decks); - } - }); -} - -fn cache_obs_state() { - glib::MainContext::default().spawn_local(async move { - if let Ok(obs_state) = Handle::current().get_state().await { - Handle::current().state().update_obs_state(obs_state); - } - }); -} - -fn cache_commands(serial_number: String) { - glib::MainContext::default().spawn_local(async move { - if let Ok(commands) = Handle::current().get_commands(serial_number.clone()).await { - if let Some(deck_state) = Handle::current().state().deck_state(&serial_number) { - deck_state.update_commands(commands); - } - } - cache_input_names(serial_number); - }); -} - -fn cache_input_names(serial_number: String) { - glib::MainContext::default().spawn_local(async move { - if let Ok(names) = Handle::current() - .get_input_names(serial_number.clone()) - .await - { - if let Some(deck_state) = Handle::current().state().deck_state(&serial_number) { - for InputName { key, name } in names { - if let Some(command_state) = deck_state.command_state(key) { - command_state.update_name(name); - } - } - } - } - }); -} - -fn cache_deck_name(serial_number: String) { - glib::MainContext::default().spawn_local(async move { - if let Ok(name) = Handle::current().get_deck_name(serial_number.clone()).await { - if let Some(deck_state) = Handle::current().state().deck_state(&serial_number) { - deck_state.update_device_name(name); - } - } - }); -} - -impl DropLog { - fn new(inner: T, name: &'static str) -> Self { - log::debug!("Constructed {}", name); - - DropLog { inner, name } - } -} - -impl std::ops::Deref for DropLog { - type Target = T; - - fn deref(&self) -> &T { - &self.inner - } -} - -impl std::ops::DerefMut for DropLog { - fn deref_mut(&mut self) -> &mut T { - &mut self.inner - } -} - -impl Drop for DropLog { - fn drop(&mut self) { - log::debug!("Dropping {}", self.name); - } -} - -impl OnDemand { - fn new(init: impl Fn() -> T + Send + Sync + 'static) -> Self { - OnDemand { - inner: Arc::new(Mutex::new(Weak::new())), - init: Arc::new(init), - } - } - - fn get(&self) -> Arc { - let opt = self.inner.lock().unwrap().upgrade(); - if let Some(arc) = opt { - arc - } else { - let arc = Arc::new((self.init)()); - *self.inner.lock().unwrap() = Arc::downgrade(&arc); - arc - } - } -} - -fn connect() -> futures_channel::mpsc::Sender { - let (tx, mut rx) = futures_channel::mpsc::channel(16); - - log::debug!("Spawning connection pool"); - - let mut senders = (0..16).map(|_| spawn_connection()).collect::>(); - - glib::MainContext::default().spawn(async move { - let mut count = 0; - let mut start; - let len = senders.len(); - - while let Some(mut msg) = rx.next().await { - start = count; - - while let Err(e) = senders[count].try_send(msg) { - msg = e.into_inner(); - count = (count + 1) % len; - - if count == start { - let _ = senders[count].send(msg).await; - break; - } - } - - count = (count + 1) % len; - } - - log::debug!("Shutting down dbus connection pool"); - }); - - tx -} - -async fn process_messages(rx: &mut Receiver) -> anyhow::Result<()> { - let conn = Connection::session().await?; - let daemon = StreamdeckDaemonProxy::new(&conn).await?; - - while let Some(msg) = rx.next().await { - match msg { - DBusMessage::Query(sender, query) => { - let response = daemon.query(&query).await?; - let _ = sender.send(response); - } - DBusMessage::Test(command) => { - daemon.test(&command).await?; - } - DBusMessage::EnableDiscovery => { - daemon.enable_discovery().await?; - } - DBusMessage::DisableDiscovery => { - daemon.disable_discovery().await?; - } - DBusMessage::GetDecks(sender) => { - let decks = daemon.get_decks().await?; - let _ = sender.send(decks); - } - DBusMessage::Connect(host, port, sender) => { - let state = daemon.connect(&host, port).await?; - let _ = sender.send(state); - } - DBusMessage::Disconnect(sender) => { - let state = daemon.disconnect().await?; - let _ = sender.send(state); - } - DBusMessage::GetState(sender) => { - let state = daemon.get_state().await?; - let _ = sender.send(state); - } - DBusMessage::Login(password, sender) => { - let state = daemon.login(&password).await?; - let _ = sender.send(state); - } - DBusMessage::GetCommands(serial_number, sender) => { - let commands = daemon.get_commands(&serial_number).await?; - let _ = sender.send(commands); - } - DBusMessage::ReadInput(sender) => { - let input = daemon.read_input().await?; - let _ = sender.send(input); - } - DBusMessage::SetInput(serial_number, key, command) => { - daemon.set_input(&serial_number, key, &command).await?; - } - DBusMessage::UnsetInput(serial_number, key) => { - daemon.unset_input(&serial_number, key).await?; - } - DBusMessage::SetInputName(serial_number, key, name) => { - daemon.set_input_name(&serial_number, key, &name).await?; - } - DBusMessage::GetInputNames(serial_number, sender) => { - let input_names = daemon.get_input_names(&serial_number).await?; - let _ = sender.send(input_names); - } - DBusMessage::SetDeckName(serial_number, name) => { - daemon.set_deck_name(&serial_number, &name).await?; - } - DBusMessage::GetDeckName(serial_number, sender) => { - let name = daemon.get_deck_name(&serial_number).await?; - let _ = sender.send(name); - } - } - } - - Ok(()) -} - -fn spawn_connection() -> futures_channel::mpsc::Sender { - let (tx, mut rx) = futures_channel::mpsc::channel(16); - - glib::MainContext::default().spawn(async move { - let mut now = Instant::now(); - let mut last_failure = now; - let mut failures_per_minute = 0f64; - let mut time_between_failures; - let mut failure_ratio; - let one_minute = Duration::from_secs(60); - - while let Err(e) = process_messages(&mut rx).await { - log::warn!("Disconnected from dbus: {}", e); - now = Instant::now(); - time_between_failures = now - last_failure; - last_failure = now; - - if time_between_failures > one_minute { - failures_per_minute = 1f64; - } else { - failure_ratio = - time_between_failures.as_secs() as f64 / one_minute.as_secs() as f64; - - failures_per_minute = ((1f64 - failure_ratio) * failures_per_minute) + 1f64; - } - - async_io::Timer::after(Duration::from_secs( - failures_per_minute.powf(1.5).min(0.1) as u64 - )) - .await; - } - - log::debug!("Shuting down dbus connection"); - }); - - tx -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "State") - } -} - -impl EventStream { - fn new(event: &Arc) -> Self { - EventStream { - listener: Some(event.listen()), - event: Arc::downgrade(event), - } - } -} - -impl Stream for EventStream { - type Item = (); - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let event = match self.event.upgrade() { - Some(event) => event, - None => return Poll::Ready(None), - }; - - let mut listener = self.listener.take().unwrap_or_else(|| event.listen()); - - match Pin::new(&mut listener).poll(cx) { - Poll::Ready(()) => { - let mut listener; - - while { - listener = event.listen(); - Pin::new(&mut listener).poll(cx).is_ready() - } {} - - self.listener = Some(listener); - Poll::Ready(Some(())) - } - Poll::Pending => { - self.listener = Some(listener); - Poll::Pending - } - } - } -} diff --git a/streamdeck-gtk/src/dbus.rs b/streamdeck-gtk/src/dbus.rs new file mode 100644 index 0000000..bdc380c --- /dev/null +++ b/streamdeck-gtk/src/dbus.rs @@ -0,0 +1,76 @@ +use streamdeck_common::Input; + +#[zbus::dbus_proxy( + interface = "dog.asonix.git.asonix.StreamdeckDaemon", + default_service = "dog.asonix.git.asonix.StreamdeckDaemon", + default_path = "/dog/asonix/git/asonix/StreamdeckDaemon" +)] +trait Daemon { + #[dbus_proxy(property)] + fn discovery_enabled(&self) -> zbus::fdo::Result; + + #[dbus_proxy(property)] + fn set_discovery_enabled(&self, enabled: bool) -> zbus::fdo::Result<()>; + + fn get_decks(&self) -> zbus::fdo::Result>; + + #[dbus_proxy(signal)] + fn deck_added(&self, path: &str) -> zbus::fdo::Result<()>; + + #[dbus_proxy(signal)] + fn deck_removed(&self, path: &str) -> zbus::fdo::Result<()>; +} + +#[zbus::dbus_proxy( + interface = "dog.asonix.git.asonix.StreamdeckDaemon.Deck", + default_service = "dog.asonix.git.asonix.StreamdeckDaemon.Deck" +)] +trait Deck { + #[dbus_proxy(property)] + fn name(&self) -> zbus::fdo::Result; + + #[dbus_proxy(property)] + fn set_name(&self, name: String) -> zbus::fdo::Result<()>; + + #[dbus_proxy(property)] + fn product_name(&self) -> zbus::fdo::Result; + + #[dbus_proxy(property)] + fn port_name(&self) -> zbus::fdo::Result; + + fn get_buttons(&self) -> zbus::fdo::Result>; + + fn create_button(&self, input: Input, command: String) -> zbus::fdo::Result; + + fn remove_button(&self, input: Input) -> zbus::fdo::Result<()>; + + #[dbus_proxy(signal)] + fn button_pushed(&self, input: Input) -> zbus::fdo::Result<()>; + + #[dbus_proxy(signal)] + fn button_added(&self, input: Input) -> zbus::fdo::Result<()>; + + #[dbus_proxy(signal)] + fn button_removed(&self, input: Input) -> zbus::fdo::Result<()>; +} + +#[zbus::dbus_proxy( + interface = "dog.asonix.git.asonix.StreamdeckDaemon.Deck.Button", + default_service = "dog.asonix.git.asonix.StreamdeckDaemon.Deck.Button" +)] +trait Button { + #[dbus_proxy(property)] + fn name(&self) -> zbus::fdo::Result; + + #[dbus_proxy(property)] + fn set_name(&self, name: String) -> zbus::fdo::Result<()>; + + #[dbus_proxy(property)] + fn command(&self) -> zbus::fdo::Result; + + #[dbus_proxy(property)] + fn set_command(&self, command: String) -> zbus::fdo::Result<()>; + + #[dbus_proxy(signal)] + fn pushed(&self) -> zbus::fdo::Result<()>; +} diff --git a/streamdeck-gtk/src/dialogs/new_command_dialog.rs b/streamdeck-gtk/src/dialogs/new_command_dialog.rs index d0d4177..f3e5a20 100644 --- a/streamdeck-gtk/src/dialogs/new_command_dialog.rs +++ b/streamdeck-gtk/src/dialogs/new_command_dialog.rs @@ -97,7 +97,7 @@ fn read_input( for keypress in keypresses { if keypress.serial_number == *serial_number { should_break = true; - command_page.set_input_key(keypress.key); + command_page.set_input_keys(keypress.keys); stack.set_visible_child_name("command"); } } diff --git a/streamdeck-gtk/src/main.rs b/streamdeck-gtk/src/main.rs index df5f29b..48a6cf5 100644 --- a/streamdeck-gtk/src/main.rs +++ b/streamdeck-gtk/src/main.rs @@ -4,7 +4,7 @@ use once_cell::unsync::Lazy; mod application; mod command; mod config; -mod daemon; +mod dbus; mod dialogs; mod main_window; mod views; @@ -28,12 +28,9 @@ fn saved_state() -> Settings { SAVED_STATE.with(|settings| (*settings).clone()) } -fn main() { - if std::env::var("RUST_LOG").is_err() { - std::env::set_var("RUST_LOG", "info"); - } - env_logger::init(); - gtk::init().expect("Failed to initialize gtk"); +fn main() -> anyhow::Result<()> { + init_tracing()?; + gtk::init()?; libhandy::functions::init(); let handle = daemon::Handle::current(); @@ -41,4 +38,32 @@ fn main() { application::App::new().run_with_args(&std::env::args().collect::>()); drop(handle); + Ok(()) +} + +fn init_tracing() -> anyhow::Result<()> { + use tracing::subscriber::set_global_default; + use tracing_error::ErrorLayer; + use tracing_log::LogTracer; + use tracing_subscriber::{ + filter::Targets, fmt::format::FmtSpan, layer::SubscriberExt, Layer, Registry, + }; + + LogTracer::init()?; + + let targets = std::env::var("RUST_LOG") + .unwrap_or_else(|_| "streamdeck_daemon=debug,streamdeck_handshake=debug,info".into()) + .parse::()?; + + let format_layer = tracing_subscriber::fmt::layer() + .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE) + .with_filter(targets); + + let subscriber = Registry::default() + .with(format_layer) + .with(ErrorLayer::default()); + + set_global_default(subscriber)?; + + Ok(()) } diff --git a/streamdeck-gtk/src/views/command_config.rs b/streamdeck-gtk/src/views/command_config.rs index 7c19abc..2ba8e91 100644 --- a/streamdeck-gtk/src/views/command_config.rs +++ b/streamdeck-gtk/src/views/command_config.rs @@ -1,7 +1,7 @@ use crate::daemon::{CommandState, Handle}; use futures_util::stream::StreamExt; use gtk::{prelude::*, subclass::prelude::*}; -use streamdeck_common::{Command, CommandVariant, StateOperation, VisibilityOperation}; +use streamdeck_common::{Command, CommandVariant, Input, StateOperation, VisibilityOperation}; glib::wrapper! { pub struct CommandConfig(ObjectSubclass) @@ -17,7 +17,7 @@ impl CommandConfig { this } - pub(crate) fn set_input_key(&self, key: u8) { + pub(crate) fn set_input_keys(&self, key: Input) { { let this = imp::CommandConfig::from_instance(self); this.input_key.set(key).unwrap(); @@ -29,7 +29,7 @@ impl CommandConfig { pub(crate) fn register_command(&self, command_state: CommandState) { self.set_command(command_state.command()); self.set_name(command_state.name()); - self.set_input_key(command_state.key()); + self.set_input_keys(command_state.keys()); let stream = command_state.command_stream(); let weak = self.downgrade(); @@ -384,7 +384,7 @@ mod imp { use gtk::{prelude::*, subclass::prelude::*}; use once_cell::unsync::OnceCell; use std::cell::RefCell; - use streamdeck_common::{Command, CommandVariant, StateOperation, VisibilityOperation}; + use streamdeck_common::{Command, CommandVariant, Input, StateOperation, VisibilityOperation}; #[derive(Debug)] pub(super) struct SwitchSceneState { @@ -411,7 +411,7 @@ mod imp { #[derive(Debug, Default)] pub struct CommandConfig { pub(super) serial_number: OnceCell, - pub(super) input_key: OnceCell, + pub(super) input_key: OnceCell, pub(super) command: RefCell>, pub(super) command_stack: OnceCell, pub(super) command_name: OnceCell, diff --git a/streamdeck-gtk/src/widgets/command_list.rs b/streamdeck-gtk/src/widgets/command_list.rs index 04f7f45..2165426 100644 --- a/streamdeck-gtk/src/widgets/command_list.rs +++ b/streamdeck-gtk/src/widgets/command_list.rs @@ -5,6 +5,7 @@ use crate::{ use futures_util::stream::StreamExt; use gtk::{prelude::*, subclass::prelude::*}; use marble::widgets::Dialog; +use streamdeck_common::Input; glib::wrapper! { pub struct CommandList(ObjectSubclass) @@ -42,9 +43,9 @@ impl CommandList { glib::MainContext::default().spawn_local(async move { futures_util::pin_mut!(stream); - while let Some(key) = stream.next().await { + while let Some(keys) = stream.next().await { if let Some(this) = weak.upgrade() { - this.remove_command(key); + this.remove_command(keys); } else { break; } @@ -60,22 +61,22 @@ impl CommandList { let serial_number = inner.serial_number.get().unwrap(); let mut rows = inner.rows.borrow_mut(); - if rows.contains_key(&command.key()) { + if rows.contains_key(&command.keys()) { return; } - let key = command.key(); + let keys = command.keys(); let row = CommandRow::new(serial_number, command); list_box.add(&row); - rows.insert(key, row); + rows.insert(keys, row); } - fn remove_command(&self, key: u8) { + fn remove_command(&self, keys: Input) { let inner = imp::CommandList::from_instance(self); let mut rows = inner.rows.borrow_mut(); - if let Some(row) = rows.remove(&key) { + if let Some(row) = rows.remove(&keys) { let list_box = inner.list_box.get().unwrap(); list_box.remove(&row); } @@ -89,16 +90,16 @@ impl CommandList { let command_row = row.downcast::().ok()?; let serial_number = command_row.serial_number(); - let input_key = command_row.input_key(); + let input_keys = command_row.input_keys(); glib::MainContext::default().spawn_local( glib::clone!(@weak self as obj, @weak command_row => async move { let this = imp::CommandList::from_instance(&obj); let list_box = this.list_box.get().unwrap(); - if Handle::current().unset_input(serial_number, input_key).await.is_ok() { + if Handle::current().unset_input(serial_number, input_keys.clone()).await.is_ok() { list_box.remove(&command_row); - this.rows.borrow_mut().remove(&input_key); + this.rows.borrow_mut().remove(&input_keys); } obj.show_all(); @@ -154,12 +155,13 @@ mod imp { use marble::widgets::{AlertView, Dialog}; use once_cell::unsync::OnceCell; use std::{cell::RefCell, collections::HashMap}; + use streamdeck_common::Input; #[derive(Debug, Default)] pub struct CommandList { pub(super) serial_number: OnceCell, pub(super) list_box: OnceCell, - pub(super) rows: RefCell>, + pub(super) rows: RefCell>, pub(super) dialog: RefCell>, } diff --git a/streamdeck-gtk/src/widgets/command_row.rs b/streamdeck-gtk/src/widgets/command_row.rs index 411347c..6d800a8 100644 --- a/streamdeck-gtk/src/widgets/command_row.rs +++ b/streamdeck-gtk/src/widgets/command_row.rs @@ -6,7 +6,7 @@ use futures_util::stream::StreamExt; use gtk::{prelude::*, subclass::prelude::*}; use marble::widgets::Dialog; use std::{cell::RefCell, rc::Rc}; -use streamdeck_common::Command; +use streamdeck_common::{Command, Input}; glib::wrapper! { pub struct CommandRow(ObjectSubclass) @@ -30,7 +30,7 @@ impl CommandRow { let row_state = CommandRowState::build(&command_state.command(), command_grid); inner.row_state.set(row_state).unwrap(); - inner.input_key.set(command_state.key()).unwrap(); + inner.input_key.set(command_state.keys()).unwrap(); inner .command .set(Rc::new(RefCell::new(command_state.command()))) @@ -86,10 +86,10 @@ impl CommandRow { serial_number.clone() } - pub(crate) fn input_key(&self) -> u8 { + pub(crate) fn input_keys(&self) -> Input { let this = imp::CommandRow::from_instance(self); - let input_key = this.input_key.get().unwrap(); - *input_key + let input_keys = this.input_key.get().unwrap(); + input_keys.clone() } fn present_dialog(&self) { @@ -102,7 +102,7 @@ impl CommandRow { let command_state = if let Some(deck_state) = Handle::current().state().deck_state(&self.serial_number()) { - if let Some(command_state) = deck_state.command_state(self.input_key()) { + if let Some(command_state) = deck_state.command_state(self.input_keys()) { command_state } else { return; @@ -149,14 +149,14 @@ mod imp { use marble::widgets::Dialog; use once_cell::unsync::OnceCell; use std::{cell::RefCell, rc::Rc}; - use streamdeck_common::Command; + use streamdeck_common::{Command, Input}; #[derive(Debug, Default)] pub struct CommandRow { pub(super) row_title: OnceCell, pub(super) command_grid: OnceCell, pub(super) serial_number: OnceCell, - pub(super) input_key: OnceCell, + pub(super) input_key: OnceCell, pub(super) command: OnceCell>>, pub(super) row_state: OnceCell, pub(super) dialog: RefCell>,