diff options
Diffstat (limited to 'src/bin')
-rw-r--r-- | src/bin/icon.rs | 78 | ||||
-rw-r--r-- | src/bin/next.rs | 97 | ||||
-rw-r--r-- | src/bin/play.rs | 129 | ||||
-rw-r--r-- | src/bin/prev.rs | 97 | ||||
-rw-r--r-- | src/bin/title.rs | 183 | ||||
-rw-r--r-- | src/bin/volume.rs | 109 |
6 files changed, 338 insertions, 355 deletions
diff --git a/src/bin/icon.rs b/src/bin/icon.rs index 6f2487f..4ffaaae 100644 --- a/src/bin/icon.rs +++ b/src/bin/icon.rs @@ -1,54 +1,52 @@ -use std::sync::Arc; - use i3blocks::{ - color_listener, - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, - i3bar::Block, - player_listener, + dbus::{media_player2::MediaPlayer2Proxy, player::PlaybackStatus, playerctld::PlayerctldProxy}, + i3bar::{Block, Click}, + Button, Component, Error, }; -use tokio::{sync::Mutex, task::JoinSet}; use zbus::Connection; #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); - - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_status, mut rx_status) = tokio::sync::mpsc::channel(128); - - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; - - tokio::spawn(player_listener(tx_player, proxy)); - - let status: Arc<Mutex<Block>> = Arc::new(Mutex::new(Block { + i3blocks::run::<Icon>(Block { full_text: " ".into(), ..Default::default() - })); + }) + .await + .map_err(Into::into) +} - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(color_listener(tx_status.clone(), proxy.clone())); - } - } - Some((color, background)) = rx_status.recv() => { - let mut status = status.lock().await; - status.color = color; - status.background = background; +pub struct Icon; + +impl Component for Icon { + const NAME: &'static str = "icon"; + type Updater = (); + type Colorer = PlaybackStatus; + type Handler = Self; +} + +impl Button for Icon { + async fn handle(conn: Connection, click: Click) -> Result<(), Error> { + let Some(name) = click.instance else { + return Ok(()); + }; + + let proxy = MediaPlayer2Proxy::builder(&conn) + .destination(name)? + .build() + .await?; + + match click.button { + 3 => { + PlayerctldProxy::builder(&conn) + .build() + .await? + .shift() + .await?; } + 1 if proxy.can_raise().await? => proxy.raise().await?, + _ => {} } - let s = status.lock().await; - s.write_stdout()?; + Ok(()) } } diff --git a/src/bin/next.rs b/src/bin/next.rs index 108432c..2e3fd95 100644 --- a/src/bin/next.rs +++ b/src/bin/next.rs @@ -1,72 +1,69 @@ use std::sync::Arc; -use futures_util::StreamExt; use i3blocks::{ - color_listener, - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, - i3bar::Block, - player_listener, Error, -}; -use tokio::{ - sync::{mpsc::Sender, Mutex}, - task::JoinSet, + dbus::player::{PlaybackStatus, PlayerProxy}, + i3bar::{Block, Click}, + Button, Component, Error, Update, }; +use tokio::sync::{mpsc::Sender, Mutex}; use zbus::Connection; #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); + i3blocks::run::<Next>(Default::default()) + .await + .map_err(Into::into) +} - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_status, mut rx_status) = tokio::sync::mpsc::channel(128); - let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128); +pub struct Next; - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; +impl Component for Next { + const NAME: &'static str = "next"; + type Updater = Self; + type Colorer = PlaybackStatus; + type Handler = Self; +} - tokio::spawn(player_listener(tx_player, proxy)); +impl Update for Next { + type Value = bool; - let status: Arc<Mutex<Block>> = Default::default(); + async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { + use futures_util::StreamExt; - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(color_listener(tx_status.clone(), proxy.clone())); - join_set.spawn(value_listener(tx_value.clone(), proxy)); - } - } - Some((color, background)) = rx_status.recv() => { - let mut status = status.lock().await; - status.color = color; - status.background = background; - } - Some(value) = rx_value.recv() => { - let mut status = status.lock().await; - status.full_text = value.then_some( " ".into()).unwrap_or_default() + tx.send(proxy.can_go_previous().await?).await?; + let mut stream = proxy.receive_can_go_previous_changed().await; + while let Some(signal) = stream.next().await { + if let Ok(value) = signal.get().await { + tx.send(value).await?; } } + Ok(()) + } - let s = status.lock().await; - s.write_stdout()?; + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + let mut block = block.lock().await; + block.full_text = value.then_some(" ".into()).unwrap_or_default(); + Ok(true) } } -pub async fn value_listener(tx: Sender<bool>, proxy: PlayerProxy<'_>) -> Result<(), Error> { - tx.send(proxy.can_go_next().await?).await?; - let mut stream = proxy.receive_can_go_next_changed().await; - while let Some(signal) = stream.next().await { - if let Ok(value) = signal.get().await { - tx.send(value).await?; +impl Button for Next { + async fn handle(conn: Connection, click: Click) -> Result<(), Error> { + let Some(name) = click.instance else { + return Ok(()); + }; + + let proxy = PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await?; + + match click.button { + 1 if proxy.can_go_next().await? => proxy.next().await?, + 3 if proxy.can_seek().await? => proxy.seek(5000).await?, + _ => {} } + + Ok(()) } - Ok(()) } diff --git a/src/bin/play.rs b/src/bin/play.rs index 0863b7a..bf4f489 100644 --- a/src/bin/play.rs +++ b/src/bin/play.rs @@ -1,96 +1,79 @@ use std::sync::Arc; -use futures_util::StreamExt; use i3blocks::{ - dbus::{ - player::{PlaybackStatus, PlayerProxy}, - playerctld::PlayerctldProxy, - }, - i3bar::Block, - Error, -}; -use tokio::{ - sync::{mpsc::Sender, Mutex}, - task::JoinSet, + dbus::player::{PlaybackStatus, PlayerProxy}, + i3bar::{Block, Click}, + Button, Component, Error, Update, }; +use tokio::sync::{mpsc::Sender, Mutex}; use zbus::Connection; #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); + i3blocks::run::<Play>(Default::default()) + .await + .map_err(Into::into) +} - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128); +pub struct Play; - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; +impl Component for Play { + const NAME: &'static str = "play"; + type Updater = Self; + type Colorer = (); + type Handler = Self; +} - tokio::spawn(player_listener(tx_player, proxy)); +impl Update for Play { + type Value = PlaybackStatus; - let status: Arc<Mutex<Block>> = Default::default(); + async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { + use futures_util::StreamExt; - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - println!("name: {name:?}"); - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(value_listener(tx_value.clone(), proxy)); - } - } - Some(value) = rx_value.recv() => { - println!("value: {value:?}"); - let mut status = status.lock().await; - let (color, background) = value.into(); - status.color = color; - status.background = background; - status.full_text = match value { - PlaybackStatus::Playing => " ", - _ => " ", - }.into(); + tx.send(proxy.playback_status().await?).await?; + let mut stream = proxy.receive_playback_status_changed().await; + while let Some(signal) = stream.next().await { + if let Ok(value) = signal.get().await { + tx.send(value).await?; } } - - let s = status.lock().await; - s.write_stdout()?; + Ok(()) } -} -pub async fn value_listener( - tx: Sender<PlaybackStatus>, - proxy: PlayerProxy<'_>, -) -> Result<(), Error> { - tx.send(proxy.playback_status().await?).await?; - let mut stream = proxy.receive_playback_status_changed().await; - while let Some(signal) = stream.next().await { - if let Ok(value) = signal.get().await { - tx.send(value).await?; - } + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + let black = std::env::var("BASE16_COLOR_00_HEX").ok(); + let cyan = std::env::var("BASE16_COLOR_0C_HEX").ok(); + let yellow = std::env::var("BASE16_COLOR_0A_HEX").ok(); + + let (full_text, color, background) = match value { + PlaybackStatus::Playing => (" ", black.clone(), cyan.clone()), + PlaybackStatus::Paused => (" ", black.clone(), yellow.clone()), + PlaybackStatus::Stopped => (" ", None, None), + }; + + let mut block = block.lock().await; + block.full_text = full_text.into(); + block.color = color; + block.background = background; + Ok(true) } - Ok(()) } -async fn player_listener(tx: Sender<String>, proxy: PlayerctldProxy<'_>) -> Result<(), Error> { - tx.send( - proxy - .player_names() - .await? - .into_iter() - .next() - .unwrap_or_default(), - ) - .await?; +impl Button for Play { + async fn handle(conn: Connection, click: Click) -> Result<(), Error> { + let Some(name) = click.instance else { + return Ok(()); + }; + + if click.button == 1 { + PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await? + .play_pause() + .await? + } - let mut stream = proxy.receive_active_player_change_begin().await?; - while let Some(signal) = stream.next().await { - tx.send(signal.args()?.name.to_owned()).await?; + Ok(()) } - Ok(()) } diff --git a/src/bin/prev.rs b/src/bin/prev.rs index f7f8dd9..7eec6c3 100644 --- a/src/bin/prev.rs +++ b/src/bin/prev.rs @@ -1,72 +1,69 @@ use std::sync::Arc; -use futures_util::StreamExt; use i3blocks::{ - color_listener, - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, - i3bar::Block, - player_listener, Error, -}; -use tokio::{ - sync::{mpsc::Sender, Mutex}, - task::JoinSet, + dbus::player::{PlaybackStatus, PlayerProxy}, + i3bar::{Block, Click}, + Button, Component, Error, Update, }; +use tokio::sync::{mpsc::Sender, Mutex}; use zbus::Connection; #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); + i3blocks::run::<Previous>(Default::default()) + .await + .map_err(Into::into) +} - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_status, mut rx_status) = tokio::sync::mpsc::channel(128); - let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128); +pub struct Previous; - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; +impl Component for Previous { + const NAME: &'static str = "previous"; + type Updater = Self; + type Colorer = PlaybackStatus; + type Handler = Self; +} - tokio::spawn(player_listener(tx_player, proxy)); +impl Update for Previous { + type Value = bool; - let status: Arc<Mutex<Block>> = Default::default(); + async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { + use futures_util::StreamExt; - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(color_listener(tx_status.clone(), proxy.clone())); - join_set.spawn(value_listener(tx_value.clone(), proxy)); - } - } - Some((color, background)) = rx_status.recv() => { - let mut status = status.lock().await; - status.color = color; - status.background = background; - } - Some(value) = rx_value.recv() => { - let mut status = status.lock().await; - status.full_text = value.then_some( " ".into()).unwrap_or_default() + tx.send(proxy.can_go_previous().await?).await?; + let mut stream = proxy.receive_can_go_previous_changed().await; + while let Some(signal) = stream.next().await { + if let Ok(value) = signal.get().await { + tx.send(value).await?; } } + Ok(()) + } - let s = status.lock().await; - s.write_stdout()?; + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + let mut block = block.lock().await; + block.full_text = value.then_some(" ".into()).unwrap_or_default(); + Ok(true) } } -pub async fn value_listener(tx: Sender<bool>, proxy: PlayerProxy<'_>) -> Result<(), Error> { - tx.send(proxy.can_go_previous().await?).await?; - let mut stream = proxy.receive_can_go_previous_changed().await; - while let Some(signal) = stream.next().await { - if let Ok(value) = signal.get().await { - tx.send(value).await?; +impl Button for Previous { + async fn handle(conn: Connection, click: Click) -> Result<(), Error> { + let Some(name) = click.instance else { + return Ok(()); + }; + + let proxy = PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await?; + + match click.button { + 1 if proxy.can_go_previous().await? => proxy.previous().await?, + 3 if proxy.can_seek().await? => proxy.seek(-5000).await?, + _ => {} } + + Ok(()) } - Ok(()) } diff --git a/src/bin/title.rs b/src/bin/title.rs index 165e715..3daee26 100644 --- a/src/bin/title.rs +++ b/src/bin/title.rs @@ -1,113 +1,120 @@ -use std::{sync::Arc, time::Duration}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use futures_util::StreamExt; use i3blocks::{ - color_listener, - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, + dbus::player::{PlaybackStatus, PlayerProxy}, i3bar::Block, - player_listener, Error, + Component, Error, Update, }; use tokio::{ sync::{mpsc::Sender, Mutex}, task::{AbortHandle, JoinSet}, }; -use zbus::Connection; +use zbus::zvariant::OwnedValue; const TICK_RATE: Duration = Duration::from_millis(500); #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); - - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_status, mut rx_status) = tokio::sync::mpsc::channel(128); - let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128); - - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; - - tokio::spawn(player_listener(tx_player, proxy)); - - let status: Arc<Mutex<Block>> = Default::default(); - let mut rotator: Option<AbortHandle> = None; - - let mut interval = tokio::time::interval(TICK_RATE); - let mut chars = Vec::new(); - - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(color_listener(tx_status.clone(), proxy.clone())); - join_set.spawn(value_listener(tx_value.clone(), proxy)); - } - } - Some((color, background)) = rx_status.recv() => { - let mut status = status.lock().await; - status.color = color; - status.background = background; - } - Some(mut value) = rx_value.recv() => { - if let Some(h) = rotator.take() { - h.abort(); - } + i3blocks::run::<Title>(Default::default()) + .await + .map_err(Into::into) +} - value.push(' '); - if value.len() >= 10 { - chars = value.chars().collect::<Vec<char>>(); - let mut status = status.lock().await; - status.full_text = format!("{} ", String::from_iter(chars[0..10].iter())); - } else { - chars = Default::default(); - let mut status = status.lock().await; - status.full_text = value; - } - } - _ = interval.tick(), if !chars.is_empty() => { - let mut status = status.lock().await; - status.full_text = format!("{} ", String::from_iter(chars[0..10].iter())); - chars.rotate_left(1); +pub struct Title; + +impl Component for Title { + const NAME: &'static str = "title"; + type Updater = Self; + type Colorer = PlaybackStatus; + type Handler = (); +} + +impl Update for Title { + type Value = String; + + async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { + use futures_util::StreamExt; + + let mut join_set = JoinSet::new(); + let mut rotator = None; + let mut old_title = None; + + Self::handle_metadata( + tx.clone(), + proxy.metadata().await?, + &mut old_title, + &mut rotator, + &mut join_set, + ) + .await?; + + let mut stream = proxy.receive_metadata_changed().await; + while let Some(signal) = stream.next().await { + if let Ok(metadata) = signal.get().await { + Self::handle_metadata( + tx.clone(), + metadata, + &mut old_title, + &mut rotator, + &mut join_set, + ) + .await?; } } + Ok(()) + } + + async fn update(mut value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + value.push(' '); + let mut block = block.lock().await; + block.full_text = value; - let s = status.lock().await; - s.write_stdout()?; + Ok(true) } } -pub async fn value_listener(tx: Sender<String>, proxy: PlayerProxy<'_>) -> Result<(), Error> { - let mut old_title = if let Some(value) = proxy.metadata().await?.get("xesam:title") { - let title: String = value.try_to_owned()?.try_into()?; - tx.send(title.clone()).await?; - Some(title) - } else { - None - }; - - let mut stream = proxy.receive_metadata_changed().await; - while let Some(signal) = stream.next().await { - if let Ok(value) = signal.get().await { - let Some(owned_value) = value.get("xesam:title") else { - continue; - }; - - let title: String = owned_value.try_to_owned()?.try_into()?; - - if old_title.as_ref().is_some_and(|s| *s == title) { - continue; - } +impl Title { + async fn handle_metadata( + tx: Sender<<<Self as Component>::Updater as Update>::Value>, + metadata: HashMap<String, OwnedValue>, + old_title: &mut Option<String>, + rotator: &mut Option<AbortHandle>, + join_set: &mut JoinSet<Result<(), Error>>, + ) -> Result<(), Error> { + let Some(owned_value) = metadata.get("xesam:title") else { + return Ok(()); + }; + + let title: String = owned_value.try_to_owned()?.try_into()?; + + if old_title.as_ref().is_some_and(|s| *s == title) { + return Ok(()); + } + + if let Some(h) = rotator.take() { + h.abort(); + }; - old_title = Some(title.clone()); + *old_title = Some(title.clone()); + + if title.len() >= 10 { + let mut chars = title.clone().chars().collect::<Vec<char>>(); + let tx = tx.clone(); + + *rotator = Some(join_set.spawn(async move { + let mut interval = tokio::time::interval(TICK_RATE); + loop { + interval.tick().await; + tx.send(format!("{} ", String::from_iter(chars[0..10].iter()))) + .await + .unwrap(); + chars.rotate_left(1); + } + })); + } else { tx.send(title).await?; } + + Ok(()) } - Ok(()) } diff --git a/src/bin/volume.rs b/src/bin/volume.rs index 5e9d1d1..b5182b3 100644 --- a/src/bin/volume.rs +++ b/src/bin/volume.rs @@ -1,76 +1,77 @@ use std::sync::Arc; -use futures_util::StreamExt; use i3blocks::{ - color_listener, - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, - i3bar::Block, - player_listener, Error, -}; -use tokio::{ - sync::{mpsc::Sender, Mutex}, - task::JoinSet, + dbus::player::{PlaybackStatus, PlayerProxy}, + i3bar::{Block, Click}, + Button, Component, Error, Update, }; +use tokio::sync::{mpsc::Sender, Mutex}; use zbus::Connection; #[tokio::main] async fn main() -> Result<(), main_error::MainError> { - let mut join_set = JoinSet::new(); + i3blocks::run::<Volume>(Default::default()) + .await + .map_err(Into::into) +} - let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128); - let (tx_status, mut rx_status) = tokio::sync::mpsc::channel(128); - let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128); +pub struct Volume; - let conn = Connection::session().await?; - let proxy = PlayerctldProxy::builder(&conn).build().await?; +impl Component for Volume { + const NAME: &'static str = "volume"; + type Updater = Self; + type Colorer = PlaybackStatus; + type Handler = Self; +} - tokio::spawn(player_listener(tx_player, proxy)); +impl Update for Volume { + type Value = f64; - let status: Arc<Mutex<Block>> = Default::default(); + async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { + use futures_util::StreamExt; - loop { - tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - if name.is_empty() { - let mut status = status.lock().await; - status.full_text = Default::default(); - } else { - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(color_listener(tx_status.clone(), proxy.clone())); - join_set.spawn(value_listener(tx_value.clone(), proxy)); - } - } - Some((color, background)) = rx_status.recv() => { - let mut status = status.lock().await; - status.color = color; - status.background = background; - } - Some(value) = rx_value.recv() => { - let mut status = status.lock().await; - status.full_text = match (value * 100_f64) as u32 { - v @ 66.. => format!(" {v}% "), - v @ 33.. => format!(" {v}% "), - v @ 0.. => format!(" {v}% "), - }; + tx.send(proxy.volume().await?).await?; + let mut stream = proxy.receive_volume_changed().await; + while let Some(signal) = stream.next().await { + if let Ok(value) = signal.get().await { + tx.send(value).await?; } } + Ok(()) + } - let s = status.lock().await; - s.write_stdout()?; + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + let mut block = block.lock().await; + block.full_text = match (value * 100_f64) as u32 { + v @ 66.. => format!(" {v}% "), + v @ 33.. => format!(" {v}% "), + v @ 0.. => format!(" {v}% "), + }; + Ok(true) } } -pub async fn value_listener(tx: Sender<f64>, proxy: PlayerProxy<'_>) -> Result<(), Error> { - tx.send(proxy.volume().await?).await?; - let mut stream = proxy.receive_volume_changed().await; - while let Some(signal) = stream.next().await { - if let Ok(value) = signal.get().await { - tx.send(value).await?; +impl Button for Volume { + async fn handle(conn: Connection, click: Click) -> Result<(), Error> { + let Some(name) = click.instance else { + return Ok(()); + }; + + let proxy = PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await?; + + match click.button { + 4 if proxy.can_control().await? => { + proxy.set_volume(proxy.volume().await? - 0.05).await? + } + 5 if proxy.can_control().await? => { + proxy.set_volume(proxy.volume().await? + 0.05).await? + } + _ => {} } + + Ok(()) } - Ok(()) } |