This commit is contained in:
Nox Sluijtman 2025-07-08 18:55:41 +02:00
parent f0ba8bf593
commit f1add467d0
Signed by: Egg
SSH key fingerprint: SHA256:2sG9X3C7Xvq2svGumz1/k7cm8l4G9+qAtAeugqB4J9M
5 changed files with 242 additions and 216 deletions

View file

@ -1,19 +0,0 @@
use clap::CommandFactory;
use clap_mangen::generate_to;
#[path = "src/cli.rs"]
mod cli;
use cli::*;
fn main() -> std::io::Result<()> {
let out_dir =
std::path::PathBuf::from(std::env::var_os("OUT_DIR").ok_or(std::io::ErrorKind::NotFound)?);
let cmd = Cli::command();
generate_to(cmd, &out_dir)?;
println!("cargo:warning=OUT_DIR: {out_dir:?}");
Ok(())
}

View file

@ -1,14 +1,12 @@
extern crate libnotify;
extern crate mpd; extern crate mpd;
mod cli; mod noise;
use cli::*; use noise::{commands::Noise, parser::*};
use clap::{Command, CommandFactory, Parser}; use clap::{Command, CommandFactory, Parser};
use clap_complete::{generate, Generator}; use clap_complete::{generate, Generator};
use libnotify::Notification; use mpd::Client;
use mpd::{Client, Query, Song, State, Term}; use std::io;
use std::{io, time::Duration};
fn print_completions<G: Generator>(generator: G, cmd: &mut Command) { fn print_completions<G: Generator>(generator: G, cmd: &mut Command) {
generate( generate(
@ -24,129 +22,40 @@ fn main() {
if let Some(completions) = cli.completions { if let Some(completions) = cli.completions {
let mut cmd = Cli::command(); let mut cmd = Cli::command();
eprintln!("Generating completion file for {completions:?}..."); eprintln!("Generatig completion file for {completions:?}...");
print_completions(completions, &mut cmd); print_completions(completions, &mut cmd);
return; return;
} }
libnotify::init("noise").unwrap();
let n = libnotify::Notification::new("Noise", None, None);
let host = cli.host.unwrap_or("localhost:6600".into()); let host = cli.host.unwrap_or("localhost:6600".into());
let mut conn = Client::connect(host).expect("Connection failed"); let mut conn = Client::connect(host).expect("Connection failed");
if let Some(cmd) = &cli.command { if let Some(cmd) = &cli.command {
match cmd { match cmd {
Commands::Play { track } => { Commands::Play { track } => conn.noise_play(*track),
if let Some(i) = track { Commands::Stop => conn.stop().unwrap(),
conn.switch(*i).unwrap(); Commands::Toggle => conn.toggle(),
} else {
conn.play().unwrap();
}
}
Commands::Stop => {
conn.stop().unwrap();
}
Commands::Toggle => {
if conn.status().unwrap().state == State::Stop {
conn.play().unwrap();
} else {
conn.toggle_pause().unwrap();
}
}
Commands::Next => conn.next().unwrap(), Commands::Next => conn.next().unwrap(),
Commands::Prev => conn.prev().unwrap(), Commands::Prev => conn.prev().unwrap(),
Commands::List { file } => { Commands::List { file } => conn.list_queue(*file, cli.verbose),
let _current_song = conn.currentsong().unwrap();
conn.queue().unwrap().iter().for_each(|x| {
println!(
"{:<02} {}",
x.place.unwrap().pos,
if *file {
x.file.clone()
} else {
format_song(x.clone())
}
)
});
}
Commands::Update => {
let thing = conn.update().unwrap();
println!("{thing}")
}
Commands::Current => (), Commands::Current => (),
Commands::Clear => conn.clear().unwrap(), Commands::Clear => conn.clear().unwrap(),
Commands::Search { query, max } => { Commands::Search { query, max } => conn.noise_search(query, *max),
let query = query.join(" "); Commands::Find { query, max, append } => conn.noise_find(query, *max, *append),
let max = max.unwrap_or(conn.stats().unwrap().songs); Commands::Crossfade { seconds } => conn.noise_crossfade(*seconds),
Commands::Shuffle => conn.shuf(),
conn.search(&Query::new().and(Term::Any, query), (0, max)) Commands::Repeat => conn.toggle_repeat(),
.unwrap() Commands::Random => conn.toggle_random(),
.iter() Commands::Single => conn.toggle_single(),
.for_each(|x| println!("{}", x.file)); Commands::Consume => conn.toggle_consume(),
} Commands::Volume { percentage } => conn.vol(*percentage),
Commands::Find { query, max, append } => { Commands::Munch => conn.munch(),
let query = query.join(" "); Commands::Length => conn.length(),
let max = max.unwrap_or(conn.stats().unwrap().songs);
if *append {
conn.findadd(&Query::new().and(Term::Any, query)).unwrap();
} else {
conn.find(&Query::new().and(Term::Any, query), (0, max))
.unwrap()
.iter()
.for_each(|x| println!("{}", x.file));
}
}
Commands::Crossfade { seconds } => {
if let Some(crossfade) = seconds {
conn.crossfade(*crossfade).unwrap();
} else {
println!(
"Crossfade: {}",
conn.status()
.unwrap()
.crossfade
.map_or("Disabled".into(), |d| d.as_secs().to_string())
);
}
}
Commands::Shuffle => {
send_notification("Shuffling queueueu...", &n);
println!("Shuffling queueueu...");
conn.shuffle(..).unwrap();
}
Commands::Repeat => {
let repeat_state = conn.status().unwrap().repeat;
conn.repeat(!repeat_state).unwrap();
}
Commands::Random => {
let random_state = conn.status().unwrap().random;
// conn.repeat(!random_state).unwrap();
println!("{}", random_state)
}
Commands::Single => {
let single_state = conn.status().unwrap().single;
conn.single(!single_state).unwrap();
}
Commands::Consume => {
let consume_state = conn.status().unwrap().consume;
conn.consume(!consume_state).unwrap();
}
Commands::Volume { percentage } => {
if let Some(volume) = percentage {
conn.volume(*volume).unwrap()
}
let vol = conn.status().unwrap().volume;
println!("Volume at {vol}%")
}
} }
if let Commands::Stop if let Commands::Stop
| Commands::List { .. } | Commands::List { .. }
| Commands::Update
| Commands::Clear | Commands::Clear
| Commands::Search { .. } | Commands::Search { .. }
| Commands::Find { .. } | Commands::Find { .. }
@ -156,86 +65,5 @@ fn main() {
return; return;
} }
} }
println!("{}", get_status(conn, cli.verbose, n)); println!("{}", conn.get_status(cli.verbose));
}
// fn toggle_state(mut client: Client, state: ) -> () {}
fn format_song(song: Song) -> String {
format!(
"{} - {}",
song.artist.unwrap_or("Unknown".into()),
song.title.unwrap()
)
}
trait QuickFmt {
fn quick_fmt(&self) -> &'static str;
}
impl QuickFmt for bool {
fn quick_fmt(&self) -> &'static str {
if *self {
"on"
} else {
"off"
}
}
}
fn get_status(mut client: Client, verbose: bool, notify: Notification) -> String {
let current_song = if let Some(song) = client.currentsong().unwrap() {
format_song(song)
} else {
"No song playing".into()
};
send_notification(&current_song, &notify);
if !verbose {
return current_song;
}
let mut output = Vec::with_capacity(3);
output.push(current_song);
let status = client.status().unwrap();
output.push(format!(
"volume: {vol:<6} repeat: {repeat:<6} random: {rnd:<6} single: {single:<6} consume: {consume:<6}",
vol = format!("{}%", status.volume),
repeat = status.repeat.quick_fmt(),
rnd = status.random.quick_fmt(),
single = status.single.quick_fmt(),
consume = status.consume.quick_fmt()
));
let state = match status.state {
State::Stop => "stopped",
State::Pause => "paused",
State::Play => "playing",
};
let duration = status.time.map_or("".into(), |(stamp, total)| {
let stamp = to_mins_string(stamp);
let total = to_mins_string(total);
format!("{stamp}/{total}")
});
output.push(format!("[{state}] {duration}"));
output.join("\n")
}
fn send_notification(body: &str, notify: &Notification) {
let _ = notify.update("Noise", body, None);
notify.show().unwrap();
}
fn to_mins_string(dur: Duration) -> String {
let seconds = dur.as_secs();
let minutes = seconds / 60;
let rem_seconds = seconds % 60;
format!("{minutes:02}:{rem_seconds:02}")
} }

2
src/noise.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod commands;
pub mod parser;

213
src/noise/commands.rs Normal file
View file

@ -0,0 +1,213 @@
extern crate libnotify;
extern crate mpd;
use libnotify::Notification;
use mpd::{Client, Query, Song, State, Term};
use std::time::Duration;
pub trait Noise {
fn noise_find(&mut self, query: &Vec<String>, max: Option<u32>, append: bool);
fn noise_search(&mut self, query: &Vec<String>, max: Option<u32>);
fn noise_crossfade(&mut self, seconds: Option<i64>);
fn noise_play(&mut self, track: Option<u32>);
fn toggle(&mut self);
fn munch(&mut self);
fn length(&mut self);
fn vol(&mut self, percentage: Option<i8>);
fn toggle_consume(&mut self);
fn toggle_repeat(&mut self);
fn toggle_random(&mut self);
fn toggle_single(&mut self);
fn shuf(&mut self);
fn get_status(&mut self, verbose: bool) -> String;
fn list_queue(&mut self, file: bool, _verbose: bool);
}
impl Noise for Client {
fn noise_find(&mut self, query: &Vec<String>, max: Option<u32>, append: bool) {
let query = query.join(" ");
let max = max.unwrap_or(self.stats().unwrap().songs);
if append {
self.findadd(&Query::new().and(Term::Any, query)).unwrap();
} else {
self.find(&Query::new().and(Term::Any, query), (0, max))
.unwrap()
.iter()
.for_each(|x| println!("{}", x.file));
}
}
fn noise_search(&mut self, query: &Vec<String>, max: Option<u32>) {
let query = query.join(" ");
let max = max.unwrap_or(self.stats().unwrap().songs);
self.search(&Query::new().and(Term::Any, query), (0, max))
.unwrap()
.iter()
.for_each(|x| println!("{}", x.file));
}
fn noise_crossfade(&mut self, seconds: Option<i64>) {
if let Some(seconds) = seconds {
self.crossfade(seconds).unwrap();
} else {
println!(
"Crossfade: {}",
self.status()
.unwrap()
.crossfade
.map_or("Disabled".into(), |d| d.as_secs().to_string())
)
}
}
fn noise_play(&mut self, track: Option<u32>) {
if let Some(i) = track {
self.switch(i).unwrap();
} else {
self.play().unwrap();
}
}
fn toggle(&mut self) {
if self.status().unwrap().state == State::Stop {
self.play().unwrap();
} else {
self.toggle_pause().unwrap();
}
}
fn munch(&mut self) {
// let current_song = self.currentsong().unwrap().unwrap().place.unwrap();
// self.delete(current_song).unwrap();
todo!("Not net implemented")
}
fn length(&mut self) {
let length = self.status().unwrap().queue_len;
println!("{length}");
}
fn vol(&mut self, percentage: Option<i8>) {
if let Some(volume) = percentage {
self.volume(volume).unwrap()
}
let vol = self.status().unwrap().volume;
println!("Volume at {vol}%")
}
fn toggle_consume(&mut self) {
let consume_state = self.status().unwrap().consume;
self.consume(!consume_state).unwrap();
}
fn toggle_repeat(&mut self) {
let repeat_state = self.status().unwrap().repeat;
self.repeat(!repeat_state).unwrap();
}
fn toggle_random(&mut self) {
let random_state = self.status().unwrap().random;
self.random(!random_state).unwrap();
}
fn toggle_single(&mut self) {
let single_state = self.status().unwrap().single;
self.single(!single_state).unwrap();
}
fn shuf(&mut self) {
send_notification("Shuffling queueueu...");
println!("Shuffling queueueu...");
self.shuffle(..).unwrap();
}
fn list_queue(&mut self, file: bool, _verbose: bool) {
self.queue().unwrap().iter().for_each(|x| {
println!(
"{:<02} {}",
x.place.unwrap().pos,
if file {
x.file.clone()
} else {
format_song(x.clone())
}
)
});
}
fn get_status(&mut self, verbose: bool) -> String {
let current_song = if let Some(song) = self.currentsong().unwrap() {
format_song(song)
} else {
"No song playing".into()
};
send_notification(&current_song);
if !verbose {
return current_song;
}
let mut output = Vec::with_capacity(3);
output.push(current_song);
let status = self.status().unwrap();
let fmt = |x| match x {
true => "On",
false => "Off",
};
output.push(format!(
"volume: {vol:<6} repeat: {repeat:<6} random: {rnd:<6} single: {single:<6} consume: {consume:<6}",
vol = format!("{}%", status.volume),
repeat = fmt(status.repeat),
rnd = fmt(status.random),
single = fmt(status.single),
consume = fmt(status.consume)
));
let state = match status.state {
State::Stop => "stopped",
State::Pause => "paused",
State::Play => "playing",
};
let duration = status.time.map_or("".into(), |(stamp, total)| {
let stamp = to_mins_string(stamp);
let total = to_mins_string(total);
format!("{stamp}/{total}")
});
output.push(format!("[{state}] {duration}"));
output.join("\n")
}
}
fn format_song(song: Song) -> String {
format!(
"{} - {}",
song.artist.unwrap_or("Unknown".into()),
song.title.unwrap()
)
}
// fn get_status(client: &mut Client, verbose: bool) -> String {}
fn send_notification(body: &str) {
libnotify::init("noise").unwrap();
let notify = Notification::new("Noise", Some(body), None);
notify.show().unwrap();
}
fn to_mins_string(dur: Duration) -> String {
let seconds = dur.as_secs();
let minutes = seconds / 60;
let rem_seconds = seconds % 60;
format!("{minutes:02}:{rem_seconds:02}")
}

View file

@ -9,7 +9,7 @@ use clap_complete::Shell;
long_about = "I like how 'mpc' works for the most part, but I don't use most of its features and there are some parts of it that I feel could be more ergonomic. In comes 'noise', an opinionated, even more minimalist 'mpd' client than 'mpc'.", long_about = "I like how 'mpc' works for the most part, but I don't use most of its features and there are some parts of it that I feel could be more ergonomic. In comes 'noise', an opinionated, even more minimalist 'mpd' client than 'mpc'.",
name = "noise" name = "noise"
)] )]
#[command(propagate_version = true)] // #[command(propagate_version = true)]
pub struct Cli { pub struct Cli {
#[command(subcommand)] #[command(subcommand)]
@ -51,8 +51,8 @@ pub enum Commands {
#[arg(value_hint = ValueHint::Other)] #[arg(value_hint = ValueHint::Other)]
seconds: Option<i64>, seconds: Option<i64>,
}, },
///Update still needs some work // ///Update still needs some work
Update, // Update,
/// Return currently playing song /// Return currently playing song
Current, Current,
/// Clear current queueueu /// Clear current queueueu
@ -94,7 +94,7 @@ pub enum Commands {
// position: Option<u32>, // position: Option<u32>,
// }, // },
/// Shuffles the current queueue /// Shuffles the current queueue
#[command(visible_alias = "scramble")] #[command(visible_alias = "shuf")]
Shuffle, Shuffle,
/// Toggles repeat /// Toggles repeat
Repeat, Repeat,
@ -110,4 +110,6 @@ pub enum Commands {
#[arg(value_hint = ValueHint::Other)] #[arg(value_hint = ValueHint::Other)]
percentage: Option<i8>, percentage: Option<i8>,
}, },
Munch,
Length,
} }