1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
|
//! This module encapsulates all interaction with pulseaudio.
//! It is the only place in this plugin that explicitly includes unsafe code.
use std::sync::{Arc, Weak};
use std::ffi::{CString, CStr, c_void};
use std::os::raw::{c_int, c_char};
use std::convert::TryFrom;
use std::marker::PhantomData;
pub(super) struct Pulse {
main_loop : Arc<PulseMainLoop>, //Beware: Never ever clone this except for PulseWakeUp!
//Make sure that Pulse never accidentally gets Sync. That's more of a reminder for myself,
//given that Arc<PulseMainLoop> already is not Sync...
_marker : PhantomData<std::cell::RefCell<i8>>
}
//In general it's a really stupid idea to Send Arcs of non-Sync types around.
//However this Arc is only cloned under one very specific condition: creating a PulseWakeUp.
//Apart from that one (thread-safe) exception it's actually single-ownership.
unsafe impl Send for Pulse {}
impl Pulse {
pub fn create() -> Result<Self,MainLoopCreationError> {
let main_loop = Arc::new(PulseMainLoop::new()?);
Ok(Pulse {
main_loop,
_marker :PhantomData
})
}
pub fn get_wake_up(&self) -> PulseWakeUp {
PulseWakeUp { main_loop : Arc::downgrade(&self.main_loop) }
}
}
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, scratch : &'m mut ContextScratch) -> Result<PulseContext<'m>, PulseContextCreationError> {
let api = pulse.main_loop.get_api();
if api.is_null() {
Err(PulseContextCreationError::FailedToGetPulseApi)
}
else {
if let Ok(plugin_name) = CString::new("Swaystatus Pulse Plugin") {
let context = unsafe { pa_context_new(api, plugin_name.as_ptr()) };
if context.is_null() {
Err(PulseContextCreationError::ContextNewFailed)
}
else {
Ok(PulseContext { context, scratch, main : pulse})
}
}
else {
Err(PulseContextCreationError::SettingNameFailed)
}
}
}
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 as *mut ContextScratch as *mut c_void);
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 as *mut ContextScratch as *mut c_void)); }
}
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 as *mut ContextScratch as *mut c_void));}
}
extern "C" fn on_context_state_change(context : *mut PaContext, scratch : *mut c_void) {
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()));
}
_ => {}
}
}
}
extern "C" fn on_context_event(context : *mut PaContext,event_type : c_int,index: u32,scratch : *mut c_void) {
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 as *mut c_void));
}
0x7 /* SERVER */ => {
pa_operation_unref(pa_context_get_server_info(context,Some(Self::on_server_info_received),scratch as *mut c_void));
}
_ /* should not happen */ => {
assert!(false);
}
}
}
}
extern "C" fn on_sink_info_received(_context : *mut PaContext, sink_info : *const PaSinkInfo, _eol : c_int, scratch_void : *mut c_void) {
if sink_info.is_null() {
return;
}
unsafe {
let scratch = scratch_void as *mut ContextScratch;
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});
}
}
}
}
extern "C" fn on_server_info_received(_context : *mut PaContext, server_info : *const PaServerInfo, scratch : *mut c_void) {
if server_info.is_null() {
return;
}
unsafe {
(*(scratch as *mut ContextScratch)).default_sink= Some(SinkHandle::from(CStr::from_ptr((*server_info).default_sink_name)));
}
}
}
impl<'c> Drop for PulseContext<'c> {
fn drop(&mut self) {
unsafe {pa_context_unref(self.context)}
}
}
#[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,
ContextNewFailed
}
impl std::fmt::Display for PulseContextCreationError {
fn fmt(&self, f : &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PulseContextCreationError::SettingNameFailed => {
write!(f, "Pulse Context creation failed, error converting context name to C chars")
}
PulseContextCreationError::FailedToGetPulseApi => {
write!(f, "Pulse Context creation failed, Pulse Main Loop didn't return a valid API")
}
PulseContextCreationError::ContextNewFailed => {
write!(f, "Pulse Context creation failed, Pulse API didn't return a valid context")
}
}
}
}
impl std::error::Error for PulseContextCreationError {}
/// Helper to wake up the Pulseaudio main loop.
/// This implements Send and Sync, because it's explicitly meant to allow cross-thread
/// communication. It only exposes the wake up function of pulse, and that function is in itself
/// send and sync (https://github.com/pulseaudio/pulseaudio/blob/master/src/pulse/mainloop.c).
pub struct PulseWakeUp {
main_loop : Weak<PulseMainLoop>
}
unsafe impl Send for PulseWakeUp {}
unsafe impl Sync for PulseWakeUp {}
impl PulseWakeUp {
pub fn wake_up(&self) -> Result<(), PulseWakeUpError> {
match self.main_loop.upgrade() {
Some(main_loop) => {
main_loop.awaken();
Ok(())
}
None => {
Err(PulseWakeUpError {} )
}
}
}
}
#[derive(Debug)]
pub struct PulseWakeUpError;
impl std::error::Error for PulseWakeUpError {}
impl std::fmt::Display for PulseWakeUpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f,"Failed to wake up Pulse main loop. It has been dropped already.")
}
}
struct PulseMainLoop {
main_loop : *mut PaMainloop,
}
impl PulseMainLoop {
//this is intentionally all private. Nobody outside this module should call anything on this.
fn awaken(&self) {
unsafe { pa_mainloop_wakeup(self.main_loop); }
}
fn new() -> Result<Self,MainLoopCreationError> {
unsafe {
let pointer = pa_mainloop_new();
if pointer.is_null() {
Err(MainLoopCreationError{})
}
else {
Ok(Self { main_loop : pointer })
}
}
}
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) {
unsafe {
pa_mainloop_quit(self.main_loop, 0);
pa_mainloop_free(self.main_loop);
}
self.main_loop = std::ptr::null_mut();
}
}
#[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 {
write!(f, "Failed to create Pulse Main loop, PulseAudio returned a null pointer.")
}
}
impl std::error::Error for MainLoopCreationError {}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(super) struct SinkHandle {
sink: CString
}
impl TryFrom<&str> for SinkHandle {
type Error = std::ffi::NulError;
fn try_from(s : &str) -> Result<Self, Self::Error> {
let converted = std::ffi::CString::new(s)?;
Ok(SinkHandle {
sink : converted
})
}
}
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>,
}
#[derive(Clone, PartialEq)]
pub(super) struct Volume {
pub volume : 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
}
}
}
#[allow(dead_code)] //this is not dead code, but it seems the compiler doesn't understand the usage in FFI...
#[repr(C)] pub(super) enum PaContextState {
Unconnected,
Connecting,
Authorizing,
SettingName,
Ready,
Failed,
Terminated
}
#[allow(dead_code)] //this is partially dead code, but the unused enum values are kept for readability reasons.
#[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 = extern "C" fn(*mut PaContext, c_int, *mut c_void);
type PaContextSubscribeCb = extern "C" fn(*mut PaContext,c_int,u32,*mut c_void);
type PaContextStateCb = extern "C" fn(*mut PaContext, *mut c_void);
type PaSinkInfoCb = extern "C" fn(*mut PaContext, *const PaSinkInfo, c_int, *mut c_void);
type PaServerInfoCb = extern "C" fn(*mut PaContext, *const PaServerInfo, *mut c_void);
#[link(name = "pulse")]
extern "C" {
#[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 c_void);
fn pa_context_set_subscribe_callback(_: *mut PaContext,callback : Option<PaContextSubscribeCb>,scratch : *mut c_void);
#[must_use]
fn pa_context_subscribe(_: *mut PaContext, subscription_mask : c_int, callback : Option<PaContextSuccessCb>, scratch : *mut c_void) -> *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 c_void) -> *mut PaOperation;
fn pa_context_get_sink_info_by_name(_: *mut PaContext, sink : *const c_char, callback : Option<PaSinkInfoCb>, scratch : *mut c_void) -> *mut PaOperation;
#[must_use]
fn pa_context_get_server_info(_: *mut PaContext, callback : Option<PaServerInfoCb>, scratch : *mut c_void) -> *mut PaOperation;
fn pa_cvolume_avg(volume : *const PaCVolume) -> u32;
fn pa_cvolume_get_balance(volume : *const PaCVolume, channel_map : *const PaChannelMap) -> f32;
}
|