diff options
author | Toby Vincent <tobyv@tobyvin.dev> | 2024-07-18 18:39:12 -0500 |
---|---|---|
committer | Toby Vincent <tobyv@tobyvin.dev> | 2024-07-18 18:39:12 -0500 |
commit | 28bcf144224838e9e93d5926dcdbb20a4d45a8bf (patch) | |
tree | 8fe29412863efb925473519c0ca899c2a6344cfe | |
parent | 6e3b66e46c959f4b799e9422bd4d8b3279a06c3b (diff) |
rewrite into individual execs
-rw-r--r-- | src/bin/icon.rs | 54 | ||||
-rw-r--r-- | src/bin/next.rs | 72 | ||||
-rw-r--r-- | src/bin/play.rs | 96 | ||||
-rw-r--r-- | src/bin/prev.rs | 72 | ||||
-rw-r--r-- | src/bin/title.rs | 113 | ||||
-rw-r--r-- | src/bin/volume.rs | 76 | ||||
-rw-r--r-- | src/dbus/playerctld.rs | 2 | ||||
-rw-r--r-- | src/error.rs | 3 | ||||
-rw-r--r-- | src/i3bar.rs | 15 | ||||
-rw-r--r-- | src/lib.rs | 69 |
10 files changed, 564 insertions, 8 deletions
diff --git a/src/bin/icon.rs b/src/bin/icon.rs new file mode 100644 index 0000000..6f2487f --- /dev/null +++ b/src/bin/icon.rs @@ -0,0 +1,54 @@ +use std::sync::Arc; + +use i3blocks::{ + color_listener, + dbus::{player::PlayerProxy, playerctld::PlayerctldProxy}, + i3bar::Block, + player_listener, +}; +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 { + full_text: " ".into(), + ..Default::default() + })); + + 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; + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} diff --git a/src/bin/next.rs b/src/bin/next.rs new file mode 100644 index 0000000..108432c --- /dev/null +++ b/src/bin/next.rs @@ -0,0 +1,72 @@ +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, +}; +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 (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(); + + 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() + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} + +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?; + } + } + Ok(()) +} diff --git a/src/bin/play.rs b/src/bin/play.rs new file mode 100644 index 0000000..0863b7a --- /dev/null +++ b/src/bin/play.rs @@ -0,0 +1,96 @@ +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, +}; +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_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(); + + 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(); + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} + +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?; + } + } + 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?; + + 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(()) +} diff --git a/src/bin/prev.rs b/src/bin/prev.rs new file mode 100644 index 0000000..f7f8dd9 --- /dev/null +++ b/src/bin/prev.rs @@ -0,0 +1,72 @@ +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, +}; +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 (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(); + + 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() + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} + +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?; + } + } + Ok(()) +} diff --git a/src/bin/title.rs b/src/bin/title.rs new file mode 100644 index 0000000..165e715 --- /dev/null +++ b/src/bin/title.rs @@ -0,0 +1,113 @@ +use std::{sync::Arc, time::Duration}; + +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::{AbortHandle, JoinSet}, +}; +use zbus::Connection; + +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(); + } + + 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); + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} + +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; + } + + old_title = Some(title.clone()); + tx.send(title).await?; + } + } + Ok(()) +} diff --git a/src/bin/volume.rs b/src/bin/volume.rs new file mode 100644 index 0000000..5e9d1d1 --- /dev/null +++ b/src/bin/volume.rs @@ -0,0 +1,76 @@ +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, +}; +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 (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(); + + 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}% "), + }; + } + } + + let s = status.lock().await; + s.write_stdout()?; + } +} + +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?; + } + } + Ok(()) +} diff --git a/src/dbus/playerctld.rs b/src/dbus/playerctld.rs index 91cfade..383faea 100644 --- a/src/dbus/playerctld.rs +++ b/src/dbus/playerctld.rs @@ -25,7 +25,7 @@ use zbus::proxy; default_service = "org.mpris.MediaPlayer2.playerctld", default_path = "/org/mpris/MediaPlayer2" )] -trait playerctld { +trait Playerctld { /// Shift method fn shift(&self) -> zbus::Result<String>; diff --git a/src/error.rs b/src/error.rs index dd4a04a..834b7cf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,9 @@ pub enum Error { #[error("Invalid instance value")] Instance, + + #[error("Failed to get player, channel closed")] + Channel, } impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error { diff --git a/src/i3bar.rs b/src/i3bar.rs index 1d65ada..e9c5959 100644 --- a/src/i3bar.rs +++ b/src/i3bar.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::Error; + /// Represent block as described in <https://i3wm.org/docs/i3bar-protocol.html> #[derive(Debug, Clone, Default, Serialize)] pub struct Block { @@ -38,6 +40,19 @@ pub struct Block { pub markup: Option<String>, } +impl Block { + pub fn write_stdout(&self) -> Result<(), Error> { + use std::io::Write; + + 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) + } +} + #[derive(Serialize, Debug, Clone, Copy)] #[serde(rename_all = "lowercase")] pub enum Align { @@ -1,13 +1,68 @@ +use dbus::{ + player::{PlaybackStatus, PlayerProxy}, + playerctld::PlayerctldProxy, +}; pub use error::{Error, Result}; +use futures_util::StreamExt; +use tokio::sync::mpsc::Sender; + pub mod color; -pub mod components; pub mod dbus; pub mod error; pub mod i3bar; -//pub mod mpris; -pub mod client; -pub mod dbus_server; -pub mod player; -pub mod printer; -pub mod server; + +const BLACK: &str = "#1d2021"; +const YELLOW: &str = "#fabd2f"; +const CYAN: &str = "#8ec07c"; + +const IGNORED: [&str; 2] = ["playerctld", "kdeconnect"]; + +impl From<PlaybackStatus> for (Option<String>, Option<String>) { + fn from(value: PlaybackStatus) -> Self { + match value { + PlaybackStatus::Playing => (Some(BLACK.into()), Some(CYAN.into())), + PlaybackStatus::Paused => (Some(BLACK.into()), Some(YELLOW.into())), + PlaybackStatus::Stopped => (None, None), + } + } +} + +pub async fn color_listener( + tx: Sender<(Option<String>, Option<String>)>, + proxy: PlayerProxy<'_>, +) -> Result<()> { + 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.into()).await?; + } + } + Ok(()) +} + +pub async fn player_listener(tx: Sender<String>, proxy: PlayerctldProxy<'_>) -> Result<(), Error> { + let mut last = proxy + .player_names() + .await? + .into_iter() + .next() + .unwrap_or_default(); + + tx.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.send(name).await?; + } + } + Ok(()) +} |