diff options
-rw-r--r-- | src/component.rs | 178 | ||||
-rw-r--r-- | src/component/icon.rs (renamed from src/bin/mpris-icon.rs) | 31 | ||||
-rw-r--r-- | src/component/next.rs (renamed from src/bin/mpris-next.rs) | 14 | ||||
-rw-r--r-- | src/component/play.rs (renamed from src/bin/mpris-play.rs) | 44 | ||||
-rw-r--r-- | src/component/prev.rs (renamed from src/bin/mpris-prev.rs) | 22 | ||||
-rw-r--r-- | src/component/title.rs (renamed from src/bin/mpris-title.rs) | 18 | ||||
-rw-r--r-- | src/component/volume.rs (renamed from src/bin/mpris-volume.rs) | 26 | ||||
-rw-r--r-- | src/lib.rs | 111 | ||||
-rw-r--r-- | src/main.rs | 19 |
9 files changed, 250 insertions, 213 deletions
diff --git a/src/component.rs b/src/component.rs index b1d6b0a..32dab6d 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1,39 +1,156 @@ -use std::sync::Arc; +use std::{ + future::Future, + io::{BufReader, Read}, + marker::Send, + sync::Arc, +}; -use tokio::sync::{mpsc::Sender, Mutex}; +use tokio::{ + sync::{mpsc::Sender, Mutex}, + task::JoinSet, +}; +use zbus::Connection; use crate::{ - dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::Block, - Error, + dbus::{ + player::{PlaybackStatus, PlayerProxy}, + playerctld::PlayerctldProxy, + }, + i3bar::{Block, Click}, + Error, IGNORED, }; +pub use icon::Icon; +pub use next::Next; +pub use play::Play; +pub use prev::Prev; +pub use title::Title; +pub use volume::Volume; + +mod icon; +mod next; +mod play; +mod prev; +mod title; +mod volume; + +pub trait Component: Send + 'static { + const NAME: &'static str; + type Updater: Update; + type Colorer: Update; + type Handler: Button; + + fn run<R: Read + Send>(reader: R) -> impl Future<Output = Result<(), Error>> + Send { + async move { + use std::io::BufRead; + + let conn = Connection::session().await?; + + let listeners = tokio::spawn(Self::listeners(conn.clone())); + let buf_reader = BufReader::new(reader); + + for click in buf_reader + .lines() + .map_while(Result::ok) + .flat_map(|s| serde_json::from_str::<Click>(&s)) + { + if let Err(err) = <Self::Handler as Button>::handle(conn.clone(), click).await { + #[cfg(debug_assertions)] + eprintln!("Error running button handler: {err}"); + } + } + + listeners.await? + } + } + + fn listeners(conn: Connection) -> impl Future<Output = Result<(), Error>> + Send { + async move { + use futures_util::StreamExt; + + 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 proxy = PlayerctldProxy::builder(&conn).build().await?; + + tokio::spawn(async move { + let mut last = proxy + .player_names() + .await? + .into_iter() + .find(|s| s.split('.').nth(3).is_some_and(|s| !IGNORED.contains(&s))) + .unwrap_or_default(); + tx_player.send(last.clone()).await?; + let mut stream = proxy.receive_active_player_change_end().await?; + while let Some(signal) = stream.next().await { + let name = signal.args()?.name.to_owned(); + if name != last + && name + .split('.') + .nth(3) + .is_some_and(|s| !IGNORED.contains(&s)) + { + last.clone_from(&name); + tx_player.send(name).await?; + } + } + Result::<_, Error>::Ok(()) + }); + + let block = Arc::new(Mutex::new(Block { + name: Some(format!("mpris-{}", Self::NAME)), + ..Default::default() + })); + + loop { + let updated = tokio::select! { + Some(name) = rx_player.recv() => { + join_set.shutdown().await; + + let mut block = block.lock().await; + block.full_text = String::new(); + block.instance = None; + if !name.is_empty() { + block.instance.clone_from(&Some(name.clone())); + let proxy = PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await?; + join_set.spawn(<Self::Colorer as Update>::listen(tx_status.clone(), proxy.clone())); + join_set.spawn(<Self::Updater as Update>::listen(tx_value.clone(), proxy)); + false + } else { + true + } + } + Some(color) = rx_status.recv() => <Self::Colorer as Update>::update(color, block.clone()).await?, + Some(value) = rx_value.recv() => <Self::Updater as Update>::update(value, block.clone()).await? + }; + + if updated { + let s = block.lock().await; + s.write_stdout()?; + } + } + } + } +} + pub trait Update: Send + 'static { type Value: Send; fn listen( tx: Sender<Self::Value>, proxy: PlayerProxy<'_>, - ) -> impl std::future::Future<Output = Result<(), Error>> + Send; + ) -> impl Future<Output = Result<(), Error>> + Send; fn update( value: Self::Value, block: Arc<Mutex<Block>>, - ) -> impl std::future::Future<Output = Result<bool, Error>> + Send; -} - -pub trait Button { - fn handle( - conn: zbus::Connection, - click: crate::i3bar::Click, - ) -> impl std::future::Future<Output = Result<(), Error>> + Send; -} - -pub trait Component: Send + 'static { - const NAME: &'static str; - type Updater: Update; - type Colorer: Update; - type Handler: Button; + ) -> impl Future<Output = Result<bool, Error>> + Send; } impl Update for () { @@ -48,12 +165,6 @@ impl Update for () { } } -impl Button for () { - async fn handle(_: zbus::Connection, _: crate::i3bar::Click) -> Result<(), Error> { - Ok(()) - } -} - impl Update for PlaybackStatus { type Value = (Option<String>, Option<String>); @@ -88,3 +199,16 @@ impl Update for PlaybackStatus { Ok(true) } } + +pub trait Button { + fn handle( + conn: zbus::Connection, + click: crate::i3bar::Click, + ) -> impl Future<Output = Result<(), Error>> + Send; +} + +impl Button for () { + async fn handle(_: zbus::Connection, _: crate::i3bar::Click) -> Result<(), Error> { + Ok(()) + } +} diff --git a/src/bin/mpris-icon.rs b/src/component/icon.rs index 39677f5..102c787 100644 --- a/src/bin/mpris-icon.rs +++ b/src/component/icon.rs @@ -1,14 +1,19 @@ -use i3blocks::{ - dbus::{media_player2::MediaPlayer2Proxy, player::PlaybackStatus, playerctld::PlayerctldProxy}, +use std::sync::Arc; + +use tokio::sync::{mpsc::Sender, Mutex}; +use zbus::Connection; + +use crate::{ + dbus::{ + media_player2::MediaPlayer2Proxy, + player::{PlaybackStatus, PlayerProxy}, + playerctld::PlayerctldProxy, + }, i3bar::{Block, Click}, - Button, Component, Error, Update, + Error, }; -use zbus::Connection; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Icon>().await.map_err(Into::into) -} +use super::{Button, Component, Update}; pub struct Icon; @@ -22,17 +27,11 @@ impl Component for Icon { impl Update for Icon { type Value = String; - async fn listen( - tx: tokio::sync::mpsc::Sender<Self::Value>, - _: i3blocks::dbus::player::PlayerProxy<'_>, - ) -> Result<(), Error> { + async fn listen(tx: Sender<Self::Value>, _: PlayerProxy<'_>) -> Result<(), Error> { tx.send(" ".into()).await.map_err(Into::into) } - async fn update( - value: Self::Value, - block: std::sync::Arc<tokio::sync::Mutex<Block>>, - ) -> Result<bool, Error> { + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { let mut block = block.lock().await; block.full_text = value; Ok(true) diff --git a/src/bin/mpris-next.rs b/src/component/next.rs index bdd8538..567f5f2 100644 --- a/src/bin/mpris-next.rs +++ b/src/component/next.rs @@ -1,17 +1,15 @@ use std::sync::Arc; -use i3blocks::{ +use tokio::sync::{mpsc::Sender, Mutex}; +use zbus::Connection; + +use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, i3bar::{Block, Click}, - Button, Component, Error, Update, + Error, }; -use tokio::sync::{mpsc::Sender, Mutex}; -use zbus::Connection; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Next>().await.map_err(Into::into) -} +use super::{Button, Component, Update}; pub struct Next; diff --git a/src/bin/mpris-play.rs b/src/component/play.rs index 0656bb9..178be3c 100644 --- a/src/bin/mpris-play.rs +++ b/src/component/play.rs @@ -1,17 +1,15 @@ use std::sync::Arc; -use i3blocks::{ +use tokio::sync::{mpsc::Sender, Mutex}; +use zbus::Connection; + +use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, i3bar::{Block, Click}, - Button, Component, Error, Update, + Error, }; -use tokio::sync::{mpsc::Sender, Mutex}; -use zbus::Connection; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Play>().await.map_err(Into::into) -} +use super::{Button, Component, Update}; pub struct Play; @@ -63,13 +61,29 @@ impl Button for Play { return Ok(()); }; - if click.button == 1 { - PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await? - .play_pause() - .await? + let proxy = PlayerProxy::builder(&conn) + .destination(name)? + .build() + .await?; + + let valid = match proxy.playback_status().await { + Ok(PlaybackStatus::Playing) => proxy.can_pause().await.unwrap_or_default(), + Ok(_) => proxy.can_play().await.unwrap_or_default(), + _ => false, + }; + + if !valid { + return Ok(()); + } + + match (click.button, proxy.playback_status().await) { + (1, Ok(PlaybackStatus::Playing)) if proxy.can_pause().await.unwrap_or_default() => { + proxy.play_pause().await? + } + (1, Ok(PlaybackStatus::Paused)) if proxy.can_play().await.unwrap_or_default() => { + proxy.play_pause().await? + } + _ => (), } Ok(()) diff --git a/src/bin/mpris-prev.rs b/src/component/prev.rs index 7c6ecee..f67be3d 100644 --- a/src/bin/mpris-prev.rs +++ b/src/component/prev.rs @@ -1,28 +1,26 @@ use std::sync::Arc; -use i3blocks::{ +use tokio::sync::{mpsc::Sender, Mutex}; +use zbus::Connection; + +use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, i3bar::{Block, Click}, - Button, Component, Error, Update, + Error, }; -use tokio::sync::{mpsc::Sender, Mutex}; -use zbus::Connection; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Previous>().await.map_err(Into::into) -} +use super::{Button, Component, Update}; -pub struct Previous; +pub struct Prev; -impl Component for Previous { +impl Component for Prev { const NAME: &'static str = "previous"; type Updater = Self; type Colorer = PlaybackStatus; type Handler = Self; } -impl Update for Previous { +impl Update for Prev { type Value = bool; async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { @@ -45,7 +43,7 @@ impl Update for Previous { } } -impl Button for Previous { +impl Button for Prev { async fn handle(conn: Connection, click: Click) -> Result<(), Error> { let Some(name) = click.instance else { return Ok(()); diff --git a/src/bin/mpris-title.rs b/src/component/title.rs index 1976f82..092550e 100644 --- a/src/bin/mpris-title.rs +++ b/src/component/title.rs @@ -1,22 +1,20 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; -use i3blocks::{ - dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::Block, - Component, Error, Update, -}; use tokio::{ sync::{mpsc::Sender, Mutex}, task::{AbortHandle, JoinSet}, }; use zbus::zvariant::OwnedValue; -const TICK_RATE: Duration = Duration::from_millis(500); +use crate::{ + dbus::player::{PlaybackStatus, PlayerProxy}, + i3bar::Block, + Error, +}; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Title>().await.map_err(Into::into) -} +use super::{Component, Update}; + +const TICK_RATE: Duration = Duration::from_millis(500); pub struct Title; diff --git a/src/bin/mpris-volume.rs b/src/component/volume.rs index cf123d9..2cc7600 100644 --- a/src/bin/mpris-volume.rs +++ b/src/component/volume.rs @@ -1,17 +1,15 @@ use std::sync::Arc; -use i3blocks::{ +use tokio::sync::{mpsc::Sender, Mutex}; +use zbus::Connection; + +use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, i3bar::{Block, Click}, - Button, Component, Error, Update, + Error, }; -use tokio::sync::{mpsc::Sender, Mutex}; -use zbus::Connection; -#[tokio::main] -async fn main() -> Result<(), main_error::MainError> { - i3blocks::run::<Volume>().await.map_err(Into::into) -} +use super::{Button, Component, Update}; pub struct Volume; @@ -62,14 +60,10 @@ impl Button for Volume { .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? - } - _ => {} + match (click.button, proxy.volume().await) { + (4, Ok(v)) if proxy.can_control().await? => proxy.set_volume(v + 0.05).await?, + (5, Ok(v)) if proxy.can_control().await? => proxy.set_volume(v - 0.05).await?, + _ => (), } Ok(()) @@ -1,115 +1,8 @@ -use std::sync::Arc; - -use futures_util::StreamExt; -use tokio::{sync::Mutex, task::JoinSet}; -use zbus::Connection; - -pub use crate::{ - component::{Button, Component, Update}, - error::{Error, Result}, -}; -use crate::{ - dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, - i3bar::{Block, Click}, -}; +pub use crate::error::{Error, Result}; pub mod component; pub mod dbus; pub mod error; pub mod i3bar; -const IGNORED: [&str; 2] = ["playerctld", "kdeconnect"]; - -pub async fn run<C>() -> Result<(), Error> -where - C: Component, -{ - let conn = Connection::session().await?; - - tokio::spawn(listeners::<C>(conn.clone())); - - for click in std::io::stdin() - .lines() - .map_while(Result::ok) - .flat_map(|s| serde_json::from_str::<Click>(&s)) - { - if let Err(err) = <<C as Component>::Handler as Button>::handle(conn.clone(), click).await { - eprintln!("Error running button handler: {err}"); - } - } - - Ok(()) -} - -async fn listeners<C>(conn: Connection) -> Result<(), Error> -where - C: Component, -{ - 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 proxy = PlayerctldProxy::builder(&conn).build().await?; - - tokio::spawn(async move { - let mut last = proxy - .player_names() - .await? - .into_iter() - .find(|s| s.split('.').nth(3).is_some_and(|s| !IGNORED.contains(&s))) - .unwrap_or_default(); - tx_player.send(last.clone()).await?; - let mut stream = proxy.receive_active_player_change_end().await?; - while let Some(signal) = stream.next().await { - let name = signal.args()?.name.to_owned(); - if name != last - && name - .split('.') - .nth(3) - .is_some_and(|s| !IGNORED.contains(&s)) - { - last.clone_from(&name); - tx_player.send(name).await?; - } - } - Result::<_, Error>::Ok(()) - }); - - let block = Arc::new(Mutex::new(Block { - name: Some(format!("mpris-{}", C::NAME)), - ..Default::default() - })); - - loop { - let updated = tokio::select! { - Some(name) = rx_player.recv() => { - join_set.shutdown().await; - - let mut block = block.lock().await; - block.full_text = String::new(); - block.instance = None; - if !name.is_empty() { - block.instance.clone_from(&Some(name.clone())); - let proxy = PlayerProxy::builder(&conn) - .destination(name)? - .build() - .await?; - join_set.spawn(<<C as Component>::Colorer as Update>::listen(tx_status.clone(), proxy.clone())); - join_set.spawn(<<C as Component>::Updater as Update>::listen(tx_value.clone(), proxy)); - false - } else { - true - } - } - Some(color) = rx_status.recv() => <<C as Component>::Colorer as Update>::update(color, block.clone()).await?, - Some(value) = rx_value.recv() => <<C as Component>::Updater as Update>::update(value, block.clone()).await? - }; - - if updated { - let s = block.lock().await; - s.write_stdout()?; - } - } -} +const IGNORED: [&str; 3] = ["playerctld", "kdeconnect", "spotifyd"]; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..3b6ed8b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,19 @@ +use i3blocks::component::{Component, Icon, Next, Play, Prev, Title, Volume}; + +#[tokio::main] +async fn main() -> Result<(), main_error::MainError> { + let stdin = std::io::stdin(); + match std::env::args().nth(1).as_deref().unwrap_or("icon") { + "icon" => Icon::run(stdin).await, + "next" => Next::run(stdin).await, + "play" => Play::run(stdin).await, + "prev" => Prev::run(stdin).await, + "title" => Title::run(stdin).await, + "volume" => Volume::run(stdin).await, + s => { + eprintln!("Invalid component name: {s}"); + std::process::exit(1) + } + } + .map_err(Into::into) +} |