1
Fork 0

Compare commits

..

10 commits

Author SHA1 Message Date
0648cab19b
added representative screenshot 2023-10-30 19:11:13 -05:00
82cd4a4e33
added readme.md 2023-10-30 18:58:48 -05:00
21548510fc
added player selection 2023-10-30 18:37:03 -05:00
edc2212d23
minor color changes 2023-10-30 18:11:40 -05:00
2f16f89986
benchmark nightly 2023-10-29 11:48:41 -05:00
60061345fc
rustfmt 2023-10-29 11:40:31 -05:00
4b5f44524c
added benchmark (12us per frame) 2023-10-29 11:36:28 -05:00
c18b11b770
added change detection
swaybar still uses ~2% cpu when a song is playing
2023-10-29 08:45:15 -05:00
88335daf6c
play/pause colors 2023-10-29 00:30:02 -05:00
62cd19625b
spinny (woo) 2023-10-29 00:23:23 -05:00
3 changed files with 105 additions and 33 deletions

18
readme.md Normal file
View file

@ -0,0 +1,18 @@
# Funring ![representative screenshot](https://user-images.githubusercontent.com/37423245/279222372-2005566f-771e-4d19-9dd2-244d3cc4ed0c.png)
A fun ring for your system tray that shows the status of the currently playing song(s)
`funring [player]`
written with swaybar on arch linux in mind
### Installation
1. `cargo install --path .`
### Usage
1. Start spotify or another [mpris compatible music player](https://wiki.archlinux.org/title/MPRIS#Supported_clients)
2. `funring`
if the wrong player is selected by default, pass a name i.e. `funring "Pragha Music Player"`

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly-2023-10-28"

View file

@ -1,70 +1,107 @@
#![feature(test)]
use std::collections::hash_map::DefaultHasher;
use std::env::args_os;
use std::hash::Hash;
use std::hash::Hasher;
use anyhow::Result;
use glam::UVec2;
use glam::Vec2;
use mpris::PlaybackStatus; use mpris::PlaybackStatus;
use mpris::PlayerFinder; use mpris::PlayerFinder;
use anyhow::Result;
use tray_item::IconSource; use tray_item::IconSource;
use tray_item::TrayItem; use tray_item::TrayItem;
use glam::UVec2;
fn main() -> Result<()> { fn main() -> Result<()> {
let pf = PlayerFinder::new()?; let pf = PlayerFinder::new()?;
let players = pf.find_all()?; let players = pf.find_all()?;
let mut tracker = players[0].track_progress(400)?; for player in players.iter().map(|p| p.identity()) {
println!("found {player}");
}
let icon = IconSource::Data{data: gen_icon(0.0, false), height: RES, width: RES}; let player = if let Some(name) = args_os().nth(1) {
pf.find_by_name(&name.into_string().unwrap())?
} else {
pf.find_active()?
};
let mut tracker = player.track_progress(25)?;
let icon = IconSource::Data {
data: gen_icon(0.0, false),
height: RES,
width: RES,
};
let mut tray = TrayItem::new("funring", icon)?; let mut tray = TrayItem::new("funring", icon)?;
println!("Hello, {}!", players[0].identity()); let mut last_hash = None;
println!("tracking {}", player.identity());
loop { loop {
let tick = tracker.tick(); let tick = tracker.tick();
let elapsed = tick.progress.position().as_millis() as f64 let elapsed = tick.progress.position().as_millis() as f64;
+ tick.progress.age().as_millis() as f64 let total = tick.progress.length();
* tick.progress.playback_rate(); if total.is_none() {continue};
let total = tick.progress.length().unwrap().as_millis(); let total = total.unwrap().as_millis();
let prog = elapsed as f32 / total as f32; let prog = elapsed as f32 / total as f32;
let playing = tick.progress.playback_status() == PlaybackStatus::Playing; let playing = tick.progress.playback_status() == PlaybackStatus::Playing;
tray.set_icon( let buffer = gen_icon(prog, playing);
IconSource::Data{data: gen_icon(prog, playing), height: RES, width: RES} let hash = {
)?; let mut hasher = DefaultHasher::new();
buffer.hash(&mut hasher);
hasher.finish()
};
if !last_hash.replace(hash).is_some_and(|h| h == hash) {
tray.set_icon(IconSource::Data {
data: buffer,
height: RES,
width: RES,
})?;
}
} }
} }
const RES: i32 = 8; const RES: i32 = 32;
const LEN: usize = (RES*RES) as usize; const LEN: usize = (RES * RES) as usize;
fn gen_icon( fn gen_icon(prog: f32, playing: bool) -> Vec<u8> {
prog: f32, let mut icon = vec![0; LEN * 4];
playing: bool,
) -> Vec<u8> {
let mut icon = Vec::with_capacity(LEN*4);
(0..LEN*4).for_each(|_| icon.push(0));
for pix in 0..(LEN) { for pix in 0..(LEN) {
let x = pix % RES as usize; let x = pix % RES as usize;
let y = pix / RES as usize; let y = pix / RES as usize;
icon_cs( icon_cs(
UVec2::new(x as u32,y as u32), UVec2::new(x as u32, y as u32),
prog, prog,
playing, playing,
&mut icon[0..LEN*4] &mut icon[0..LEN * 4],
); );
} }
icon icon
} }
// TODO: integrate rust-gpu // TODO: integrate rust-gpu
fn icon_cs( fn icon_cs(idx: UVec2, progress: f32, playing: bool, buf: &mut [u8]) {
pix: UVec2, let index = (idx.y * RES as u32 + idx.x) as usize;
progress: f32, let pix = &mut buf[4 * index..4 * index + 4];
playing: bool, pix[0] = 0;
buf: &mut [u8],
) {
let idx = (pix.y * RES as u32 + pix.x) as usize;
let pix = &mut buf[4*idx..4*idx+4];
pix[0] = 1;
if progress > idx as f32 / LEN as f32 { let center = Vec2::splat(RES as f32 / 2.0);
pix[1] = 255; let idx = idx.as_vec2() - center;
let radius = idx.length() / RES as f32 * 2.0;
let theta = std::f32::consts::PI - f32::atan2(idx.x, idx.y);
let offset = theta - progress * std::f32::consts::TAU;
if offset < 0.0 && (0.5..1.0).contains(&radius) {
pix[1] = if playing { 255 } else { 0 };
//pix[1] = if playing { 255 - (offset * -55.0) as u8 } else { 0 };
pix[2] = if playing { 0 } else { 95 };
pix[3] = 0;
} else if (0.5..1.0).contains(&radius) {
pix[1] = if playing { 6 } else { 20 };
pix[2] = 0; pix[2] = 0;
pix[3] = 0; pix[3] = 0;
} else { } else {
@ -73,3 +110,18 @@ fn icon_cs(
pix[3] = 0; pix[3] = 0;
} }
} }
extern crate test;
#[cfg(test)]
mod tests {
use super::*;
use test::Bencher;
use crate::gen_icon;
#[bench]
fn generate_icon(b: &mut Bencher) {
b.iter(|| gen_icon(0.5, true));
}
}