summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/i3blocks.config6
-rw-r--r--src/dbus/player.rs8
-rw-r--r--src/i3bar.rs20
-rw-r--r--src/listener.rs115
-rw-r--r--src/main.rs31
5 files changed, 109 insertions, 71 deletions
diff --git a/contrib/i3blocks.config b/contrib/i3blocks.config
index 87f7a5e..e012698 100644
--- a/contrib/i3blocks.config
+++ b/contrib/i3blocks.config
@@ -3,6 +3,6 @@
[mpris]
full_text=
-[time]
-command=date +" %a %m/%d %T "
-interval=1
+# [time]
+# command=date +" %a %m/%d %T "
+# interval=1
diff --git a/src/dbus/player.rs b/src/dbus/player.rs
index a78d7ed..b3424a4 100644
--- a/src/dbus/player.rs
+++ b/src/dbus/player.rs
@@ -176,7 +176,7 @@ pub struct Metadata {
/// Integer: The speed of the music, in beats per minute.
#[zvariant(rename = "xesam:audioBPM")]
- pub audio_bpm: Option<u64>,
+ pub audio_bpm: Option<i32>,
/// Float: An automatically-generated rating, based on things such as how often it has been played. This should be in the range 0.0 to 1.0.
#[zvariant(rename = "xesam:autoRating")]
@@ -196,7 +196,7 @@ pub struct Metadata {
/// Integer: The disc number on the album that this track is from.
#[zvariant(rename = "xesam:discNumber")]
- pub disc_number: Option<u64>,
+ pub disc_number: Option<i32>,
/// Date/Time: When the track was first played.
#[zvariant(rename = "xesam:firstUsed")]
@@ -220,7 +220,7 @@ pub struct Metadata {
/// Integer: The track number on the album disc.
#[zvariant(rename = "xesam:trackNumber")]
- pub track_number: Option<u64>,
+ pub track_number: Option<i32>,
/// URI: The location of the media file.
#[zvariant(rename = "xesam:url")]
@@ -228,7 +228,7 @@ pub struct Metadata {
/// Integer: The number of times the track has been played.
#[zvariant(rename = "xesam:useCount")]
- pub use_count: Option<u64>,
+ pub use_count: Option<i32>,
/// Float: A user-specified rating. This should be in the range 0.0 to 1.0.
#[zvariant(rename = "xesam:userRating")]
diff --git a/src/i3bar.rs b/src/i3bar.rs
index b69095e..6d74693 100644
--- a/src/i3bar.rs
+++ b/src/i3bar.rs
@@ -2,6 +2,26 @@ use serde::{Deserialize, Serialize};
use crate::Error;
+/// Represent header as described in <https://i3wm.org/docs/i3bar-protocol.html>
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct Header {
+ version: u8,
+ stop_signal: u16,
+ cont_signal: u16,
+ click_events: bool,
+}
+
+impl Default for Header {
+ fn default() -> Self {
+ Self {
+ version: 1,
+ stop_signal: 10,
+ cont_signal: 12,
+ click_events: true,
+ }
+ }
+}
+
/// Represent block as described in <https://i3wm.org/docs/i3bar-protocol.html>
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Block {
diff --git a/src/listener.rs b/src/listener.rs
index 88efb6c..5f86d18 100644
--- a/src/listener.rs
+++ b/src/listener.rs
@@ -1,8 +1,7 @@
use std::{ops::ControlFlow, sync::LazyLock, time::Duration};
-use tokio::task::JoinHandle;
use tokio_stream::StreamExt;
-use zbus::{proxy::PropertyChanged, Connection};
+use zbus::{names::UniqueName, proxy::PropertyChanged, Connection};
use crate::{
dbus::player::{Metadata, PlaybackStatus, PlayerProxy},
@@ -22,80 +21,88 @@ static COLORS: LazyLock<[String; 3]> = LazyLock::new(|| {
});
pub async fn listeners(conn: Connection, blocks: State) -> Result<(), Error> {
- let proxy = PlayerProxy::builder(&conn)
- .destination("org.mpris.MediaPlayer2.playerctld")?
- .build()
- .await?;
-
- let mut rotator: Option<JoinHandle<Result<(), Error>>> = None;
- let mut old_title = String::new();
-
- let mut metadata = proxy.receive_metadata_changed().await;
- let mut prev = proxy.receive_can_go_previous_changed().await;
- let mut play = proxy.receive_playback_status_changed().await;
- let mut next = proxy.receive_can_go_next_changed().await;
- let mut volume = proxy.receive_volume_changed().await;
-
loop {
- tokio::select! {
- Some(prop) = metadata.next() => handle_metadata(prop, blocks.clone(), &mut old_title, &mut rotator).await,
- Some(prop) = prev.next() => handle_prev_next(prop, blocks.clone(), BlockKind::Prev).await,
- Some(prop) = play.next() => handle_play(prop, blocks.clone()).await,
- Some(prop) = next.next() => handle_prev_next(prop, blocks.clone(), BlockKind::Next).await,
- Some(prop) = volume.next() => handle_volume(prop, blocks.clone()).await,
- else => {
- eprintln!("Failed to get next property");
- break
+ let player_proxy = PlayerProxy::builder(&conn)
+ .destination("org.mpris.MediaPlayer2.playerctld")?
+ .build()
+ .await?;
+
+ //let mut rotator: Option<JoinHandle<Result<(), Error>>> = None;
+ let mut old_title = String::new();
+ let mut chars: Vec<char> = Vec::new();
+
+ let mut owner_changed = player_proxy.inner().receive_owner_changed().await?;
+ let mut metadata = player_proxy.receive_metadata_changed().await;
+ let mut prev = player_proxy.receive_can_go_previous_changed().await;
+ let mut play = player_proxy.receive_playback_status_changed().await;
+ let mut next = player_proxy.receive_can_go_next_changed().await;
+ let mut volume = player_proxy.receive_volume_changed().await;
+
+ loop {
+ let control_flow = tokio::select! {
+ Some(prop) = owner_changed.next() => handle_owner_changed(prop).await,
+ Some(prop) = metadata.next() => handle_metadata(prop, blocks.clone(), &mut old_title, &mut chars).await,
+ Some(prop) = prev.next() => handle_prev_next(prop, blocks.clone(), BlockKind::Prev).await,
+ Some(prop) = play.next() => handle_play(prop, blocks.clone()).await,
+ Some(prop) = next.next() => handle_prev_next(prop, blocks.clone(), BlockKind::Next).await,
+ Some(prop) = volume.next() => handle_volume(prop, blocks.clone()).await,
+ _ = tokio::time::sleep(TICK_RATE), if chars.len() > 10 => {
+ chars.rotate_left(1);
+ handle_title(&chars, blocks.clone()).await
+ }
+ };
+
+ match control_flow {
+ ControlFlow::Continue(_) => blocks.notify.notify_one(),
+ ControlFlow::Break(res) => {
+ res?;
+ break;
+ }
}
-
- };
-
- blocks.notify.notify_one();
+ }
}
-
- Ok(())
}
-async fn title_rotator(blocks: State, title: String) -> Result<(), Error> {
- let mut interval = tokio::time::interval(TICK_RATE);
- let mut chars = title.chars().collect::<Vec<char>>();
- chars.push(' ');
- let full_text = String::from_iter(chars[0..10].iter());
+async fn handle_owner_changed(prop: Option<UniqueName<'static>>) -> ControlFlow<Result<(), Error>> {
+ if prop.is_none() {
+ ControlFlow::Break(Ok(()))
+ } else {
+ ControlFlow::Continue(())
+ }
+}
+async fn handle_title(title: &[char], blocks: State) -> ControlFlow<Result<(), Error>> {
let block = &mut blocks.value.write().await[BlockKind::Title as usize];
- block.min_width = Some(MinWidth::Text(full_text.len() + 1));
- block.full_text = full_text;
- block.enabled = false;
+ block.full_text = String::from_iter(title.iter().take(10));
+ block.min_width = Some(MinWidth::Text(block.full_text.len() + 1));
+ block.enabled = true;
- loop {
- interval.tick().await;
- let block = &mut blocks.value.write().await[BlockKind::Title as usize];
- block.full_text = String::from_iter(chars[0..10].iter());
- chars.rotate_left(1);
- }
+ ControlFlow::Continue(())
}
async fn handle_metadata(
prop: PropertyChanged<'_, Metadata>,
blocks: State,
old_title: &mut String,
- rotator: &mut Option<JoinHandle<Result<(), Error>>>,
+ chars: &mut Vec<char>,
) -> ControlFlow<Result<(), Error>> {
- let title = match prop.get().await.map(|m| m.title) {
+ let title = match prop
+ .get()
+ .await
+ .map(|m| m.title)
+ .inspect_err(|err| eprintln!("{err}"))
+ {
Ok(Some(s)) if *old_title != s => s,
_ => return ControlFlow::Continue(()),
};
old_title.clone_from(&title);
- rotator.take().into_iter().for_each(|h| h.abort());
+
+ *chars = title.chars().collect::<Vec<char>>();
+ handle_title(chars, blocks).await;
if title.len() >= 10 {
- *rotator = Some(tokio::spawn(title_rotator(blocks.clone(), title)));
- } else {
- let block = &mut blocks.value.write().await[BlockKind::Title as usize];
- block.min_width = Some(MinWidth::Text(title.len() + 1));
- block.full_text = title;
- block.enabled = true;
+ chars.push(' ');
}
ControlFlow::Continue(())
diff --git a/src/main.rs b/src/main.rs
index cd17ec2..146d450 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,7 @@ use std::{process::Stdio, sync::Arc};
use i3blocks_mpris::{
handler::handlers,
- i3bar::{Align, Block, Click, MinWidth},
+ i3bar::{Align, Block, Click, Header, MinWidth},
listener::listeners,
BlockKind, Watcher,
};
@@ -95,39 +95,50 @@ async fn main() -> Result<(), main_error::MainError> {
tokio::spawn(listeners(conn.clone(), blocks.clone()));
tokio::spawn(handlers(conn, rx));
+ let mut header = serde_json::to_string(&Header::default())?;
+ header.push_str("\n[[]\n");
+ stdout.write_all(header.as_bytes()).await?;
+ stdout.flush().await?;
+
let mut mpris_blocks = String::new();
let mut other_blocks = String::new();
loop {
tokio::select! {
_ = blocks.notify.notified() => {
- mpris_blocks = blocks
+ let enabled = blocks
.value
.read()
.await
.iter()
.filter(|b| b.enabled)
.map(serde_json::to_string)
- .collect::<Result<Vec<_>, _>>()?
- .join(",");
+ .collect::<Result<Vec<_>, _>>()?;
+ mpris_blocks = if enabled.is_empty() {
+ r#"{"name":"mpris","full_text":""}"#.to_owned()
+ } else {
+ enabled.join(",")
+ };
}
- Ok(Some(line)) = child_stdout.next_line() => other_blocks = line,
+ Ok(Some(line)) = child_stdout.next_line() => {
+ if line.starts_with(",[{") {
+ other_blocks = line
+ }
+ },
Ok(Some(line)) = stdin.next_line() => {
handle_stdin(&line, tx.clone(), &mut child_stdin).await?;
continue;
}
- else => break,
+ else => continue,
}
- let output = other_blocks.replace(r#"{"name":"mpris","full_text":""},"#, &mpris_blocks);
- if !output.is_empty() {
+ if !other_blocks.is_empty() {
+ let output = other_blocks.replace(r#"{"name":"mpris","full_text":""}"#, &mpris_blocks);
stdout.write_all(output.as_bytes()).await?;
stdout.write_u8(b'\n').await?;
stdout.flush().await?;
}
}
-
- Ok(())
}
async fn handle_stdin(