Add sink input volume manipulation

This commit is contained in:
GHOSCHT 2024-04-30 23:44:39 +02:00
parent 12b8a4c93b
commit 0a663e97f2
Signed by: ghoscht
GPG key ID: 2C2C1C62A5388E82

View file

@ -3,10 +3,11 @@ extern crate libpulse_binding as pulse;
use pulse::context::{Context, FlagSet as ContextFlagSet}; use pulse::context::{Context, FlagSet as ContextFlagSet};
use pulse::mainloop::threaded::Mainloop; use pulse::mainloop::threaded::Mainloop;
use pulse::proplist::Proplist; use pulse::proplist::Proplist;
use pulse::volume;
use regex::Regex; use regex::Regex;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::sync::Arc;
use std::time; use std::time;
pub fn run() { pub fn run() {
@ -18,11 +19,11 @@ pub fn run() {
) )
.unwrap(); .unwrap();
let mainloop = Rc::new(RefCell::new( let mainloop = Arc::new(RefCell::new(
Mainloop::new().expect("Failed to create mainloop"), Mainloop::new().expect("Failed to create mainloop"),
)); ));
let context = Rc::new(RefCell::new( let context = Arc::new(RefCell::new(
Context::new_with_proplist( Context::new_with_proplist(
mainloop.borrow().deref(), mainloop.borrow().deref(),
"picoKontrollerContext", "picoKontrollerContext",
@ -33,8 +34,8 @@ pub fn run() {
// Context state change callback // Context state change callback
{ {
let ml_ref = Rc::clone(&mainloop); let ml_ref = Arc::clone(&mainloop);
let context_ref = Rc::clone(&context); let context_ref = Arc::clone(&context);
context context
.borrow_mut() .borrow_mut()
.set_state_callback(Some(Box::new(move || { .set_state_callback(Some(Box::new(move || {
@ -82,19 +83,21 @@ pub fn run() {
mainloop.borrow_mut().unlock(); mainloop.borrow_mut().unlock();
//--------------------- //---------------------
let ml = Rc::clone(&mainloop); let ml = Arc::clone(&mainloop);
let ctx = Rc::clone(&context); let ctx = Arc::clone(&context);
mute_sink_input(ml, ctx, "Firefox", false);
set_volume_sink_input(ml, ctx, "Firefox", 100);
//--------------------- //---------------------
loop { loop {
//TODO: react to incoming commands here instead of infinite sleep
std::thread::sleep(time::Duration::from_secs(u64::MAX)); std::thread::sleep(time::Duration::from_secs(u64::MAX));
} }
} }
pub fn set_default_sink( pub fn set_default_sink(
mainloop: Rc<RefCell<Mainloop>>, mainloop: Arc<RefCell<Mainloop>>,
context: Rc<RefCell<Context>>, context: Arc<RefCell<Context>>,
name: &str, name: &str,
) { ) {
mainloop.borrow_mut().lock(); mainloop.borrow_mut().lock();
@ -117,11 +120,11 @@ pub fn set_default_sink(
mainloop.borrow_mut().unlock(); mainloop.borrow_mut().unlock();
} }
pub fn mute_sink_input( fn for_all_sink_inputs(
mainloop: Rc<RefCell<Mainloop>>, mainloop: Arc<RefCell<Mainloop>>,
context: Rc<RefCell<Context>>, context: Arc<RefCell<Context>>,
name: &str, name: &str,
state: bool, function: Arc<dyn Fn(Arc<RefCell<Context>>, u32, &mut volume::ChannelVolumes)>,
) { ) {
mainloop.borrow_mut().lock(); mainloop.borrow_mut().lock();
let sink_filter = Regex::new(name).expect("Creating RegEx failed"); let sink_filter = Regex::new(name).expect("Creating RegEx failed");
@ -139,14 +142,73 @@ pub fn mute_sink_input(
println!("{:?}", application_name); println!("{:?}", application_name);
if matches_regex { if matches_regex {
println!("Setting default sink to: {}", application_name); function(
callback_context callback_context.clone(),
.borrow_mut() item.index,
.introspect() &mut item.volume.clone(),
.set_sink_input_mute(item.index, state, Option::None); );
} }
} }
_ => {} _ => {}
}); });
mainloop.borrow_mut().unlock(); mainloop.borrow_mut().unlock();
} }
pub fn mute_sink_input(
mainloop: Arc<RefCell<Mainloop>>,
context: Arc<RefCell<Context>>,
name: &str,
state: bool,
) {
for_all_sink_inputs(
mainloop,
context,
name,
Arc::new(move |ctx, index, _| {
ctx.borrow_mut()
.introspect()
.set_sink_input_mute(index, state, Option::None);
}),
)
}
pub fn set_volume_sink_input(
mainloop: Arc<RefCell<Mainloop>>,
context: Arc<RefCell<Context>>,
name: &str,
volume: i16,
) {
for_all_sink_inputs(
mainloop,
context,
name,
Arc::new(move |ctx, index, vols| {
let target = percent_to_volume(volume);
for v in vols.get_mut() {
v.0 = target;
}
ctx.borrow_mut()
.introspect()
.set_sink_input_volume(index, vols, Option::None);
}),
)
}
//taken from https://github.com/jantap/rsmixer
pub fn percent_to_volume(target_percent: i16) -> u32 {
let base_delta = (volume::Volume::NORMAL.0 as f32 - volume::Volume::MUTED.0 as f32) / 100.0;
if target_percent < 0 {
volume::Volume::MUTED.0
} else if target_percent == 100 {
volume::Volume::NORMAL.0
} else if target_percent >= 150 {
(volume::Volume::NORMAL.0 as f32 * 1.5) as u32
} else if target_percent < 100 {
volume::Volume::MUTED.0 + target_percent as u32 * base_delta as u32
} else {
volume::Volume::NORMAL.0 + (target_percent - 100) as u32 * base_delta as u32
}
}