Enable basic SinkInput midi control

This commit is contained in:
GHOSCHT 2024-05-01 16:12:32 +02:00
parent 0a663e97f2
commit 6617652839
Signed by: ghoscht
GPG key ID: 2C2C1C62A5388E82
3 changed files with 73 additions and 24 deletions

View file

@ -104,6 +104,19 @@ pub enum MprisAction {
Volume, Volume,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum PulseAction {
SinkVolume,
SinkMute,
SinkInputVolume,
SinkInputMute,
SourceVolume,
SourceMute,
SourceOutputVolume,
SourceOutputMute,
DefaultSink,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum KeyMapVariant { pub enum KeyMapVariant {
@ -113,6 +126,7 @@ pub enum KeyMapVariant {
}, },
PulseAudio { PulseAudio {
ids: Vec<String>, ids: Vec<String>,
action: PulseAction,
}, },
Exec { Exec {
command: String, command: String,

View file

@ -1,25 +1,28 @@
use crate::config::NanoKeys; use crate::config::NanoKeys;
use crate::config::{Config, KeyMapVariant, MprisAction}; use crate::config::{Config, KeyMapVariant, MprisAction, PulseAction};
use crate::midi_client::KeyEvent; use crate::midi_client::KeyEvent;
use crate::mpris_client; use crate::mpris_client;
use crossbeam_channel::Receiver; use crate::pulse_client::{self, PulseMessage};
use crossbeam_channel::{unbounded, Receiver, Sender};
use log::{error, info, warn}; use log::{error, info, warn};
use std::process::{Command, Output}; use std::process::{Command, Output};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
pub fn run(in_channel: Receiver<KeyEvent>, config: Arc<Config>) { pub fn run(in_channel: Receiver<KeyEvent>, config: Arc<Config>) {
let (sender, receiver) = unbounded::<PulseMessage>();
pulse_client::run(receiver);
thread::spawn(move || { thread::spawn(move || {
exec(in_channel, config); exec(in_channel, sender, config);
}); });
} }
fn exec(in_channel: Receiver<KeyEvent>, config: Arc<Config>) { fn exec(in_channel: Receiver<KeyEvent>, pulse_channel: Sender<PulseMessage>, config: Arc<Config>) {
loop { loop {
let event_in = in_channel.recv(); let event_in = in_channel.recv();
match event_in { match event_in {
Ok((key, state)) => { Ok((key, state)) => {
eval(key, state, &config); eval(key, state, &config, &pulse_channel);
} }
Err(err) => { Err(err) => {
error!("Failed receiving event in controller thread: {err}"); error!("Failed receiving event in controller thread: {err}");
@ -41,7 +44,7 @@ where
} }
} }
fn eval(key: NanoKeys, state: u8, config: &Arc<Config>) { fn eval(key: NanoKeys, state: u8, config: &Arc<Config>, pulse_channel: &Sender<PulseMessage>) {
match config.keymap.get(&key) { match config.keymap.get(&key) {
Some(actions) => { Some(actions) => {
info!( info!(
@ -51,7 +54,10 @@ fn eval(key: NanoKeys, state: u8, config: &Arc<Config>) {
for action in actions { for action in actions {
match action { match action {
KeyMapVariant::Mpris { ids, action } => parse_mpris_action(state, ids, action), KeyMapVariant::Mpris { ids, action } => parse_mpris_action(state, ids, action),
KeyMapVariant::PulseAudio { ids } => {} KeyMapVariant::PulseAudio { ids, action } => {
//TODO: maybe clean up?
let _ = pulse_channel.send((Arc::new(ids.to_vec()), action.clone(), state));
}
KeyMapVariant::Exec { command, args } => parse_exec_action(command, args), KeyMapVariant::Exec { command, args } => parse_exec_action(command, args),
} }
} }

View file

@ -1,5 +1,8 @@
extern crate libpulse_binding as pulse; extern crate libpulse_binding as pulse;
use crate::config::{Config, KeyMapVariant, PulseAction};
use crossbeam_channel::Receiver;
use log::{error, info, warn};
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;
@ -8,9 +11,16 @@ use regex::Regex;
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::time; use std::thread;
pub fn run() { pub type PulseMessage = (Arc<Vec<String>>, PulseAction, u8);
pub fn run(in_channel: Receiver<PulseMessage>) {
thread::spawn(move || {
exec(in_channel);
});
}
pub fn exec(in_channel: Receiver<PulseMessage>) {
let mut proplist = Proplist::new().unwrap(); let mut proplist = Proplist::new().unwrap();
proplist proplist
.set_str( .set_str(
@ -82,16 +92,38 @@ pub fn run() {
context.borrow_mut().set_state_callback(None); context.borrow_mut().set_state_callback(None);
mainloop.borrow_mut().unlock(); mainloop.borrow_mut().unlock();
//---------------------
let ml = Arc::clone(&mainloop);
let ctx = Arc::clone(&context);
set_volume_sink_input(ml, ctx, "Firefox", 100);
//---------------------
loop { loop {
//TODO: react to incoming commands here instead of infinite sleep let ml = Arc::clone(&mainloop);
std::thread::sleep(time::Duration::from_secs(u64::MAX)); let ctx = Arc::clone(&context);
let event_in = in_channel.recv();
match event_in {
Ok((ids, action, state)) => {
parse_pulse_action(state, ids, &action, ml, ctx);
}
Err(err) => {
error!("Failed receiving event in controller thread: {err}");
}
}
}
}
fn parse_pulse_action(
state: u8,
ids: Arc<Vec<String>>,
action: &PulseAction,
mainloop: Arc<RefCell<Mainloop>>,
context: Arc<RefCell<Context>>,
) {
match action {
PulseAction::SinkInputVolume => {
for id in ids.iter() {
set_volume_sink_input(mainloop.clone(), context.clone(), id, state);
}
}
PulseAction::DefaultSink => {
set_default_sink(mainloop.clone(), context.clone(), ids.get(0).unwrap());
}
_ => {}
} }
} }
@ -140,7 +172,6 @@ fn for_all_sink_inputs(
.unwrap(); .unwrap();
let matches_regex = sink_filter.is_match(&application_name); let matches_regex = sink_filter.is_match(&application_name);
println!("{:?}", application_name);
if matches_regex { if matches_regex {
function( function(
callback_context.clone(), callback_context.clone(),
@ -176,7 +207,7 @@ pub fn set_volume_sink_input(
mainloop: Arc<RefCell<Mainloop>>, mainloop: Arc<RefCell<Mainloop>>,
context: Arc<RefCell<Context>>, context: Arc<RefCell<Context>>,
name: &str, name: &str,
volume: i16, volume: u8,
) { ) {
for_all_sink_inputs( for_all_sink_inputs(
mainloop, mainloop,
@ -197,12 +228,10 @@ pub fn set_volume_sink_input(
} }
//taken from https://github.com/jantap/rsmixer //taken from https://github.com/jantap/rsmixer
pub fn percent_to_volume(target_percent: i16) -> u32 { pub fn percent_to_volume(target_percent: u8) -> u32 {
let base_delta = (volume::Volume::NORMAL.0 as f32 - volume::Volume::MUTED.0 as f32) / 100.0; let base_delta = (volume::Volume::NORMAL.0 as f32 - volume::Volume::MUTED.0 as f32) / 100.0;
if target_percent < 0 { if target_percent == 100 {
volume::Volume::MUTED.0
} else if target_percent == 100 {
volume::Volume::NORMAL.0 volume::Volume::NORMAL.0
} else if target_percent >= 150 { } else if target_percent >= 150 {
(volume::Volume::NORMAL.0 as f32 * 1.5) as u32 (volume::Volume::NORMAL.0 as f32 * 1.5) as u32