summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-07-18 18:39:12 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-07-18 18:39:12 -0500
commit28bcf144224838e9e93d5926dcdbb20a4d45a8bf (patch)
tree8fe29412863efb925473519c0ca899c2a6344cfe
parent6e3b66e46c959f4b799e9422bd4d8b3279a06c3b (diff)
rewrite into individual execs
-rw-r--r--src/bin/icon.rs54
-rw-r--r--src/bin/next.rs72
-rw-r--r--src/bin/play.rs96
-rw-r--r--src/bin/prev.rs72
-rw-r--r--src/bin/title.rs113
-rw-r--r--src/bin/volume.rs76
-rw-r--r--src/dbus/playerctld.rs2
-rw-r--r--src/error.rs3
-rw-r--r--src/i3bar.rs15
-rw-r--r--src/lib.rs69
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 {
diff --git a/src/lib.rs b/src/lib.rs
index 3adc3b9..0bdb7de 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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(())
+}