2025-06-13 13:09:18 +02:00
|
|
|
extern crate libnotify;
|
2025-06-07 11:24:20 +02:00
|
|
|
extern crate mpd;
|
|
|
|
|
2025-07-02 23:07:43 +02:00
|
|
|
mod cli;
|
|
|
|
use cli::*;
|
|
|
|
|
|
|
|
use clap::{Command, CommandFactory, Parser};
|
|
|
|
use clap_complete::{generate, Generator};
|
2025-06-18 09:30:08 +02:00
|
|
|
use libnotify::Notification;
|
2025-06-19 09:48:46 +02:00
|
|
|
use mpd::{Client, Query, Song, State, Term};
|
2025-07-02 23:07:43 +02:00
|
|
|
use std::{io, time::Duration};
|
2025-06-07 11:43:42 +02:00
|
|
|
|
2025-07-02 23:07:43 +02:00
|
|
|
fn print_completions<G: Generator>(generator: G, cmd: &mut Command) {
|
|
|
|
generate(
|
|
|
|
generator,
|
|
|
|
cmd,
|
|
|
|
cmd.get_name().to_string(),
|
|
|
|
&mut io::stdout(),
|
|
|
|
);
|
2025-06-07 11:24:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn main() {
|
2025-07-02 23:07:43 +02:00
|
|
|
let cli = Cli::parse();
|
|
|
|
|
|
|
|
if let Some(completions) = cli.completions {
|
|
|
|
let mut cmd = Cli::command();
|
|
|
|
eprintln!("Generating completion file for {completions:?}...");
|
|
|
|
print_completions(completions, &mut cmd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2025-06-18 09:30:08 +02:00
|
|
|
libnotify::init("noise").unwrap();
|
|
|
|
|
|
|
|
let n = libnotify::Notification::new("Noise", None, None);
|
2025-06-07 23:50:30 +02:00
|
|
|
|
2025-06-21 21:16:26 +02:00
|
|
|
let host = cli.host.unwrap_or("localhost:6600".into());
|
2025-06-08 22:53:08 +02:00
|
|
|
|
2025-06-19 16:48:22 +02:00
|
|
|
let mut conn = Client::connect(host).expect("Connection failed");
|
2025-06-13 13:09:18 +02:00
|
|
|
|
2025-06-07 23:50:30 +02:00
|
|
|
if let Some(cmd) = &cli.command {
|
|
|
|
match cmd {
|
|
|
|
Commands::Play { track } => {
|
|
|
|
if let Some(i) = track {
|
2025-06-19 16:48:22 +02:00
|
|
|
conn.switch(*i).unwrap();
|
2025-06-07 23:50:30 +02:00
|
|
|
} else {
|
|
|
|
conn.play().unwrap();
|
|
|
|
}
|
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Stop => {
|
2025-06-19 16:48:22 +02:00
|
|
|
conn.stop().unwrap();
|
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Toggle => {
|
2025-06-19 09:48:46 +02:00
|
|
|
if conn.status().unwrap().state == State::Stop {
|
|
|
|
conn.play().unwrap();
|
|
|
|
} else {
|
|
|
|
conn.toggle_pause().unwrap();
|
|
|
|
}
|
2025-06-18 09:30:08 +02:00
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Next => conn.next().unwrap(),
|
|
|
|
Commands::Prev => conn.prev().unwrap(),
|
2025-07-02 23:07:43 +02:00
|
|
|
Commands::List { file } => {
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
)
|
|
|
|
});
|
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Update => {
|
2025-06-07 23:50:30 +02:00
|
|
|
let thing = conn.update().unwrap();
|
2025-06-13 13:09:18 +02:00
|
|
|
println!("{thing}")
|
2025-06-07 23:50:30 +02:00
|
|
|
}
|
2025-07-06 00:20:06 +02:00
|
|
|
Commands::Current => (),
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Clear => conn.clear().unwrap(),
|
|
|
|
Commands::Search { query, max } => {
|
2025-06-07 23:50:30 +02:00
|
|
|
let query = query.join(" ");
|
2025-06-21 15:46:40 +02:00
|
|
|
let max = max.unwrap_or(conn.stats().unwrap().songs);
|
2025-06-18 09:30:08 +02:00
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
conn.search(&Query::new().and(Term::Any, query), (0, max))
|
2025-06-18 09:30:08 +02:00
|
|
|
.unwrap()
|
|
|
|
.iter()
|
2025-06-21 15:46:40 +02:00
|
|
|
.for_each(|x| println!("{}", x.file));
|
2025-06-13 13:09:18 +02:00
|
|
|
}
|
2025-06-18 09:30:08 +02:00
|
|
|
Commands::Find { query, max, append } => {
|
2025-06-13 13:09:18 +02:00
|
|
|
let query = query.join(" ");
|
2025-06-21 15:46:40 +02:00
|
|
|
let max = max.unwrap_or(conn.stats().unwrap().songs);
|
|
|
|
|
2025-06-18 09:30:08 +02:00
|
|
|
if *append {
|
|
|
|
conn.findadd(&Query::new().and(Term::Any, query)).unwrap();
|
|
|
|
} else {
|
2025-06-21 15:46:40 +02:00
|
|
|
conn.find(&Query::new().and(Term::Any, query), (0, max))
|
2025-06-18 09:30:08 +02:00
|
|
|
.unwrap()
|
|
|
|
.iter()
|
2025-06-21 15:46:40 +02:00
|
|
|
.for_each(|x| println!("{}", x.file));
|
2025-06-18 09:30:08 +02:00
|
|
|
}
|
2025-06-07 23:50:30 +02:00
|
|
|
}
|
|
|
|
Commands::Crossfade { seconds } => {
|
|
|
|
if let Some(crossfade) = seconds {
|
2025-06-21 15:46:40 +02:00
|
|
|
conn.crossfade(*crossfade).unwrap();
|
2025-06-07 23:50:30 +02:00
|
|
|
} else {
|
2025-06-21 15:46:40 +02:00
|
|
|
println!(
|
|
|
|
"Crossfade: {}",
|
|
|
|
conn.status()
|
|
|
|
.unwrap()
|
|
|
|
.crossfade
|
|
|
|
.map_or("Disabled".into(), |d| d.as_secs().to_string())
|
|
|
|
);
|
2025-06-07 23:50:30 +02:00
|
|
|
}
|
2025-06-19 16:48:22 +02:00
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
Commands::Shuffle => {
|
|
|
|
send_notification("Shuffling queueueu...", &n);
|
2025-06-19 16:48:22 +02:00
|
|
|
println!("Shuffling queueueu...");
|
|
|
|
conn.shuffle(..).unwrap();
|
|
|
|
}
|
2025-07-06 00:20:06 +02:00
|
|
|
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}%")
|
|
|
|
}
|
2025-06-07 11:43:42 +02:00
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
|
|
|
|
if let Commands::Stop
|
|
|
|
| Commands::List { .. }
|
|
|
|
| Commands::Update
|
|
|
|
| Commands::Clear
|
|
|
|
| Commands::Search { .. }
|
|
|
|
| Commands::Find { .. }
|
|
|
|
| Commands::Crossfade { .. }
|
|
|
|
| Commands::Shuffle = cmd
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2025-06-07 11:43:42 +02:00
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
println!("{}", get_status(conn, cli.verbose, n));
|
2025-06-07 11:24:20 +02:00
|
|
|
}
|
2025-06-07 23:50:30 +02:00
|
|
|
|
2025-07-06 00:20:06 +02:00
|
|
|
// fn toggle_state(mut client: Client, state: ) -> () {}
|
|
|
|
|
2025-06-07 23:50:30 +02:00
|
|
|
fn format_song(song: Song) -> String {
|
2025-07-06 00:20:06 +02:00
|
|
|
format!(
|
|
|
|
"{} - {}",
|
|
|
|
song.artist.unwrap_or("Unknown".into()),
|
|
|
|
song.title.unwrap()
|
|
|
|
)
|
2025-06-07 23:50:30 +02:00
|
|
|
}
|
2025-06-08 22:53:08 +02:00
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
trait QuickFmt {
|
|
|
|
fn quick_fmt(&self) -> &'static str;
|
2025-06-08 22:53:08 +02:00
|
|
|
}
|
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
impl QuickFmt for bool {
|
|
|
|
fn quick_fmt(&self) -> &'static str {
|
|
|
|
if *self {
|
|
|
|
"on"
|
|
|
|
} else {
|
|
|
|
"off"
|
2025-06-08 22:53:08 +02:00
|
|
|
}
|
2025-06-21 15:46:40 +02:00
|
|
|
}
|
|
|
|
}
|
2025-06-08 22:53:08 +02:00
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
fn get_status(mut client: Client, verbose: bool, notify: Notification) -> String {
|
|
|
|
let current_song = if let Some(song) = client.currentsong().unwrap() {
|
|
|
|
format_song(song)
|
2025-06-08 22:53:08 +02:00
|
|
|
} else {
|
2025-06-21 15:46:40 +02:00
|
|
|
"No song playing".into()
|
|
|
|
};
|
|
|
|
|
|
|
|
send_notification(¤t_song, ¬ify);
|
|
|
|
if !verbose {
|
|
|
|
return current_song;
|
2025-06-08 22:53:08 +02:00
|
|
|
}
|
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
let mut output = Vec::with_capacity(3);
|
|
|
|
output.push(current_song);
|
2025-06-18 09:30:08 +02:00
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
let status = client.status().unwrap();
|
2025-06-08 22:53:08 +02:00
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
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}"));
|
2025-06-08 22:53:08 +02:00
|
|
|
|
|
|
|
output.join("\n")
|
|
|
|
}
|
|
|
|
|
2025-06-21 15:46:40 +02:00
|
|
|
fn send_notification(body: &str, notify: &Notification) {
|
|
|
|
let _ = notify.update("Noise", body, None);
|
2025-06-18 09:30:08 +02:00
|
|
|
notify.show().unwrap();
|
|
|
|
}
|
|
|
|
|
2025-06-08 22:53:08 +02:00
|
|
|
fn to_mins_string(dur: Duration) -> String {
|
|
|
|
let seconds = dur.as_secs();
|
|
|
|
|
|
|
|
let minutes = seconds / 60;
|
|
|
|
let rem_seconds = seconds % 60;
|
|
|
|
|
2025-06-13 13:09:18 +02:00
|
|
|
format!("{minutes:02}:{rem_seconds:02}")
|
2025-06-08 22:53:08 +02:00
|
|
|
}
|