aboutsummaryrefslogtreecommitdiff
path: root/pulse/src/runnable/pulse.rs
diff options
context:
space:
mode:
authorAndreas Grois <andi@grois.info>2021-06-04 21:48:01 +0200
committerAndreas Grois <andi@grois.info>2021-06-04 21:48:01 +0200
commit15ae48210535358ad90dc85c4bc8809a71627d1d (patch)
treeb62e55887cce7ec7d90d12952b9968630e4565ba /pulse/src/runnable/pulse.rs
parent6457d779ec253ade253fc49f5891b5479b270a6d (diff)
Pulse now displays volume correctly.
Still missing actual functionality beyond a numeric display, and error cases have not been tested, but hey, it's there!
Diffstat (limited to 'pulse/src/runnable/pulse.rs')
-rw-r--r--pulse/src/runnable/pulse.rs290
1 files changed, 283 insertions, 7 deletions
diff --git a/pulse/src/runnable/pulse.rs b/pulse/src/runnable/pulse.rs
index 64a7d33..d92a41f 100644
--- a/pulse/src/runnable/pulse.rs
+++ b/pulse/src/runnable/pulse.rs
@@ -2,9 +2,8 @@
//! It is the only place in this plugin that explicitly includes unsafe code.
use std::sync::{Arc, Weak};
-use std::ffi::{c_void, CString};
-use libc::{c_int, size_t};
-use std::os::raw::c_char;
+use std::ffi::{CString, CStr};
+use std::os::raw::{c_int, c_char};
use std::convert::TryFrom;
use std::marker::PhantomData;
@@ -38,11 +37,12 @@ impl Pulse {
pub(super) struct PulseContext<'c> {
context : *mut PaContext, //Can actually never be null, the nullptr case is handled by create, which returns an Err instead of a PulseContext then.
+ scratch : &'c mut ContextScratch, //This scratch space is needed, because user data pointers in pulse need to survive between iterations. Yes, that's insane. No, I can't do anything about it...
main : &'c Pulse
}
impl<'c> PulseContext<'c> {
- pub(super) fn create<'m>(pulse : &'m Pulse) -> Result<PulseContext<'m>, PulseContextCreationError> {
+ pub(super) fn create<'m>(pulse : &'m Pulse, scratch : &'m mut ContextScratch) -> Result<PulseContext<'m>, PulseContextCreationError> {
let api = pulse.main_loop.get_api();
if api.is_null() {
Err(PulseContextCreationError::FailedToGetPulseApi)
@@ -54,10 +54,102 @@ impl<'c> PulseContext<'c> {
Err(PulseContextCreationError::ContextNewFailed)
}
else {
- Ok(PulseContext { context, main : pulse})
+ Ok(PulseContext { context, scratch, main : pulse})
}
}
}
+ pub(super) fn iterate(&mut self, relevant_sink : &Option<SinkHandle>) -> Result<IterationResult,MainLoopIterationError> {
+ self.scratch.sink_we_care_about = relevant_sink.clone();
+ self.scratch.volume = None;
+ self.scratch.default_sink = None;
+ self.main.main_loop.iterate()?;
+ return Ok(IterationResult {
+ default_sink : self.scratch.default_sink.clone(),
+ volume : self.scratch.volume.clone()
+ });
+ }
+ pub(super) fn get_state(&self) -> PaContextState {
+ unsafe { pa_context_get_state(self.context) }
+ }
+ pub(super) fn connect_and_set_callbacks(&mut self) -> Result<(), PulseContextConnectError> {
+ unsafe {
+ pa_context_set_state_callback(self.context,Some(Self::on_context_state_change),self.scratch);
+ let connect_result = pa_context_connect(self.context,std::ptr::null(),PaContextFlags::NoAutoSpawnNoFail,std::ptr::null());
+ if connect_result == 0 {
+ Ok(())
+ }
+ else {
+ Err(PulseContextConnectError(connect_result))
+ }
+ }
+ }
+
+ pub(super) fn refresh_default_sink(&mut self) {
+ unsafe {pa_operation_unref(pa_context_get_server_info(self.context,Some(Self::on_server_info_received),self.scratch)); }
+ }
+
+ pub(super) fn refresh_volume(&mut self, sink : &SinkHandle) {
+ unsafe {pa_operation_unref(pa_context_get_sink_info_by_name(self.context,sink.sink.as_ptr(),Some(Self::on_sink_info_received),self.scratch));}
+ }
+
+ fn on_context_state_change(context : *mut PaContext, scratch : *mut ContextScratch) {
+ unsafe {
+ match pa_context_get_state(context) {
+ PaContextState::Ready => {
+ pa_context_set_subscribe_callback(context,Some(Self::on_context_event),scratch);
+ pa_operation_unref(pa_context_subscribe(context,0x80 /* SERVER */ | 0x01 /* SINK */, None, std::ptr::null_mut()));
+ }
+ _ => {}
+ }
+ }
+ }
+
+ fn on_context_event(context : *mut PaContext,event_type : c_int,index: u32,scratch : *mut ContextScratch) {
+ assert!(!context.is_null());
+ assert!(!scratch.is_null());
+ unsafe {
+ let facility = 0x000f & event_type;
+ match facility {
+ 0x0 /* SINK */ => {
+ pa_operation_unref(pa_context_get_sink_info_by_index(context,index,Some(Self::on_sink_info_received),scratch));
+ }
+ 0x7 /* SERVER */ => {
+ pa_operation_unref(pa_context_get_server_info(context,Some(Self::on_server_info_received),scratch));
+ }
+ _ /* should not happen */ => {
+ assert!(false);
+ }
+ }
+ }
+ }
+
+ fn on_sink_info_received(_context : *mut PaContext, sink_info : *const PaSinkInfo, _eol : c_int, scratch : *mut ContextScratch) {
+ if sink_info.is_null() {
+ return;
+ }
+ unsafe {
+ if let Some(s) = &(*scratch).sink_we_care_about {
+ if s.sink.as_c_str() == CStr::from_ptr((*sink_info).sink_name) {
+ let avg_volume = pa_cvolume_avg(&(*sink_info).volume);
+ const norm : u32 = 0x10000;
+ let volume = (avg_volume as f32) / (norm as f32);
+ let balance = pa_cvolume_get_balance(&(*sink_info).volume, &(*sink_info).channel_map);
+ let muted = (*sink_info).mute != 0;
+
+ (*scratch).volume = Some(Volume {volume, balance, muted});
+ }
+ }
+ }
+ }
+
+ fn on_server_info_received(_context : *mut PaContext, server_info : *const PaServerInfo, scratch : *mut ContextScratch) {
+ if server_info.is_null() {
+ return;
+ }
+ unsafe {
+ (*scratch).default_sink= Some(SinkHandle::from(CStr::from_ptr((*server_info).default_sink_name)));
+ }
+ }
}
impl<'c> Drop for PulseContext<'c> {
@@ -67,6 +159,46 @@ impl<'c> Drop for PulseContext<'c> {
}
#[derive(Debug)]
+pub struct PulseContextConnectError(i32);
+impl std::fmt::Display for PulseContextConnectError {
+ fn fmt(&self, f : &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "Pulse Context connect error: ")?;
+ match self.0 {
+ 0 => { write!(f,"No error occured.") }
+ 1 => { write!(f,"Access failure") }
+ 2 => { write!(f,"Unknown command") }
+ 3 => { write!(f,"Invalid argument") }
+ 4 => { write!(f,"Entity exists") }
+ 5 => { write!(f,"No such entity") }
+ 6 => { write!(f,"Connection refused") }
+ 7 => { write!(f,"Protocol error") }
+ 8 => { write!(f,"Timeout") }
+ 9 => { write!(f,"No authentication key") }
+ 10 => { write!(f,"Internal error") }
+ 11 => { write!(f,"Connection terminated") }
+ 12 => { write!(f,"Entity killed") }
+ 13 => { write!(f,"Invalid server") }
+ 14 => { write!(f,"Module initialization failed") }
+ 15 => { write!(f,"Bad state") }
+ 16 => { write!(f,"No data") }
+ 17 => { write!(f,"Incompatible protocol version") }
+ 18 => { write!(f,"Data too large") }
+ 19 => { write!(f,"Operation not supported") }
+ 20 => { write!(f,"The error code was unknown to the client") }
+ 21 => { write!(f,"Extension does not exist.") }
+ 22 => { write!(f,"Obsolete functionality.") }
+ 23 => { write!(f,"Missing implementation.") }
+ 24 => { write!(f,"The caller forked without calling execve() and tried to reuse the context.") }
+ 25 => { write!(f,"An IO error happened.") }
+ 26 => { write!(f,"Device or resource busy.") }
+ _ => { write!(f,"Unknown error. Not documented at time of writing.") }
+ }
+ }
+}
+impl std::error::Error for PulseContextConnectError {}
+
+
+#[derive(Debug)]
pub enum PulseContextCreationError {
SettingNameFailed,
FailedToGetPulseApi,
@@ -144,6 +276,15 @@ impl PulseMainLoop {
fn get_api(&self) -> *mut PaMainloopApi {
unsafe { pa_mainloop_get_api(self.main_loop) }
}
+ fn iterate(&self) -> Result<(),MainLoopIterationError> {
+ let i = unsafe { pa_mainloop_iterate(self.main_loop, 1, std::ptr::null_mut()) };
+ if i >= 0 {
+ Ok(())
+ }
+ else {
+ Err(MainLoopIterationError(i))
+ }
+ }
}
impl Drop for PulseMainLoop {
fn drop(&mut self) {
@@ -156,6 +297,15 @@ impl Drop for PulseMainLoop {
}
#[derive(Debug)]
+pub struct MainLoopIterationError(i32);
+impl std::fmt::Display for MainLoopIterationError {
+ fn fmt(&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
+ write!(f, "Failed to iterate pulseaudio main loop. Error {}", self.0)
+ }
+}
+impl std::error::Error for MainLoopIterationError {}
+
+#[derive(Debug)]
pub struct MainLoopCreationError {}
impl std::fmt::Display for MainLoopCreationError {
fn fmt(&self, f : &mut std::fmt::Formatter) -> std::fmt::Result {
@@ -164,6 +314,7 @@ impl std::fmt::Display for MainLoopCreationError {
}
impl std::error::Error for MainLoopCreationError {}
+#[derive(Clone, PartialEq, Eq, Debug)]
pub(super) struct SinkHandle {
sink: CString
}
@@ -178,15 +329,41 @@ impl TryFrom<&str> for SinkHandle {
}
}
+impl From<&CStr> for SinkHandle {
+ fn from(s : &CStr) -> SinkHandle {
+ SinkHandle { sink : CString::from(s) }
+ }
+}
+
pub(super) struct IterationResult {
pub default_sink : Option<SinkHandle>,
pub volume : Option<Volume>,
- pub state : Option<PaContextState>
}
+#[derive(Clone, PartialEq)]
pub(super) struct Volume {
pub volume : f32,
- pub balance : f32
+ pub balance : f32,
+ pub muted : bool
+}
+
+pub(super) struct ContextScratch {
+ /* output */
+ volume : Option<Volume>,
+ default_sink : Option<SinkHandle>,
+
+ /* input */
+ sink_we_care_about : Option<SinkHandle>
+}
+
+impl Default for ContextScratch {
+ fn default() -> Self {
+ ContextScratch {
+ volume : None,
+ default_sink : None,
+ sink_we_care_about : None
+ }
+ }
}
#[repr(C)] pub(super) enum PaContextState {
@@ -199,21 +376,120 @@ pub(super) struct Volume {
Terminated
}
+#[repr(C)] enum PaContextFlags {
+ NoFlags = 0,
+ NoAutoSpawn = 1,
+ NoFail = 2,
+ NoAutoSpawnNoFail = 3
+}
+
+#[repr(C)] struct PaSinkInfo {
+ sink_name : *const c_char,
+ index : u32,
+ description : *const c_char,
+ sample_spec : PaSampleSpec,
+ channel_map : PaChannelMap,
+ owner_module : u32,
+ volume : PaCVolume,
+ mute : c_int,
+ monitor_source : u32,
+ monitor_source_name : *const c_char,
+ latency : u64,
+ driver : *const c_char,
+ flags : c_int,
+ proplist : *mut PaPropList,
+ configured_latency : u64,
+ base_volume : u32,
+ state : c_int,
+ n_volume_steps : u32,
+ card : u32,
+ n_ports : u32,
+ ports : *mut *mut PaSinkPortInfo,
+ active_port : *mut PaSinkPortInfo,
+ n_formats : u8,
+ formats : *mut *mut PaFormatInfo
+}
+
+#[repr(C)] struct PaSampleSpec {
+ format : c_int,
+ rate : u32,
+ channels : u8
+}
+
+#[repr(C)] struct PaChannelMap {
+ channels : u8,
+ map : [c_int; 32]
+}
+
+#[repr(C)] struct PaCVolume {
+ channels : u8,
+ values : [u32; 32]
+}
+
+#[repr(C)] struct PaServerInfo {
+ user_name : *const c_char,
+ host_name : *const c_char,
+ server_version : *const c_char,
+ server_name : *const c_char,
+ sample_spec : PaSampleSpec,
+ default_sink_name : *const c_char,
+ default_source_name : *const c_char,
+ cookie : u32,
+ channel_map : PaChannelMap
+}
+
+#[repr(C)] struct PaPropList { _private: [u8; 0]}
+
+///This actually holds meaningful data, but we don't care about it for now.
+#[repr(C)] struct PaSinkPortInfo { _private: [u8; 0]}
+
+///Similar to PaSinkPortInfo, we could interpret this, but won't need to.
+#[repr(C)] struct PaFormatInfo { _private: [u8;0]}
+
#[repr(C)] struct PaMainloop { _private: [u8; 0] }
#[repr(C)] struct PaContext { _private: [u8; 0] }
+#[repr(C)] struct PaOperation { _private: [u8; 0] }
///While we could in theory wrap the whole API, there's no need for it. We can treat it as an
///opaque type, because we never call any functions on it.
#[repr(C)] struct PaMainloopApi { _private: [u8; 0] }
+///If we were to allow auto-spawning, we would need to actually implement this...
+#[repr(C)] struct PaSpawnApi { _private: [u8; 0] }
+
+type PaContextSuccessCb = fn(*mut PaContext, c_int, *mut ContextScratch);
+type PaContextSubscribeCb = fn(*mut PaContext,c_int,u32,*mut ContextScratch);
+type PaContextStateCb = fn(*mut PaContext, *mut ContextScratch);
+type PaSinkInfoCb = fn(*mut PaContext, *const PaSinkInfo, c_int, *mut ContextScratch);
+type PaServerInfoCb = fn(*mut PaContext, *const PaServerInfo, *mut ContextScratch);
+
#[link(name = "pulse")]
extern {
+ #[must_use]
fn pa_mainloop_new() -> *mut PaMainloop;
fn pa_mainloop_wakeup(_ : *mut PaMainloop);
fn pa_mainloop_quit(_ : *mut PaMainloop, retval : c_int);
fn pa_mainloop_free(_ : *mut PaMainloop);
+ fn pa_mainloop_iterate(_ : *mut PaMainloop, block : c_int, return_value : *mut c_int) -> c_int;
fn pa_mainloop_get_api(_ : *mut PaMainloop) -> *mut PaMainloopApi;
+ #[must_use]
fn pa_context_new(_ : *mut PaMainloopApi, name :*const c_char) -> *mut PaContext;
fn pa_context_unref(_ : *mut PaContext);
+ fn pa_context_get_state(_ : *mut PaContext) -> PaContextState;
+ fn pa_context_connect(_: *mut PaContext, server : *const c_char, flags : PaContextFlags, api : *const PaSpawnApi) -> c_int;
+ fn pa_context_set_state_callback(_: *mut PaContext, callback: Option<PaContextStateCb>, scratch : *mut ContextScratch);
+ fn pa_context_set_subscribe_callback(_: *mut PaContext,callback : Option<PaContextSubscribeCb>,scratch : *mut ContextScratch);
+ #[must_use]
+ fn pa_context_subscribe(_: *mut PaContext, subscription_mask : c_int, callback : Option<PaContextSuccessCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ fn pa_operation_unref(operation : *mut PaOperation);
+
+ #[must_use]
+ fn pa_context_get_sink_info_by_index(_: *mut PaContext, sink_index : u32, callback : Option<PaSinkInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ fn pa_context_get_sink_info_by_name(_: *mut PaContext, sink : *const c_char, callback : Option<PaSinkInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+ #[must_use]
+ fn pa_context_get_server_info(_: *mut PaContext, callback : Option<PaServerInfoCb>, scratch : *mut ContextScratch) -> *mut PaOperation;
+
+ fn pa_cvolume_avg(volume : *const PaCVolume) -> u32;
+ fn pa_cvolume_get_balance(volume : *const PaCVolume, channel_map : *const PaChannelMap) -> f32;
}