summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-07-21 17:04:19 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-07-21 17:04:19 -0500
commit642557c36d5f431e09afd2c3989617af05e6f72b (patch)
tree133c9ef1a9e0a3244cf0fd3958e2712d6cc52cef
parent06dadd8cfc92b4276d44a203cb33c7b08daab308 (diff)
perf: add component block init and output actions
-rw-r--r--src/component.rs111
-rw-r--r--src/component/icon.rs34
-rw-r--r--src/component/next.rs30
-rw-r--r--src/component/play.rs43
-rw-r--r--src/component/prev.rs28
-rw-r--r--src/component/title.rs125
-rw-r--r--src/component/volume.rs35
-rw-r--r--src/i3bar.rs23
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 {