Compare commits
10 commits
2bdf9b32b8
...
0648cab19b
Author | SHA1 | Date | |
---|---|---|---|
0648cab19b | |||
82cd4a4e33 | |||
21548510fc | |||
edc2212d23 | |||
2f16f89986 | |||
60061345fc | |||
4b5f44524c | |||
c18b11b770 | |||
88335daf6c | |||
62cd19625b |
3 changed files with 105 additions and 33 deletions
18
readme.md
Normal file
18
readme.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Funring 
|
||||||
|
|
||||||
|
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
2
rust-toolchain.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly-2023-10-28"
|
114
src/main.rs
114
src/main.rs
|
@ -1,44 +1,74 @@
|
||||||
|
#![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;
|
||||||
|
@ -46,25 +76,32 @@ fn gen_icon(
|
||||||
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue