summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-07-20 15:19:09 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-07-20 15:19:09 -0500
commit5b46ff1843bbed0ff1e167928d7f2b3b9968a918 (patch)
tree85e785067a162e67135c53e024f078b08f810ed5
parentbab037ad907a4948799ecd545986fa2d4c709c7a (diff)
feat!: move to single executable with arguments
-rw-r--r--src/component.rs178
-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.rs111
-rw-r--r--src/main.rs19
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(())
diff --git a/src/lib.rs b/src/lib.rs
index aeefa01..01c25aa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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)
+}