diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-07-21 17:04:19 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-07-21 17:04:19 -0500 |
commit | 642557c36d5f431e09afd2c3989617af05e6f72b (patch) | |
tree | 133c9ef1a9e0a3244cf0fd3958e2712d6cc52cef | |
parent | 06dadd8cfc92b4276d44a203cb33c7b08daab308 (diff) |
perf: add component block init and output actions
-rw-r--r-- | src/component.rs | 111 | ||||
-rw-r--r-- | src/component/icon.rs | 34 | ||||
-rw-r--r-- | src/component/next.rs | 30 | ||||
-rw-r--r-- | src/component/play.rs | 43 | ||||
-rw-r--r-- | src/component/prev.rs | 28 | ||||
-rw-r--r-- | src/component/title.rs | 125 | ||||
-rw-r--r-- | src/component/volume.rs | 35 | ||||
-rw-r--r-- | src/i3bar.rs | 23 |
8 files changed, 226 insertions, 203 deletions
diff --git a/src/component.rs b/src/component.rs index 3c294b3..e06f2cb 100644 --- a/src/component.rs +++ b/src/component.rs @@ -1,6 +1,6 @@ use std::{ future::Future, - io::{BufReader, Read}, + io::{BufReader, Read, Write}, marker::Send, sync::Arc, }; @@ -35,10 +35,11 @@ mod title; mod volume; pub trait Component: Send + 'static { - const NAME: &'static str; type Updater: Update; type Colorer: Update; type Handler: Button; + + fn initialize() -> Block; } pub trait Runner: Component + private::Sealed { @@ -48,7 +49,9 @@ pub trait Runner: Component + private::Sealed { let conn = Connection::session().await?; - let listeners = tokio::spawn(Self::listeners(conn.clone())); + let writer = std::io::stdout(); + + let listeners = tokio::spawn(Self::listeners(conn.clone(), writer)); let buf_reader = BufReader::new(reader); for click in buf_reader @@ -63,79 +66,79 @@ pub trait Runner: Component + private::Sealed { } } - fn listeners(conn: Connection) -> impl Future<Output = Result<(), Error>> + Send { + fn listeners<W: Write + Send>( + conn: Connection, + mut writer: W, + ) -> 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(Self::initialize())); - let block = Arc::new(Mutex::new(Block { - name: Some(format!("mpris-{}", Self::NAME)), - ..Default::default() - })); + tokio::spawn(Self::active_listener(conn.clone(), tx_player)); loop { - let updated = tokio::select! { + let output = 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; + *block = Self::initialize(); if !name.is_empty() { block.instance.clone_from(&Some(name.clone())); let proxy = PlayerProxy::builder(&conn) - .destination(name)? + .destination(name.clone())? .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 } + Output::Clear } 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()?; + match output { + Output::Print => (block.lock().await).write_to(&mut writer)?, + Output::Clear => writer.write_all(&[b'\n'])?, + Output::Skip => continue, } + + writer.flush()?; } } } + + fn active_listener( + conn: Connection, + tx: Sender<String>, + ) -> impl std::future::Future<Output = Result<(), Error>> + Send { + use futures_util::StreamExt; + + async move { + let proxy = PlayerctldProxy::builder(&conn).build().await?; + let mut last = String::new(); + let mut stream = proxy.receive_player_names_changed().await; + while let Some(signal) = stream.next().await { + let name = signal + .get() + .await? + .into_iter() + .find(|s| s.split('.').nth(3).is_some_and(|s| !IGNORED.contains(&s))) + .unwrap_or_default(); + if name != last { + last.clone_from(&name); + tx.send(name).await?; + } + } + Ok(()) + } + } } impl<T: Component> Runner for T {} @@ -146,6 +149,14 @@ mod private { impl<T: super::Component> Sealed for T {} } +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum Output { + Print, + Clear, + #[default] + Skip, +} + pub trait Update: Send + 'static { type Value: Send; @@ -157,7 +168,7 @@ pub trait Update: Send + 'static { fn update( value: Self::Value, block: Arc<Mutex<Block>>, - ) -> impl Future<Output = Result<bool, Error>> + Send; + ) -> impl Future<Output = Result<Output, Error>> + Send; } impl Update for () { @@ -167,8 +178,8 @@ impl Update for () { Ok(()) } - async fn update(_: Self::Value, _: Arc<Mutex<Block>>) -> Result<bool, Error> { - Ok(false) + async fn update(_: Self::Value, _: Arc<Mutex<Block>>) -> Result<Output, Error> { + Ok(Output::Skip) } } @@ -199,11 +210,11 @@ impl Update for PlaybackStatus { async fn update( (color, background): Self::Value, block: Arc<Mutex<Block>>, - ) -> Result<bool, Error> { + ) -> Result<Output, Error> { let mut block = block.lock().await; block.color = color; block.background = background; - Ok(true) + Ok(Output::Print) } } diff --git a/src/component/icon.rs b/src/component/icon.rs index 102c787..287c655 100644 --- a/src/component/icon.rs +++ b/src/component/icon.rs @@ -1,40 +1,28 @@ -use std::sync::Arc; - -use tokio::sync::{mpsc::Sender, Mutex}; use zbus::Connection; use crate::{ - dbus::{ - media_player2::MediaPlayer2Proxy, - player::{PlaybackStatus, PlayerProxy}, - playerctld::PlayerctldProxy, - }, + dbus::{media_player2::MediaPlayer2Proxy, player::PlaybackStatus, playerctld::PlayerctldProxy}, i3bar::{Block, Click}, Error, }; -use super::{Button, Component, Update}; +use super::{Button, Component}; pub struct Icon; impl Component for Icon { - const NAME: &'static str = "icon"; - type Updater = Self; + type Updater = (); type Colorer = PlaybackStatus; type Handler = Self; -} - -impl Update for Icon { - type Value = String; - 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: Arc<Mutex<Block>>) -> Result<bool, Error> { - let mut block = block.lock().await; - block.full_text = value; - Ok(true) + fn initialize() -> Block { + Block { + name: Some("mpris-icon".into()), + full_text: " ".into(), + separator: Some(false), + separator_block_width: Some(0), + ..Default::default() + } } } diff --git a/src/component/next.rs b/src/component/next.rs index 567f5f2..52e72ff 100644 --- a/src/component/next.rs +++ b/src/component/next.rs @@ -5,19 +5,30 @@ use zbus::Connection; use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::{Block, Click}, + i3bar::{Align, Block, Click, MinWidth}, Error, }; -use super::{Button, Component, Update}; +use super::{Button, Component, Output, Update}; pub struct Next; impl Component for Next { - const NAME: &'static str = "next"; type Updater = Self; type Colorer = PlaybackStatus; type Handler = Self; + + fn initialize() -> Block { + Block { + name: Some("mpris-next".into()), + full_text: ''.into(), + min_width: Some(MinWidth::Text("xx".to_string())), + align: Align::Center, + separator: Some(false), + separator_block_width: Some(0), + ..Default::default() + } + } } impl Update for Next { @@ -26,8 +37,7 @@ impl Update for Next { async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { use futures_util::StreamExt; - tx.send(proxy.can_go_previous().await?).await?; - let mut stream = proxy.receive_can_go_previous_changed().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?; @@ -36,10 +46,12 @@ impl Update for Next { Ok(()) } - 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) + async fn update(value: Self::Value, _: Arc<Mutex<Block>>) -> Result<Output, Error> { + if value { + Ok(Output::Print) + } else { + Ok(Output::Clear) + } } } diff --git a/src/component/play.rs b/src/component/play.rs index 178be3c..512bc77 100644 --- a/src/component/play.rs +++ b/src/component/play.rs @@ -5,53 +5,62 @@ use zbus::Connection; use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::{Block, Click}, + i3bar::{Align, Block, Click, MinWidth}, Error, }; -use super::{Button, Component, Update}; +use super::{Button, Component, Output, Update}; pub struct Play; impl Component for Play { - const NAME: &'static str = "play"; type Updater = Self; type Colorer = (); type Handler = Self; + + fn initialize() -> Block { + Block { + name: Some("mpris-play".into()), + min_width: Some(MinWidth::Text("xx".to_string())), + align: Align::Center, + separator: Some(false), + separator_block_width: Some(0), + ..Default::default() + } + } } impl Update for Play { - type Value = PlaybackStatus; + type Value = (char, Option<String>, Option<String>); async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { use futures_util::StreamExt; + 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(); - 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?; + tx.send(match value { + PlaybackStatus::Playing => ('', black.clone(), cyan.clone()), + PlaybackStatus::Paused => ('', black.clone(), yellow.clone()), + PlaybackStatus::Stopped => ('', None, None), + }) + .await?; } } Ok(()) } - 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), - }; + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<Output, Error> { + let (full_text, color, background) = value; let mut block = block.lock().await; block.full_text = full_text.into(); block.color = color; block.background = background; - Ok(true) + Ok(Output::Print) } } diff --git a/src/component/prev.rs b/src/component/prev.rs index f67be3d..73372ab 100644 --- a/src/component/prev.rs +++ b/src/component/prev.rs @@ -5,19 +5,30 @@ use zbus::Connection; use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::{Block, Click}, + i3bar::{Align, Block, Click, MinWidth}, Error, }; -use super::{Button, Component, Update}; +use super::{Button, Component, Output, Update}; pub struct Prev; impl Component for Prev { - const NAME: &'static str = "previous"; type Updater = Self; type Colorer = PlaybackStatus; type Handler = Self; + + fn initialize() -> Block { + Block { + name: Some("mpris-prev".into()), + full_text: ''.into(), + min_width: Some(MinWidth::Text("xx".to_string())), + align: Align::Center, + separator: Some(false), + separator_block_width: Some(0), + ..Default::default() + } + } } impl Update for Prev { @@ -26,7 +37,6 @@ impl Update for Prev { async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { use futures_util::StreamExt; - 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 { @@ -36,10 +46,12 @@ impl Update for Prev { Ok(()) } - 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) + async fn update(value: Self::Value, _: Arc<Mutex<Block>>) -> Result<Output, Error> { + if value { + Ok(Output::Print) + } else { + Ok(Output::Clear) + } } } diff --git a/src/component/title.rs b/src/component/title.rs index 092550e..870fe1e 100644 --- a/src/component/title.rs +++ b/src/component/title.rs @@ -1,28 +1,35 @@ -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use tokio::{ sync::{mpsc::Sender, Mutex}, task::{AbortHandle, JoinSet}, }; -use zbus::zvariant::OwnedValue; use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::Block, + i3bar::{Align, Block, MinWidth}, Error, }; -use super::{Component, Update}; +use super::{Component, Output, Update}; const TICK_RATE: Duration = Duration::from_millis(500); pub struct Title; impl Component for Title { - const NAME: &'static str = "title"; type Updater = Self; type Colorer = PlaybackStatus; type Handler = (); + + fn initialize() -> Block { + Block { + name: Some("mpris-title".into()), + separator: Some(false), + separator_block_width: Some(0), + ..Default::default() + } + } } impl Update for Title { @@ -32,85 +39,55 @@ impl Update for Title { 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 rotator: Option<AbortHandle> = None; + let mut old_title = String::new(); 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?; + let Some(owned_value) = metadata.get("xesam:title") else { + continue; + }; + + let title: String = owned_value.try_to_owned()?.try_into()?; + + if old_title == title { + continue; + } + + if let Some(h) = rotator.take() { + h.abort(); + }; + + old_title.clone_from(&title); + + if title.len() >= 10 { + let tx = tx.clone(); + let mut chars = title.chars().collect::<Vec<char>>(); + chars.push(' '); + rotator = Some(join_set.spawn(async move { + let mut interval = tokio::time::interval(TICK_RATE); + loop { + interval.tick().await; + tx.send(String::from_iter(chars[0..10].iter())) + .await + .unwrap(); + chars.rotate_left(1); + } + })); + } else { + tx.send(title).await?; + } } } Ok(()) } - async fn update(mut value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { - value.push(' '); + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<Output, Error> { let mut block = block.lock().await; block.full_text = value; + block.min_width = Some(MinWidth::Text(format!("x{}", block.full_text))); + block.align = Align::Center; - Ok(true) - } -} - -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()); - - if title.len() >= 10 { - let tx = tx.clone(); - let mut chars = title.clone().chars().collect::<Vec<char>>(); - chars.push(' '); - *rotator = Some(join_set.spawn(async move { - let mut interval = tokio::time::interval(TICK_RATE); - loop { - interval.tick().await; - tx.send(String::from_iter(chars[0..10].iter())) - .await - .unwrap(); - chars.rotate_left(1); - } - })); - } else { - tx.send(title).await?; - } - - Ok(()) + Ok(Output::Print) } } diff --git a/src/component/volume.rs b/src/component/volume.rs index 2cc7600..83341f0 100644 --- a/src/component/volume.rs +++ b/src/component/volume.rs @@ -5,19 +5,27 @@ use zbus::Connection; use crate::{ dbus::player::{PlaybackStatus, PlayerProxy}, - i3bar::{Block, Click}, + i3bar::{Align, Block, Click, MinWidth}, Error, }; -use super::{Button, Component, Update}; +use super::{Button, Component, Output, Update}; pub struct Volume; impl Component for Volume { - const NAME: &'static str = "volume"; type Updater = Self; type Colorer = PlaybackStatus; type Handler = Self; + + fn initialize() -> Block { + Block { + name: Some("mpris-volume".into()), + full_text: " ".to_string(), + align: Align::Center, + ..Default::default() + } + } } impl Update for Volume { @@ -25,8 +33,6 @@ impl Update for Volume { async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> { use futures_util::StreamExt; - - tx.send(proxy.volume().await.ok()).await?; let mut stream = proxy.receive_volume_changed().await; while let Some(signal) = stream.next().await { if let Ok(value) = signal.get().await { @@ -36,16 +42,21 @@ impl Update for Volume { Ok(()) } - async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> { + async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<Output, Error> { + let volume = match value { + Some(v) => (v * 100_f64) as u32, + None => return Ok(Output::Clear), + }; + let mut block = block.lock().await; - block.full_text = match value.map(|v| (v * 100_f64) as u32) { - Some(v @ 66..) => format!(" {v}% "), - Some(v @ 33..) => format!(" {v}% "), - Some(v @ 0..) => format!(" {v}% "), - None => " ".to_string(), + block.full_text = match volume { + v @ 66.. => format!(" {v}%"), + v @ 33.. => format!(" {v}%"), + v @ 0.. => format!(" {v}%"), }; + block.min_width = Some(MinWidth::Text(format!("x{}", block.full_text))); - Ok(true) + Ok(Output::Print) } } diff --git a/src/i3bar.rs b/src/i3bar.rs index 94cd036..bdc92fa 100644 --- a/src/i3bar.rs +++ b/src/i3bar.rs @@ -24,8 +24,8 @@ pub struct Block { pub border_left: Option<usize>, #[serde(skip_serializing_if = "Option::is_none")] pub min_width: Option<MinWidth>, - #[serde(skip_serializing_if = "Option::is_none")] - pub align: Option<Align>, + #[serde(skip_serializing_if = "Align::is_left")] + pub align: Align, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option<String>, #[serde(skip_serializing_if = "Option::is_none")] @@ -41,26 +41,29 @@ pub struct Block { } impl Block { - pub fn write_stdout(&self) -> Result<(), Error> { - use std::io::Write; - + pub fn write_to<W: std::io::Write>(&self, w: &mut W) -> Result<(), Error> { let mut buf = serde_json::to_vec(self)?; buf.push(b'\n'); - - let mut writer = std::io::stdout().lock(); - writer.write_all(&buf)?; - writer.flush().map_err(Into::into) + w.write_all(&buf)?; + w.flush().map_err(Into::into) } } -#[derive(Serialize, Debug, Clone, Copy)] +#[derive(Debug, Copy, Clone, Default, Serialize)] #[serde(rename_all = "lowercase")] pub enum Align { Center, Right, + #[default] Left, } +impl Align { + fn is_left(&self) -> bool { + matches!(self, Self::Left) + } +} + #[derive(Serialize, Debug, Clone)] #[serde(untagged)] pub enum MinWidth { |