summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2024-07-19 17:14:42 -0500
committerToby Vincent <tobyv@tobyvin.dev>2024-07-19 17:14:42 -0500
commite12cd9dfb9f20f432edd69414b4ff83325226995 (patch)
tree986e640f3c6355215d36958476be605819da1c22 /src
parent28bcf144224838e9e93d5926dcdbb20a4d45a8bf (diff)
feat: working
Diffstat (limited to 'src')
-rw-r--r--src/bin/icon.rs78
-rw-r--r--src/bin/next.rs97
-rw-r--r--src/bin/play.rs129
-rw-r--r--src/bin/prev.rs97
-rw-r--r--src/bin/title.rs183
-rw-r--r--src/bin/volume.rs109
-rw-r--r--src/client.rs31
-rw-r--r--src/color.rs116
-rw-r--r--src/component.rs90
-rw-r--r--src/components.rs234
-rw-r--r--src/dbus_server.rs190
-rw-r--r--src/i3bar.rs9
-rw-r--r--src/lib.rs139
-rw-r--r--src/main.rs7
-rw-r--r--src/mpris.rs129
-rw-r--r--src/player.rs97
-rw-r--r--src/printer.rs255
-rw-r--r--src/server.rs173
18 files changed, 523 insertions, 1640 deletions
diff --git a/src/bin/icon.rs b/src/bin/icon.rs
index 6f2487f..4ffaaae 100644
--- a/src/bin/icon.rs
+++ b/src/bin/icon.rs
@@ -1,54 +1,52 @@
-use std::sync::Arc;
-
use i3blocks::{
- color_listener,
- dbus::{player::PlayerProxy, playerctld::PlayerctldProxy},
- i3bar::Block,
- player_listener,
+ dbus::{media_player2::MediaPlayer2Proxy, player::PlaybackStatus, playerctld::PlayerctldProxy},
+ i3bar::{Block, Click},
+ Button, Component, Error,
};
-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 {
+ i3blocks::run::<Icon>(Block {
full_text: " 󰝚 ".into(),
..Default::default()
- }));
+ })
+ .await
+ .map_err(Into::into)
+}
- 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;
+pub struct Icon;
+
+impl Component for Icon {
+ const NAME: &'static str = "icon";
+ type Updater = ();
+ type Colorer = PlaybackStatus;
+ type Handler = Self;
+}
+
+impl Button for Icon {
+ async fn handle(conn: Connection, click: Click) -> Result<(), Error> {
+ let Some(name) = click.instance else {
+ return Ok(());
+ };
+
+ let proxy = MediaPlayer2Proxy::builder(&conn)
+ .destination(name)?
+ .build()
+ .await?;
+
+ match click.button {
+ 3 => {
+ PlayerctldProxy::builder(&conn)
+ .build()
+ .await?
+ .shift()
+ .await?;
}
+ 1 if proxy.can_raise().await? => proxy.raise().await?,
+ _ => {}
}
- let s = status.lock().await;
- s.write_stdout()?;
+ Ok(())
}
}
diff --git a/src/bin/next.rs b/src/bin/next.rs
index 108432c..2e3fd95 100644
--- a/src/bin/next.rs
+++ b/src/bin/next.rs
@@ -1,72 +1,69 @@
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,
+ dbus::player::{PlaybackStatus, PlayerProxy},
+ i3bar::{Block, Click},
+ Button, Component, Error, Update,
};
+use tokio::sync::{mpsc::Sender, Mutex};
use zbus::Connection;
#[tokio::main]
async fn main() -> Result<(), main_error::MainError> {
- let mut join_set = JoinSet::new();
+ i3blocks::run::<Next>(Default::default())
+ .await
+ .map_err(Into::into)
+}
- 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);
+pub struct Next;
- let conn = Connection::session().await?;
- let proxy = PlayerctldProxy::builder(&conn).build().await?;
+impl Component for Next {
+ const NAME: &'static str = "next";
+ type Updater = Self;
+ type Colorer = PlaybackStatus;
+ type Handler = Self;
+}
- tokio::spawn(player_listener(tx_player, proxy));
+impl Update for Next {
+ type Value = bool;
- let status: Arc<Mutex<Block>> = Default::default();
+ async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> {
+ use futures_util::StreamExt;
- 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()
+ 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(())
+ }
- let s = status.lock().await;
- s.write_stdout()?;
+ 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)
}
}
-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?;
+impl Button for Next {
+ async fn handle(conn: Connection, click: Click) -> Result<(), Error> {
+ let Some(name) = click.instance else {
+ return Ok(());
+ };
+
+ let proxy = PlayerProxy::builder(&conn)
+ .destination(name)?
+ .build()
+ .await?;
+
+ match click.button {
+ 1 if proxy.can_go_next().await? => proxy.next().await?,
+ 3 if proxy.can_seek().await? => proxy.seek(5000).await?,
+ _ => {}
}
+
+ Ok(())
}
- Ok(())
}
diff --git a/src/bin/play.rs b/src/bin/play.rs
index 0863b7a..bf4f489 100644
--- a/src/bin/play.rs
+++ b/src/bin/play.rs
@@ -1,96 +1,79 @@
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,
+ dbus::player::{PlaybackStatus, PlayerProxy},
+ i3bar::{Block, Click},
+ Button, Component, Error, Update,
};
+use tokio::sync::{mpsc::Sender, Mutex};
use zbus::Connection;
#[tokio::main]
async fn main() -> Result<(), main_error::MainError> {
- let mut join_set = JoinSet::new();
+ i3blocks::run::<Play>(Default::default())
+ .await
+ .map_err(Into::into)
+}
- let (tx_player, mut rx_player) = tokio::sync::mpsc::channel(128);
- let (tx_value, mut rx_value) = tokio::sync::mpsc::channel(128);
+pub struct Play;
- let conn = Connection::session().await?;
- let proxy = PlayerctldProxy::builder(&conn).build().await?;
+impl Component for Play {
+ const NAME: &'static str = "play";
+ type Updater = Self;
+ type Colorer = ();
+ type Handler = Self;
+}
- tokio::spawn(player_listener(tx_player, proxy));
+impl Update for Play {
+ type Value = PlaybackStatus;
- let status: Arc<Mutex<Block>> = Default::default();
+ async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> {
+ use futures_util::StreamExt;
- 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();
+ 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?;
}
}
-
- let s = status.lock().await;
- s.write_stdout()?;
+ Ok(())
}
-}
-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?;
- }
+ 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),
+ };
+
+ let mut block = block.lock().await;
+ block.full_text = full_text.into();
+ block.color = color;
+ block.background = background;
+ Ok(true)
}
- 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?;
+impl Button for Play {
+ async fn handle(conn: Connection, click: Click) -> Result<(), Error> {
+ let Some(name) = click.instance else {
+ return Ok(());
+ };
+
+ if click.button == 1 {
+ PlayerProxy::builder(&conn)
+ .destination(name)?
+ .build()
+ .await?
+ .play_pause()
+ .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(())
}
- Ok(())
}
diff --git a/src/bin/prev.rs b/src/bin/prev.rs
index f7f8dd9..7eec6c3 100644
--- a/src/bin/prev.rs
+++ b/src/bin/prev.rs
@@ -1,72 +1,69 @@
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,
+ dbus::player::{PlaybackStatus, PlayerProxy},
+ i3bar::{Block, Click},
+ Button, Component, Error, Update,
};
+use tokio::sync::{mpsc::Sender, Mutex};
use zbus::Connection;
#[tokio::main]
async fn main() -> Result<(), main_error::MainError> {
- let mut join_set = JoinSet::new();
+ i3blocks::run::<Previous>(Default::default())
+ .await
+ .map_err(Into::into)
+}
- 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);
+pub struct Previous;
- let conn = Connection::session().await?;
- let proxy = PlayerctldProxy::builder(&conn).build().await?;
+impl Component for Previous {
+ const NAME: &'static str = "previous";
+ type Updater = Self;
+ type Colorer = PlaybackStatus;
+ type Handler = Self;
+}
- tokio::spawn(player_listener(tx_player, proxy));
+impl Update for Previous {
+ type Value = bool;
- let status: Arc<Mutex<Block>> = Default::default();
+ async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> {
+ use futures_util::StreamExt;
- 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()
+ 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(())
+ }
- let s = status.lock().await;
- s.write_stdout()?;
+ 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)
}
}
-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?;
+impl Button for Previous {
+ async fn handle(conn: Connection, click: Click) -> Result<(), Error> {
+ let Some(name) = click.instance else {
+ return Ok(());
+ };
+
+ let proxy = PlayerProxy::builder(&conn)
+ .destination(name)?
+ .build()
+ .await?;
+
+ match click.button {
+ 1 if proxy.can_go_previous().await? => proxy.previous().await?,
+ 3 if proxy.can_seek().await? => proxy.seek(-5000).await?,
+ _ => {}
}
+
+ Ok(())
}
- Ok(())
}
diff --git a/src/bin/title.rs b/src/bin/title.rs
index 165e715..3daee26 100644
--- a/src/bin/title.rs
+++ b/src/bin/title.rs
@@ -1,113 +1,120 @@
-use std::{sync::Arc, time::Duration};
+use std::{collections::HashMap, sync::Arc, time::Duration};
-use futures_util::StreamExt;
use i3blocks::{
- color_listener,
- dbus::{player::PlayerProxy, playerctld::PlayerctldProxy},
+ dbus::player::{PlaybackStatus, PlayerProxy},
i3bar::Block,
- player_listener, Error,
+ Component, Error, Update,
};
use tokio::{
sync::{mpsc::Sender, Mutex},
task::{AbortHandle, JoinSet},
};
-use zbus::Connection;
+use zbus::zvariant::OwnedValue;
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();
- }
+ i3blocks::run::<Title>(Default::default())
+ .await
+ .map_err(Into::into)
+}
- 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);
+pub struct Title;
+
+impl Component for Title {
+ const NAME: &'static str = "title";
+ type Updater = Self;
+ type Colorer = PlaybackStatus;
+ type Handler = ();
+}
+
+impl Update for Title {
+ type Value = String;
+
+ async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> {
+ 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 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?;
}
}
+ Ok(())
+ }
+
+ async fn update(mut value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> {
+ value.push(' ');
+ let mut block = block.lock().await;
+ block.full_text = value;
- let s = status.lock().await;
- s.write_stdout()?;
+ Ok(true)
}
}
-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;
- }
+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());
+ *old_title = Some(title.clone());
+
+ if title.len() >= 10 {
+ let mut chars = title.clone().chars().collect::<Vec<char>>();
+ let tx = tx.clone();
+
+ *rotator = Some(join_set.spawn(async move {
+ let mut interval = tokio::time::interval(TICK_RATE);
+ loop {
+ interval.tick().await;
+ tx.send(format!("{} ", String::from_iter(chars[0..10].iter())))
+ .await
+ .unwrap();
+ chars.rotate_left(1);
+ }
+ }));
+ } else {
tx.send(title).await?;
}
+
+ Ok(())
}
- Ok(())
}
diff --git a/src/bin/volume.rs b/src/bin/volume.rs
index 5e9d1d1..b5182b3 100644
--- a/src/bin/volume.rs
+++ b/src/bin/volume.rs
@@ -1,76 +1,77 @@
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,
+ dbus::player::{PlaybackStatus, PlayerProxy},
+ i3bar::{Block, Click},
+ Button, Component, Error, Update,
};
+use tokio::sync::{mpsc::Sender, Mutex};
use zbus::Connection;
#[tokio::main]
async fn main() -> Result<(), main_error::MainError> {
- let mut join_set = JoinSet::new();
+ i3blocks::run::<Volume>(Default::default())
+ .await
+ .map_err(Into::into)
+}
- 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);
+pub struct Volume;
- let conn = Connection::session().await?;
- let proxy = PlayerctldProxy::builder(&conn).build().await?;
+impl Component for Volume {
+ const NAME: &'static str = "volume";
+ type Updater = Self;
+ type Colorer = PlaybackStatus;
+ type Handler = Self;
+}
- tokio::spawn(player_listener(tx_player, proxy));
+impl Update for Volume {
+ type Value = f64;
- let status: Arc<Mutex<Block>> = Default::default();
+ async fn listen(tx: Sender<Self::Value>, proxy: PlayerProxy<'_>) -> Result<(), Error> {
+ use futures_util::StreamExt;
- 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}% "),
- };
+ 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(())
+ }
- let s = status.lock().await;
- s.write_stdout()?;
+ async fn update(value: Self::Value, block: Arc<Mutex<Block>>) -> Result<bool, Error> {
+ let mut block = block.lock().await;
+ block.full_text = match (value * 100_f64) as u32 {
+ v @ 66.. => format!("󰕾 {v}% "),
+ v @ 33.. => format!("󰖀 {v}% "),
+ v @ 0.. => format!("󰕿 {v}% "),
+ };
+ Ok(true)
}
}
-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?;
+impl Button for Volume {
+ async fn handle(conn: Connection, click: Click) -> Result<(), Error> {
+ let Some(name) = click.instance else {
+ return Ok(());
+ };
+
+ let proxy = PlayerProxy::builder(&conn)
+ .destination(name)?
+ .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?
+ }
+ _ => {}
}
+
+ Ok(())
}
- Ok(())
}
diff --git a/src/client.rs b/src/client.rs
deleted file mode 100644
index 1e38cce..0000000
--- a/src/client.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use serde::{Deserialize, Serialize};
-use tokio::{io::AsyncWriteExt, net::UnixStream};
-
-use crate::Error;
-
-#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
-pub enum ClientKind {
- #[serde(rename = "mpris-icon")]
- Icon,
- #[serde(rename = "mpris-title")]
- Title,
- #[serde(rename = "mpris-prev")]
- Prev,
- #[serde(rename = "mpris-playPause")]
- PlayPause,
- #[serde(rename = "mpris-next")]
- Next,
- #[serde(rename = "mpris-volume")]
- Volume,
-}
-
-async fn client(kind: ClientKind) -> Result<(), Error> {
- let stream = UnixStream::connect("/tmp/i3blocks-mpris.sock").await?;
- let (mut reader, mut writer) = stream.into_split();
-
- let buf = bincode::serialize(&kind)?;
- writer.write_all(&buf).await?;
-
- todo!()
-}
-
diff --git a/src/color.rs b/src/color.rs
deleted file mode 100644
index 076d995..0000000
--- a/src/color.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use std::{fmt::Display, str::FromStr};
-
-use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
-
-use crate::Error;
-
-/// An RGBA color.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
-pub struct Color {
- pub r: u8,
- pub g: u8,
- pub b: u8,
- pub a: u8,
-}
-
-impl Color {
- pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Color {
- Color { r, g, b, a }
- }
-}
-
-impl FromStr for Color {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let s = s.trim_start_matches('#');
-
- let num = u32::from_str_radix(s, 16)
- .map_err(|err| Error::Color(format!("Failed to parse int: {err}")))?;
- let mut bytes = num.to_be_bytes();
-
- if s.len() == 6 {
- bytes[0] = 0xff;
- bytes.rotate_left(1);
- } else if s.len() != 9 {
- return Err(Error::Color(format!("Invalid color format: {s}")));
- }
-
- let [r, g, b, a] = bytes;
-
- Ok(Self { r, g, b, a })
- }
-}
-
-impl Display for Color {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "#{:02X}{:02X}{:02X}{:02X}",
- self.r, self.g, self.b, self.a
- )
- }
-}
-
-impl Serialize for Color {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- serializer.serialize_str(&self.to_string())
- }
-}
-
-impl<'de> Deserialize<'de> for Color {
- fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- struct ColorVisitor;
-
- impl<'de> Visitor<'de> for ColorVisitor {
- type Value = Color;
-
- fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
- formatter.write_str("color")
- }
-
- fn visit_str<E>(self, s: &str) -> Result<Color, E>
- where
- E: serde::de::Error,
- {
- s.parse().map_err(serde::de::Error::custom)
- }
- }
-
- deserializer.deserialize_any(ColorVisitor)
- }
-}
-
-pub struct Scheme {
- pub black: Color,
- pub red: Color,
- pub green: Color,
- pub yellow: Color,
- pub blue: Color,
- pub magenta: Color,
- pub cyan: Color,
- pub white: Color,
- pub gray: Color,
-}
-
-impl Default for Scheme {
- fn default() -> Self {
- Self {
- black: Color::new(0, 0, 0, 255),
- red: Color::new(255, 0, 0, 255),
- green: Color::new(0, 255, 0, 255),
- yellow: Color::new(255, 255, 0, 255),
- blue: Color::new(0, 0, 255, 255),
- magenta: Color::new(255, 0, 255, 255),
- cyan: Color::new(0, 255, 255, 255),
- white: Color::new(255, 255, 255, 255),
- gray: Color::new(128, 128, 128, 255),
- }
- }
-}
diff --git a/src/component.rs b/src/component.rs
new file mode 100644
index 0000000..b1d6b0a
--- /dev/null
+++ b/src/component.rs
@@ -0,0 +1,90 @@
+use std::sync::Arc;
+
+use tokio::sync::{mpsc::Sender, Mutex};
+
+use crate::{
+ dbus::player::{PlaybackStatus, PlayerProxy},
+ i3bar::Block,
+ Error,
+};
+
+pub trait Update: Send + 'static {
+ type Value: Send;
+
+ fn listen(
+ tx: Sender<Self::Value>,
+ proxy: PlayerProxy<'_>,
+ ) -> impl std::future::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 Update for () {
+ type Value = ();
+
+ async fn listen(_: Sender<Self::Value>, _: PlayerProxy<'_>) -> Result<(), Error> {
+ Ok(())
+ }
+
+ async fn update(_: Self::Value, _: Arc<Mutex<Block>>) -> Result<bool, Error> {
+ Ok(false)
+ }
+}
+
+impl Button for () {
+ async fn handle(_: zbus::Connection, _: crate::i3bar::Click) -> Result<(), Error> {
+ Ok(())
+ }
+}
+
+impl Update for PlaybackStatus {
+ type Value = (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();
+
+ let mut stream = proxy.receive_playback_status_changed().await;
+ while let Some(signal) = stream.next().await {
+ if let Ok(value) = signal.get().await {
+ let val = match value {
+ PlaybackStatus::Playing => (black.clone(), cyan.clone()),
+ PlaybackStatus::Paused => (black.clone(), yellow.clone()),
+ PlaybackStatus::Stopped => (None, None),
+ };
+ tx.send(val).await?;
+ }
+ }
+ Ok(())
+ }
+
+ async fn update(
+ (color, background): Self::Value,
+ block: Arc<Mutex<Block>>,
+ ) -> Result<bool, Error> {
+ let mut block = block.lock().await;
+ block.color = color;
+ block.background = background;
+ Ok(true)
+ }
+}
diff --git a/src/components.rs b/src/components.rs
deleted file mode 100644
index 1548365..0000000
--- a/src/components.rs
+++ /dev/null
@@ -1,234 +0,0 @@
-use std::{collections::HashMap, option::Option, time::Duration};
-
-use futures_util::StreamExt;
-use tokio::{sync::mpsc::Sender, task::JoinHandle};
-use zbus::zvariant::OwnedValue;
-
-use crate::{
- dbus::player::{PlaybackStatus, PlayerProxy},
- error::Result,
- i3bar::Block,
-};
-
-const TICK_RATE: Duration = Duration::from_millis(500);
-
-pub trait Component: std::marker::Sized {
- fn listen(
- &self,
- proxy: PlayerProxy<'static>,
- tx: Sender<Block>,
- ) -> impl std::future::Future<Output = Result<()>> + Send;
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Icon;
-
-impl Component for Icon {
- async fn listen(&self, _: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- tx.send(Block {
- full_text: " 󰝚 ".into(),
- instance: "mpris-icon".into(),
- ..Default::default()
- })
- .await
- .map_err(Into::into)
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Title;
-
-impl Title {
- async fn process_metadata(
- tx: Sender<Block>,
- metadata: HashMap<String, OwnedValue>,
- rotator: &mut Option<JoinHandle<Result<()>>>,
- last: &mut Option<String>,
- ) -> Result<()> {
- let Some(val) = metadata.get("xesam:title") else {
- return Ok(());
- };
-
- let title: String = val.try_to_owned()?.try_into()?;
-
- if last.as_ref().is_some_and(|s| *s == title) {
- return Ok(());
- }
-
- let mut block = Block {
- instance: "mpris-title".into(),
- ..Default::default()
- };
-
- if let Some(h) = rotator.take() {
- h.abort()
- };
-
- if title.len() > 10 {
- let mut chars = title.chars().collect::<Vec<char>>();
- *last = Some(title);
- *rotator = Some(tokio::spawn(async move {
- let mut interval = tokio::time::interval(TICK_RATE);
- chars.push(' ');
-
- loop {
- interval.tick().await;
- block.full_text = chars[0..10].iter().collect();
- tx.send(block.clone()).await?;
- chars.rotate_left(1);
- }
- }));
- } else {
- *last = Some(title.clone());
- block.full_text = title;
- tx.send(block).await?;
- }
-
- Ok(())
- }
-}
-
-impl Component for Title {
- async fn listen(&self, proxy: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- let mut stream = proxy.receive_metadata_changed().await;
- let mut rotator = None;
- let mut last = None;
-
- Self::process_metadata(tx.clone(), proxy.metadata().await?, &mut rotator, &mut last)
- .await?;
-
- while let Some(signal) = stream.next().await {
- if let Ok(metadata) = signal.get().await {
- Self::process_metadata(tx.clone(), metadata, &mut rotator, &mut last).await?;
- };
- }
- Ok(())
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Prev;
-
-impl Component for Prev {
- async fn listen(&self, proxy: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- let mut block = Block {
- full_text: proxy
- .can_go_previous()
- .await
- .unwrap_or_default()
- .then_some(" 󰒮 ".into())
- .unwrap_or_default(),
- instance: "mpris-prev".into(),
- ..Default::default()
- };
-
- tx.send(block.clone()).await?;
-
- let mut stream = proxy.receive_can_go_previous_changed().await;
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- block.full_text = val.then_some(" 󰒮 ".into()).unwrap_or_default();
-
- tx.send(block.clone()).await?;
- };
- }
- Ok(())
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct PlayPause;
-
-impl Component for PlayPause {
- async fn listen(&self, proxy: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- let mut block = Block {
- instance: "mpris-playpause".into(),
- ..Default::default()
- };
-
- block.full_text = match proxy.playback_status().await? {
- PlaybackStatus::Playing => " 󰏤 ",
- PlaybackStatus::Paused | PlaybackStatus::Stopped => " 󰐊 ",
- }
- .to_string();
-
- tx.send(block.clone()).await?;
-
- let mut stream = proxy.receive_playback_status_changed().await;
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- block.full_text = match val {
- PlaybackStatus::Playing => " 󰏤 ",
- PlaybackStatus::Paused | PlaybackStatus::Stopped => " 󰐊 ",
- }
- .to_string();
-
- tx.send(block.clone()).await?;
- };
- }
- Ok(())
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Next;
-
-impl Component for Next {
- async fn listen(&self, proxy: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- let mut block = Block {
- full_text: proxy
- .can_go_next()
- .await
- .unwrap_or_default()
- .then_some(" 󰒭 ".into())
- .unwrap_or_default(),
- instance: "mpris-next".into(),
- ..Default::default()
- };
-
- tx.send(block.clone()).await?;
-
- let mut stream = proxy.receive_can_go_next_changed().await;
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- block.full_text = val.then_some(" 󰒭 ".into()).unwrap_or_default();
- tx.send(block.clone()).await?;
- };
- }
- Ok(())
- }
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct Volume;
-
-impl Component for Volume {
- async fn listen(&self, proxy: PlayerProxy<'static>, tx: Sender<Block>) -> Result<()> {
- let mut block = Block {
- instance: "mpris-volume".into(),
- ..Default::default()
- };
-
- block.full_text = match (proxy.volume().await? * 100_f64) as u32 {
- v @ 66.. => format!("󰕾 {v}% "),
- v @ 33.. => format!("󰖀 {v}% "),
- v @ 0.. => format!("󰕿 {v}% "),
- };
-
- tx.send(block.clone()).await?;
-
- let mut stream = proxy.receive_volume_changed().await;
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- block.full_text = match (val * 100_f64) as u32 {
- v @ 66.. => format!("󰕾 {v}% "),
- v @ 33.. => format!("󰖀 {v}% "),
- v @ 0.. => format!("󰕿 {v}% "),
- };
-
- tx.send(block.clone()).await?;
- };
- }
- Ok(())
- }
-}
diff --git a/src/dbus_server.rs b/src/dbus_server.rs
deleted file mode 100644
index 6f7fee4..0000000
--- a/src/dbus_server.rs
+++ /dev/null
@@ -1,190 +0,0 @@
-use futures_util::StreamExt;
-use std::sync::Arc;
-
-use tokio::{
- io::{AsyncReadExt, BufReader},
- net::{UnixListener, UnixStream},
- sync::{mpsc::Sender, Mutex, Notify},
- task::JoinSet,
-};
-use zbus::{
- fdo::{DBusProxy, NameOwnerChangedArgs},
- interface,
- names::OwnedBusName,
- zvariant::Optional,
- Connection,
-};
-
-use crate::{
- client::ClientKind,
- dbus::{
- media_player2::MediaPlayer2Proxy,
- player::{PlaybackStatus, PlayerProxy},
- },
- i3bar::Click,
- player::Event,
- printer::Status,
- Error,
-};
-
-pub struct MprisStatus {
- player: Option<String>,
- title: String,
- can_go_previous: bool,
- playback_status: PlaybackStatus,
- can_go_next: bool,
- volume: Option<String>
- color: Option<String>,
- background: Option<String>,
-}
-
-#[interface(
- name = "org.tobyvin.i3blocks.Mpris",
- proxy(
- default_path = "/org/i3blocks/Mpris",
- default_service = "org.i3blocks.Mpris",
- )
-)]
-impl MprisStatus {
- #[zbus(property)]
- async fn title(&self) -> Optional<String> {
- if self.player.is_none() {
- return None.into();
- }
-
- Some(self.title.clone()).into()
- }
-
- #[zbus(property)]
- async fn can_go_previous(&self) -> Optional<String> {
- if self.player.is_none() {
- return None.into();
- }
- self.can_go_previous.then_some(" 󰒮 ".into()).into()
- }
-
- #[zbus(property)]
- async fn playback_status(&self) -> Optional<String> {
- if self.player.is_none() {
- return None.into();
- }
-
- match self.playback_status {
- PlaybackStatus::Playing => Some(" 󰐊 ".into()),
- _ => Some(" 󰏤 ".into()),
- }
- .into()
- }
-
- #[zbus(property)]
- async fn can_go_next(&self) -> Optional<String> {
- if self.player.is_none() {
- return None.into();
- }
-
- self.can_go_next.then_some(" 󰒭 ".into()).into()
- }
-}
-
-impl MprisStatus {
- async fn set_title(conn: Connection, value: String) -> Result<(), Error> {
- let iface_ref = conn
- .object_server()
- .interface::<_, Self>("/org/i3blocks/Mpris")
- .await?;
- let mut iface = iface_ref.get_mut().await;
- iface.title = value;
- iface
- .title_changed(iface_ref.signal_context())
- .await
- .map_err(Into::into)
- }
-
- async fn set_can_go_previous(conn: Connection, value: bool) -> Result<(), Error> {
- let iface_ref = conn
- .object_server()
- .interface::<_, Self>("/org/i3blocks/Mpris")
- .await?;
- let mut iface = iface_ref.get_mut().await;
- iface.can_go_previous = value;
- iface
- .can_go_previous_changed(iface_ref.signal_context())
- .await
- .map_err(Into::into)
- }
-
- async fn set_playback_status(conn: Connection, value: PlaybackStatus) -> Result<(), Error> {
- let iface_ref = conn
- .object_server()
- .interface::<_, Self>("/org/i3blocks/Mpris")
- .await?;
- let mut iface = iface_ref.get_mut().await;
- iface.playback_status = value;
- iface
- .playback_status_changed(iface_ref.signal_context())
- .await
- .map_err(Into::into)
- }
-
- async fn set_can_go_next(conn: Connection, value: bool) -> Result<(), Error> {
- let iface_ref = conn
- .object_server()
- .interface::<_, Self>("/org/i3blocks/Mpris")
- .await?;
- let mut iface = iface_ref.get_mut().await;
- iface.can_go_next = value;
- iface
- .can_go_next_changed(iface_ref.signal_context())
- .await
- .map_err(Into::into)
- }
-
- async fn set_volume(conn: Connection, value: f64) -> Result<(), Error> {
- let iface_ref = conn
- .object_server()
- .interface::<_, Self>("/org/i3blocks/Mpris")
- .await?;
- let mut iface = iface_ref.get_mut().await;
-
- iface.volume = match (value * 100_f64) as u32 {
- v @ 66.. => format!("󰕾 {v}% "),
- v @ 33.. => format!("󰖀 {v}% "),
- v @ 0.. => format!("󰕿 {v}% "),
- };
-
- iface
- .volume_changed(iface_ref.signal_context())
- .await
- .map_err(Into::into)
- }
-}
-
-//full_text.push_str(" 󰝚 ");
-//
-//if let Some(title) = self.title.as_ref() {
-// full_text.push_str(title);
-// full_text.push(' ');
-//}
-//
-//if self.can_go_previous {
-// full_text.push_str("󰒮 ");
-//}
-//
-//if self.playback_status == PlaybackStatus::Playing {
-// full_text.push_str("󰏤 ");
-//} else {
-// full_text.push_str("󰐊 ");
-//}
-//
-//if self.can_go_next {
-// full_text.push_str("󰒭 ");
-//}
-//
-//if let Some(volume) = self.volume.map(|v| (v * 100_f64) as u32) {
-// match volume {
-// v @ 66.. => full_text.push_str(&format!("󰕾 {v}% ")),
-// v @ 33.. => full_text.push_str(&format!("󰖀 {v}% ")),
-// v @ 0.. => full_text.push_str(&format!("󰕿 {v}% ")),
-// }
-//}
-
diff --git a/src/i3bar.rs b/src/i3bar.rs
index e9c5959..94cd036 100644
--- a/src/i3bar.rs
+++ b/src/i3bar.rs
@@ -6,8 +6,8 @@ use crate::Error;
#[derive(Debug, Clone, Default, Serialize)]
pub struct Block {
pub full_text: String,
- #[serde(skip_serializing_if = "String::is_empty")]
- pub short_text: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub short_text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -28,8 +28,8 @@ pub struct Block {
pub align: Option<Align>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
- #[serde(skip_serializing_if = "String::is_empty")]
- pub instance: String,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub instance: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub urgent: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
@@ -69,6 +69,7 @@ pub enum MinWidth {
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
+#[serde(default)]
pub struct Click {
pub name: Option<String>,
pub instance: Option<String>,
diff --git a/src/lib.rs b/src/lib.rs
index 0bdb7de..35e7f5b 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,68 +1,109 @@
-use dbus::{
- player::{PlaybackStatus, PlayerProxy},
- playerctld::PlayerctldProxy,
-};
-pub use error::{Error, Result};
+use std::sync::Arc;
use futures_util::StreamExt;
-use tokio::sync::mpsc::Sender;
+use tokio::{sync::Mutex, task::JoinSet};
+use zbus::Connection;
-pub mod color;
+pub use crate::{
+ component::{Button, Component, Update},
+ error::{Error, Result},
+};
+use crate::{
+ dbus::{player::PlayerProxy, playerctld::PlayerctldProxy},
+ i3bar::{Block, Click},
+};
+
+pub mod component;
pub mod dbus;
pub mod error;
pub mod i3bar;
-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 run<C>(mut block: Block) -> Result<(), Error>
+where
+ C: Component,
+{
+ let conn = Connection::session().await?;
-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?;
- }
+ block.name = Some(format!("mpris-{}", C::NAME));
+ tokio::spawn(listeners::<C>(conn.clone(), block));
+
+ for click in std::io::stdin()
+ .lines()
+ .map_while(Result::ok)
+ .flat_map(|s| serde_json::from_str::<Click>(&s))
+ {
+ <<C as Component>::Handler as Button>::handle(conn.clone(), click).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();
+async fn listeners<C>(conn: Connection, block: Block) -> 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?;
- tx.send(last.clone()).await?;
+ tokio::spawn(async move {
+ let mut last = proxy
+ .player_names()
+ .await?
+ .into_iter()
+ .next()
+ .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));
- 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?;
+ loop {
+ let updated = tokio::select! {
+ Some(name) = rx_player.recv() => {
+ join_set.shutdown().await;
+ if name.is_empty() {
+ let mut block = block.lock().await;
+ block.full_text = String::new();
+ block.instance = None;
+ true
+ } else {
+ 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
+ }
+ }
+ 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()?;
}
}
- Ok(())
}
diff --git a/src/main.rs b/src/main.rs
deleted file mode 100644
index 84e5e51..0000000
--- a/src/main.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use main_error::MainError;
-
-#[tokio::main]
-async fn main() -> Result<(), MainError> {
- todo!()
- //i3blocks::mpris::mpris().await.map_err(Into::into)
-}
diff --git a/src/mpris.rs b/src/mpris.rs
deleted file mode 100644
index 09aab74..0000000
--- a/src/mpris.rs
+++ /dev/null
@@ -1,129 +0,0 @@
-use std::marker::Sync;
-
-use futures_util::StreamExt;
-use tokio::{sync::mpsc::Sender, task::JoinSet};
-use zbus::{
- fdo::{DBusProxy, NameOwnerChangedArgs, NameOwnerChangedStream},
- names::OwnedBusName,
- Connection,
-};
-
-use crate::{
- components::Component,
- dbus::{media_player2::MediaPlayer2Proxy, player::PlayerProxy},
- i3bar::Click,
- player::{self, Event},
- Result,
-};
-
-const IGNORED: [&str; 1] = ["playerctld"];
-
-#[derive(Debug, Clone)]
-pub enum Instance {
- Icon,
- Title,
- Prev,
- PlayPause,
- Next,
- Volume,
-}
-
-pub async fn mpris<T: Component + Sync>(component: &'static T) -> Result<()> {
- let mut join_set = JoinSet::new();
- let (tx, rx) = tokio::sync::mpsc::channel(128);
-
- let conn = Connection::session().await?;
-
- let dbus_proxy = DBusProxy::new(&conn).await?;
- let names: Vec<OwnedBusName> = dbus_proxy.list_names().await?;
-
- let players = names.iter().filter(|s| valid_player(s)).collect::<Vec<_>>();
-
- for player in players {
- tx.send(Event::Add(player.clone())).await?;
- }
-
- let name_owner_changed = dbus_proxy.receive_name_owner_changed().await?;
-
- join_set.spawn(listen_name_owner_change(tx.clone(), name_owner_changed));
- join_set.spawn(player::listener(conn.clone(), rx, component));
-
- for line in std::io::stdin().lines() {
- let click: Click = dbg!(serde_json::from_str(&line?)?);
-
- let Some(name) = click.name else {
- continue;
- };
-
- let Some(instance) = click.instance else {
- continue;
- };
-
- let player_proxy = PlayerProxy::builder(&conn)
- .destination(name)?
- .build()
- .await?;
-
- let mpris_proxy = MediaPlayer2Proxy::builder(player_proxy.inner().connection())
- .destination(player_proxy.inner().destination())?
- .build()
- .await?;
-
- match (instance.as_ref(), click.button) {
- ("mpris-icon", 1) if mpris_proxy.can_raise().await? => mpris_proxy.raise().await?,
- ("mpris-title", _) => {}
- ("mpris-prev", 1) => player_proxy.previous().await?,
- ("mpris-playPause", 1) => player_proxy.play_pause().await?,
- ("mpris-next", 1) => player_proxy.next().await?,
- ("mpris-volume", 4) => {
- player_proxy
- .set_volume(player_proxy.volume().await? - 0.05)
- .await?
- }
- ("mpris-volume", 5) => {
- player_proxy
- .set_volume(player_proxy.volume().await? + 0.05)
- .await?
- }
- (_, 3) => tx.send(Event::Shift).await?,
- _ => {}
- }
- }
-
- while let Some(res) = join_set.join_next().await {
- if let Err(err) = res? {
- eprintln!("{err}")
- };
- }
-
- Ok(())
-}
-
-fn valid_player(name: &str) -> bool {
- name.strip_prefix("org.mpris.MediaPlayer2.")
- .and_then(|s| s.split('.').next())
- .is_some_and(|s| !IGNORED.contains(&s))
-}
-
-async fn listen_name_owner_change(
- tx: Sender<Event>,
- mut stream: NameOwnerChangedStream<'static>,
-) -> Result<()> {
- while let Some(signal) = stream.next().await {
- match signal.args()? {
- NameOwnerChangedArgs {
- name, old_owner, ..
- } if valid_player(&name) && old_owner.is_some() => {
- tx.send(Event::Remove(name.into())).await?
- }
- NameOwnerChangedArgs {
- name, new_owner, ..
- } if valid_player(&name) && new_owner.is_some() => {
- tx.send(Event::Add(name.into())).await?
- }
- _ => {}
- }
- }
-
- Ok(())
-}
diff --git a/src/player.rs b/src/player.rs
deleted file mode 100644
index 8dd3a51..0000000
--- a/src/player.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-use std::collections::VecDeque;
-
-use tokio::{
- sync::{mpsc::Receiver, watch::Sender},
- task::{AbortHandle, JoinHandle, JoinSet},
-};
-use zbus::{names::OwnedBusName, Connection};
-
-use crate::{
- components::Component,
- dbus::player::{PlaybackStatus, PlayerProxy},
- printer::Status,
- Result,
-};
-
-const BLACK: &str = "#1d2021";
-const YELLOW: &str = "#fabd2f";
-const CYAN: &str = "#8ec07c";
-
-pub enum Command {
- Shift,
- Raise(String),
-}
-
-#[derive(Debug)]
-pub struct Player {
- pub name: OwnedBusName,
- pub handle: JoinHandle<Result<()>>,
- pub tx: Sender<(u8, usize)>,
-}
-
-#[derive(Debug, Clone)]
-pub enum Event {
- Add(OwnedBusName),
- Remove(OwnedBusName),
- Shift,
-}
-
-pub async fn listener(
- conn: Connection,
- mut rx: Receiver<Event>,
- wtx: Sender<Status>,
-) -> Result<()> {
- let mut players: VecDeque<OwnedBusName> = VecDeque::new();
- let mut active: Option<OwnedBusName> = Default::default();
- let mut listener: Option<AbortHandle> = None;
- let mut printer: Option<AbortHandle> = None;
- let mut join_set = JoinSet::new();
-
- while let Some(value) = rx.recv().await {
- match value {
- Event::Add(name) => {
- let player_proxy = PlayerProxy::builder(&conn)
- .destination(name.clone())?
- .build()
- .await?;
-
- match player_proxy.playback_status().await? {
- PlaybackStatus::Playing => players.push_front(name),
- PlaybackStatus::Paused => players.push_back(name),
- PlaybackStatus::Stopped => players.push_back(name),
- }
- }
- Event::Remove(name) => {
- if let Some(index) = players.iter().position(|p| *p == name) {
- players.remove(index);
- }
- }
- Event::Shift if !players.is_empty() => players.rotate_left(1),
- _ => {}
- };
-
- if players.front() != active.as_ref() {
- if active.is_some() {
- if let Some(h) = listener.take() {
- h.abort();
- }
-
- if let Some(h) = printer.take() {
- h.abort();
- }
- active = None;
- }
-
- if let Some(name) = players.front().cloned() {
- let proxy = PlayerProxy::builder(&conn)
- .destination(name.clone())?
- .build()
- .await?;
-
- listener = Some(join_set.spawn(crate::printer::printer(proxy, wtx.clone())));
- }
- }
- }
-
- Ok(())
-}
diff --git a/src/printer.rs b/src/printer.rs
deleted file mode 100644
index 5a6b0ae..0000000
--- a/src/printer.rs
+++ /dev/null
@@ -1,255 +0,0 @@
-use std::{collections::HashMap, sync::Arc, time::Duration};
-
-use futures_util::stream::StreamExt;
-use serde::{Deserialize, Serialize};
-use tokio::{
- sync::{watch::Sender, Mutex},
- task::{JoinHandle, JoinSet},
-};
-use zbus::{proxy::PropertyStream, zvariant::OwnedValue};
-
-use crate::{
- dbus::player::{PlaybackStatus, PlayerProxy},
- i3bar::Block,
- Result,
-};
-
-type StatusLock = Arc<Mutex<Status>>;
-
-const TICK_RATE: Duration = Duration::from_millis(500);
-
-const BLACK: &str = "#1d2021";
-const YELLOW: &str = "#fabd2f";
-const CYAN: &str = "#8ec07c";
-
-pub async fn printer(
- proxy: PlayerProxy<'static>,
- wtx: tokio::sync::watch::Sender<Status>,
-) -> Result<()> {
- let mut join_set: JoinSet<Result<()>> = JoinSet::new();
- let mut rotator = None;
-
- let status = Status {
- can_go_previous: proxy.can_go_previous().await.unwrap_or_default(),
- playback_status: proxy.playback_status().await.unwrap_or_default(),
- can_go_next: proxy.can_go_next().await.unwrap_or_default(),
- volume: proxy.volume().await.ok(),
- ..Default::default()
- };
-
- wtx.send_modify(|s| *s = status);
-
- process_metadata(
- wtx.clone(),
- proxy.metadata().await?,
- &mut rotator,
- &mut None,
- )
- .await;
-
- join_set.spawn(metadata(
- wtx.clone(),
- proxy.receive_metadata_changed().await,
- rotator,
- ));
-
- join_set.spawn(can_go_previous(
- wtx.clone(),
- proxy.receive_can_go_previous_changed().await,
- ));
-
- join_set.spawn(playback_state(
- wtx.clone(),
- proxy.receive_playback_status_changed().await,
- ));
-
- join_set.spawn(can_go_next(
- wtx.clone(),
- proxy.receive_can_go_next_changed().await,
- ));
-
- join_set.spawn(volume(wtx.clone(), proxy.receive_volume_changed().await));
-
- while let Some(res) = join_set.join_next().await {
- res??;
- }
-
- Ok(())
-}
-
-async fn process_metadata(
- wtx: Sender<Status>,
- metadata: HashMap<String, OwnedValue>,
- rotator: &mut Option<JoinHandle<Result<()>>>,
- old_title: &mut Option<String>,
-) {
- let title: Option<String> = metadata
- .get("xesam:title")
- .and_then(|o| o.try_to_owned().ok())
- .and_then(|o| o.try_into().ok());
-
- if *old_title == title {
- return;
- }
-
- *old_title = title.clone();
- let Some(title) = title else {
- wtx.send_modify(|s| {
- s.title = None;
- });
- return;
- };
-
- if title.len() > 10 {
- if let Some(h) = rotator.take() {
- h.abort()
- };
-
- let wtx = wtx.clone();
- *rotator = Some(tokio::spawn(async move {
- let mut interval = tokio::time::interval(TICK_RATE);
- let mut chars = title.chars().collect::<Vec<char>>();
- chars.push(' ');
- loop {
- interval.tick().await;
- wtx.send_modify(|s| s.title = Some(chars[0..10].iter().collect()));
- chars.rotate_left(1);
- }
- }));
- } else {
- wtx.send_modify(|s| s.title = Some(title));
- }
-}
-
-async fn metadata(
- wtx: Sender<Status>,
- mut stream: PropertyStream<'_, HashMap<String, OwnedValue>>,
- mut rotator: Option<JoinHandle<Result<()>>>,
-) -> Result<()> {
- let mut old_title = None;
- while let Some(signal) = stream.next().await {
- if let Ok(metadata) = signal.get().await {
- process_metadata(wtx.clone(), metadata, &mut rotator, &mut old_title).await
- };
- }
- Ok(())
-}
-
-async fn can_go_previous(wtx: Sender<Status>, mut stream: PropertyStream<'_, bool>) -> Result<()> {
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- wtx.send_modify(|status| status.can_go_previous = val);
- };
- }
- Ok(())
-}
-
-async fn playback_state(
- wtx: Sender<Status>,
- mut stream: PropertyStream<'_, PlaybackStatus>,
-) -> Result<()> {
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- let (color, background) = match val {
- PlaybackStatus::Playing => (Some(BLACK.into()), Some(CYAN.into())),
- PlaybackStatus::Paused => (Some(BLACK.into()), Some(YELLOW.into())),
- PlaybackStatus::Stopped => (None, None),
- };
-
- wtx.send_modify(|status| {
- status.playback_status = val;
- status.color = color;
- status.background = background;
- });
- }
- }
- Ok(())
-}
-
-async fn can_go_next(wtx: Sender<Status>, mut stream: PropertyStream<'_, bool>) -> Result<()> {
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- wtx.send_modify(|status| status.can_go_next = val);
- };
- }
- Ok(())
-}
-
-async fn volume(wtx: Sender<Status>, mut stream: PropertyStream<'_, f64>) -> Result<()> {
- while let Some(signal) = stream.next().await {
- if let Ok(val) = signal.get().await {
- wtx.send_modify(|status| status.volume = Some(val));
- };
- }
- Ok(())
-}
-
-#[derive(Debug, Clone, Copy)]
-pub enum Component {
- Icon,
- Title,
- Prev,
- Play,
- Pause,
- Next,
- Volume,
- Space,
-}
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct Status {
- title: Option<String>,
- can_go_previous: bool,
- playback_status: PlaybackStatus,
- can_go_next: bool,
- volume: Option<f64>,
- color: Option<String>,
- background: Option<String>,
-}
-
-impl Status {
- fn build(&mut self) -> Block {
- let mut full_text = String::new();
- full_text.push_str(" 󰝚 ");
-
- if let Some(title) = self.title.as_ref() {
- full_text.push_str(title);
- full_text.push(' ');
- }
-
- if self.can_go_previous {
- full_text.push_str("󰒮 ");
- }
-
- if self.playback_status == PlaybackStatus::Playing {
- full_text.push_str("󰏤 ");
- } else {
- full_text.push_str("󰐊 ");
- }
-
- if self.can_go_next {
- full_text.push_str("󰒭 ");
- }
-
- if let Some(volume) = self.volume.map(|v| (v * 100_f64) as u32) {
- match volume {
- v @ 66.. => full_text.push_str(&format!("󰕾 {v}% ")),
- v @ 33.. => full_text.push_str(&format!("󰖀 {v}% ")),
- v @ 0.. => full_text.push_str(&format!("󰕿 {v}% ")),
- }
- }
-
- let (color, background) = match self.playback_status {
- PlaybackStatus::Playing => (Some(BLACK.into()), Some(CYAN.into())),
- PlaybackStatus::Paused => (Some(BLACK.into()), Some(YELLOW.into())),
- PlaybackStatus::Stopped => (None, None),
- };
-
- Block {
- full_text,
- color,
- background,
- ..Default::default()
- }
- }
-}
diff --git a/src/server.rs b/src/server.rs
deleted file mode 100644
index 4346923..0000000
--- a/src/server.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-use futures_util::StreamExt;
-
-use tokio::{
- io::{AsyncReadExt, AsyncWriteExt, BufReader},
- net::{UnixListener, UnixStream},
- sync::mpsc::Sender,
- task::JoinSet,
-};
-use zbus::{
- fdo::{DBusProxy, NameOwnerChangedArgs},
- names::OwnedBusName,
- Connection,
-};
-
-use crate::{
- dbus::{media_player2::MediaPlayer2Proxy, player::PlayerProxy},
- i3bar::Click,
- player::Event,
- printer::Status,
- Error,
-};
-
-pub async fn server() -> Result<(), Error> {
- let mut join_set = JoinSet::new();
- let conn = Connection::session().await?;
-
- let status = Status::default();
-
- let (tx, rx) = tokio::sync::mpsc::channel(128);
- let (wtx, wrx) = tokio::sync::watch::channel(status);
-
- join_set.spawn(handle_name_owner_change(tx.clone(), conn.clone()));
- join_set.spawn(crate::player::listener(conn.clone(), rx, wtx));
-
- let listener = UnixListener::bind("/tmp/i3blocks-mpris.sock").unwrap();
-
- loop {
- match listener.accept().await {
- Ok((stream, _addr)) => {
- tokio::spawn(handle_client(stream, conn.clone(), wrx.clone(), tx.clone()));
- }
- Err(err) => {
- println!("Error: {}", err);
- break;
- }
- }
- }
-
- while let Some(res) = join_set.join_next().await {
- if let Err(err) = res? {
- eprintln!("{err}")
- };
- }
-
- Ok(())
-}
-
-async fn handle_client(
- stream: UnixStream,
- conn: Connection,
- mut wrx: tokio::sync::watch::Receiver<Status>,
- tx: Sender<Event>,
-) -> Result<(), Error> {
- let (reader, mut writer) = stream.into_split();
-
- let mut reader = BufReader::new(reader);
- let mut buf = Default::default();
-
- reader.read_to_end(&mut buf).await?;
-
- tokio::spawn(async move {
- while reader.read_to_end(&mut buf).await.is_ok() {
- handle_click(conn.clone(), tx.clone(), serde_json::from_slice(&buf)?).await?;
- }
- Result::<(), Error>::Ok(())
- });
-
- loop {
- if wrx.changed().await.is_ok() {
- let status = wrx.borrow_and_update();
- let Ok(v) = bincode::serialize(&*status) else {
- continue;
- };
- writer.write_all(&v).await;
- }
- }
-}
-
-pub async fn handle_click(
- conn: Connection,
- tx: Sender<Event>,
- click: Click,
-) -> Result<(), Error> {
- let Some(name) = click.name else {
- return Ok(());
- };
-
- let Some(instance) = click.instance else {
- return Ok(());
- };
-
- let player_proxy = PlayerProxy::builder(&conn)
- .destination(name)?
- .build()
- .await?;
-
- let mpris_proxy = MediaPlayer2Proxy::builder(player_proxy.inner().connection())
- .destination(player_proxy.inner().destination())?
- .build()
- .await?;
-
- match (instance.as_ref(), click.button) {
- ("mpris-icon", 1) if mpris_proxy.can_raise().await? => mpris_proxy.raise().await?,
- ("mpris-title", _) => {}
- ("mpris-prev", 1) => player_proxy.previous().await?,
- ("mpris-playPause", 1) => player_proxy.play_pause().await?,
- ("mpris-next", 1) => player_proxy.next().await?,
- ("mpris-volume", 4) => {
- player_proxy
- .set_volume(player_proxy.volume().await? - 0.05)
- .await?
- }
- ("mpris-volume", 5) => {
- player_proxy
- .set_volume(player_proxy.volume().await? + 0.05)
- .await?
- }
- (_, 3) => tx.send(Event::Shift).await?,
- _ => {}
- }
-
- Ok(())
-}
-
-const IGNORED: [&str; 1] = ["playerctld"];
-
-fn valid_player(name: &str) -> bool {
- name.strip_prefix("org.mpris.MediaPlayer2.")
- .and_then(|s| s.split('.').next())
- .is_some_and(|s| !IGNORED.contains(&s))
-}
-
-async fn handle_name_owner_change(tx: Sender<Event>, conn: Connection) -> Result<(), Error> {
- let dbus_proxy = DBusProxy::new(&conn).await?;
- let names: Vec<OwnedBusName> = dbus_proxy.list_names().await?;
-
- let players = names.iter().filter(|s| valid_player(s)).collect::<Vec<_>>();
-
- for player in players {
- tx.send(Event::Add(player.clone())).await?;
- }
-
- let mut stream = dbus_proxy.receive_name_owner_changed().await?;
-
- while let Some(signal) = stream.next().await {
- match signal.args()? {
- NameOwnerChangedArgs {
- name, old_owner, ..
- } if valid_player(&name) && old_owner.is_some() => {
- tx.send(Event::Remove(name.into())).await?
- }
- NameOwnerChangedArgs {
- name, new_owner, ..
- } if valid_player(&name) && new_owner.is_some() => {
- tx.send(Event::Add(name.into())).await?
- }
- _ => {}
- }
- }
-
- Ok(())
-}
-