From ca4fc5a00dca8eb0493fe57b92d45cc1476211d4 Mon Sep 17 00:00:00 2001 From: 409 Date: Sat, 29 Jun 2024 01:57:14 +0200 Subject: [PATCH] feat: previous / next instructions --- src/instructions.rs | 4 ++ src/main.rs | 36 ++---------------- src/mixer.rs | 91 +++++++++++++++++++++++++++++++++++++-------- src/playerctl.rs | 48 ++++++++++++++++++++++++ src/utils.rs | 47 +++++++++++++++++++++-- 5 files changed, 174 insertions(+), 52 deletions(-) create mode 100644 src/playerctl.rs diff --git a/src/instructions.rs b/src/instructions.rs index b6d96cd..4d8efb2 100644 --- a/src/instructions.rs +++ b/src/instructions.rs @@ -7,6 +7,8 @@ pub enum MixerInstruction { DecreaseCurrent, GetCurrent, PlayPauseCurrent, + PlayNext, + PlayPrevious, } impl MixerInstruction { @@ -19,6 +21,8 @@ impl MixerInstruction { 4 => Some(MixerInstruction::DecreaseCurrent), 5 => Some(MixerInstruction::GetCurrent), 6 => Some(MixerInstruction::PlayPauseCurrent), + 7 => Some(MixerInstruction::PlayNext), + 8 => Some(MixerInstruction::PlayPrevious), _ => None, } } diff --git a/src/main.rs b/src/main.rs index 0b61ca5..b56bd93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,16 +2,14 @@ mod instructions; pub mod mixer; pub mod pulseaudio; pub mod utils; +pub mod playerctl; -use anyhow::Result; use mixer::Mixer; use pulseaudio::PulseInstruction; -use std::{process::Command, sync::mpsc::channel}; - -const NOTIFY_SEND_REPLACE_ID: u32 = 1448531; +use std::sync::mpsc::channel; fn main() { - let mainloop = pulse::mainloop::standard::Mainloop::new().expect("Error getting main loop"); + let mainloop = pulse::mainloop::standard::Mainloop::new().expect("Error getting PulseAudio main loop"); let (pulse_ix_tx, pulse_ix_rx) = channel::(); @@ -19,31 +17,3 @@ fn main() { mixer.run(pulse_ix_rx); } - -pub fn send_notification(message: &str) -> Result<()> { - Command::new("notify-send") - .args(vec![ - "Mixrs", - message, - "-r", - &NOTIFY_SEND_REPLACE_ID.to_string(), - ]) - .env("DBUS_SESSION_BUS_ADDRESS", "unix:path=/run/user/1000/bus") - .spawn()?; - - Ok(()) -} - -pub fn playerctl_toggle(target: &str) -> Result<()> { - let get_players = Command::new("playerctl").arg("-l").output()?; - let get_players_output = String::from_utf8(get_players.stdout)?; - let players: Vec<&str> = get_players_output.split("\n").collect(); - - match players.iter().find(|p| p.to_lowercase().contains(&target.to_lowercase())) { - Some(player) => { - Command::new("playerctl").args(vec!["-p", player, "play-pause"]).spawn()?; - }, - None => {}, - }; - Ok(()) -} diff --git a/src/mixer.rs b/src/mixer.rs index 9397731..989b67f 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -28,10 +28,9 @@ use pulse::{ use crate::{ instructions::MixerInstruction, - playerctl_toggle, + playerctl::{playerctl_next, playerctl_play_pause, playerctl_previous}, pulseaudio::{PulseInstruction, SinkInputMixerData}, - send_notification, - utils::{get_sink_input_name, percentage_to_total_volume}, + utils::{get_sink_input_name, percentage_to_total_volume, send_notification, total_volume_to_percentage, volume_to_percentage}, }; pub struct Mixer { @@ -187,6 +186,8 @@ impl Mixer { MixerInstruction::DecreaseCurrent => self.decrease_volume_current(), MixerInstruction::GetCurrent => self.get_current(), MixerInstruction::PlayPauseCurrent => self.play_pause_current(), + MixerInstruction::PlayNext => self.play_next_current(), + MixerInstruction::PlayPrevious => self.play_previous_current(), }, Err(_) => (), } @@ -357,14 +358,16 @@ impl Mixer { drop(index_lock); - let sink_index = *self.sink_inputs.keys().nth(index).unwrap(); + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; self.context .borrow_mut() .introspect() .borrow_mut() .set_sink_input_mute( - sink_index, + *sink_index, !self.sink_inputs.get(&sink_index).unwrap().muted, None, ); @@ -379,9 +382,12 @@ impl Mixer { drop(index_lock); - let sink_index = *self.sink_inputs.keys().nth(index).unwrap(); + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; let sink_input = self.sink_inputs.get(&sink_index).unwrap(); + let sink_name = sink_input.name.clone(); let mut volume = ChannelVolumes::default(); volume.set( @@ -395,7 +401,11 @@ impl Mixer { .borrow_mut() .introspect() .borrow_mut() - .set_sink_input_volume(sink_index, &volume, None); + .set_sink_input_volume(*sink_index, &volume, Some(Box::new(move |success| { + if success { + let _ = send_notification(&format!("{sink_name}: {}%", volume_to_percentage(volume))); + } + }))); } pub fn decrease_volume_current(&mut self) { @@ -407,9 +417,12 @@ impl Mixer { drop(index_lock); - let sink_index = *self.sink_inputs.keys().nth(index).unwrap(); + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; let sink_input = self.sink_inputs.get(&sink_index).unwrap(); + let sink_name = sink_input.name.clone(); let mut volume = ChannelVolumes::default(); volume.set( @@ -423,7 +436,11 @@ impl Mixer { .borrow_mut() .introspect() .borrow_mut() - .set_sink_input_volume(sink_index, &volume, None); + .set_sink_input_volume(*sink_index, &volume, Some(Box::new(move |success| { + if success { + let _ = send_notification(&format!("{sink_name}: {}%", volume_to_percentage(volume))); + } + }))); } pub fn get_current(&self) { @@ -435,10 +452,12 @@ impl Mixer { drop(index_lock); - let sink_index = *self.sink_inputs.keys().nth(index).unwrap(); + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; let current_name = &self.sink_inputs.get(&sink_index).unwrap().name; - let _ = send_notification(&format!("Selected: {current_name}")); + let _ = send_notification(&format!("{current_name}")); } pub fn play_pause_current(&self) { @@ -450,13 +469,53 @@ impl Mixer { drop(index_lock); - let sink_index = *self.sink_inputs.keys().nth(index).unwrap(); + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; let current_name = &self.sink_inputs.get(&sink_index).unwrap().name; - match playerctl_toggle(current_name) { - Ok(_) => { - let _ = send_notification(&format!("Toggled {current_name}")); - } + match playerctl_play_pause(current_name) { + Ok(_) => (), + Err(_) => (), + }; + } + + pub fn play_next_current(&self) { + let index_lock = self.selected_index.lock().unwrap(); + + let Some(index) = *index_lock else { + return; + }; + + drop(index_lock); + + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; + + let current_name = &self.sink_inputs.get(&sink_index).unwrap().name; + match playerctl_next(current_name) { + Ok(_) => (), + Err(_) => (), + }; + } + + pub fn play_previous_current(&self) { + let index_lock = self.selected_index.lock().unwrap(); + + let Some(index) = *index_lock else { + return; + }; + + drop(index_lock); + + let Some(sink_index) = self.sink_inputs.keys().nth(index) else { + return; + }; + + let current_name = &self.sink_inputs.get(&sink_index).unwrap().name; + match playerctl_previous(current_name) { + Ok(_) => (), Err(_) => (), }; } diff --git a/src/playerctl.rs b/src/playerctl.rs new file mode 100644 index 0000000..bf18e4b --- /dev/null +++ b/src/playerctl.rs @@ -0,0 +1,48 @@ +use std::process::Command; + +use anyhow::{anyhow, Result}; + +fn get_playerctl_player(target: &str) -> Result { + let get_players = Command::new("playerctl").arg("-l").output()?; + let get_players_output = String::from_utf8(get_players.stdout)?; + let players: Vec<&str> = get_players_output.split("\n").collect(); + + let Some(player) = players + .iter() + .find(|p| p.to_lowercase().contains(&target.to_lowercase())) + else { + return Err(anyhow!("Error getting player '{target}'")); + }; + + Ok(player.to_string()) +} + +pub fn playerctl_play_pause(target: &str) -> Result<()> { + let player = get_playerctl_player(target)?; + + Command::new("playerctl") + .args(vec!["-p", &player, "play-pause"]) + .spawn()?; + + Ok(()) +} + +pub fn playerctl_next(target: &str) -> Result<()> { + let player = get_playerctl_player(target)?; + + Command::new("playerctl") + .args(vec!["-p", &player, "next"]) + .spawn()?; + + Ok(()) +} + +pub fn playerctl_previous(target: &str) -> Result<()> { + let player = get_playerctl_player(target)?; + + Command::new("playerctl") + .args(vec!["-p", &player, "previous"]) + .spawn()?; + + Ok(()) +} diff --git a/src/utils.rs b/src/utils.rs index 61b7939..e92d761 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,16 @@ -use anyhow::anyhow; +use std::process::Command; + +use anyhow::{anyhow, Result}; use pulse::{context::introspect::SinkInputInfo, volume}; +#[link(name = "c")] +extern "C" { + /// Gets the current user's ID + pub fn getuid() -> u32; +} + +const NOTIFY_SEND_REPLACE_ID: u32 = 1448531; +const NOTIFICATION_DURATION_MILLIS: u32 = 1000; const FULL_VOLUME: u32 = 1 << 16; pub fn volume_to_percentage(volume: volume::ChannelVolumes) -> u8 { @@ -22,7 +32,38 @@ pub fn get_sink_input_name(sink_input: &SinkInputInfo) -> anyhow::Result return Err(anyhow!("Invalid sink input name")); }; - Ok(String::from_utf8( + Ok(capitalize_string(&String::from_utf8( name_bytes[..name_bytes.len() - 1].to_vec(), - )?) + )?)) +} + +fn capitalize_string(s: &str) -> String { + let mut c = s.chars(); + match c.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + c.as_str(), + } +} + +pub fn send_notification(message: &str) -> Result<()> { + let user_id = unsafe { getuid() }; + + Command::new("notify-send") + .args(vec![ + "Mixrs", + message, + "-t", + &NOTIFICATION_DURATION_MILLIS.to_string(), + "-r", + &NOTIFY_SEND_REPLACE_ID.to_string(), + "-i", + "/", + ]) + .env( + "DBUS_SESSION_BUS_ADDRESS", + format!("unix:path=/run/user/{user_id}/bus"), + ) + .spawn()?; + + Ok(()) }