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
use pipewire::context::ContextRc;
use pipewire::main_loop::MainLoopRc;
use pipewire::spa::utils::result::AsyncSeq;
use serde::Deserialize;
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::sync::mpsc;
use std::time::Duration;
use tokio::sync::watch;
use crate::models::error::WayclipError;
/// The number of attempts to connect to pipewire
pub const MAX_CONNECT_ATTEMPTS: usize = 10;
/// The intervals between retries
pub const RETRY_INTERVAL: Duration = Duration::from_millis(500);
/// Amount of time to away after init
pub const INIT_TIMEOUT: Duration = Duration::from_secs(5);
/// The different types of a pipewire node
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum PipewireNodeType {
Source,
Sink,
}
impl From<&str> for PipewireNodeType {
fn from(value: &str) -> Self {
if value.starts_with("Audio/Source") {
Self::Source
} else if value.starts_with("Audio/Sink") {
Self::Sink
} else {
unreachable!()
}
}
}
/// A struct defining a single device
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PipewireDevice {
/// The node type (Source/Sink/Unknown)
pub node_type: PipewireNodeType,
/// The display name of node used to identify the nodes
pub node_name: String,
/// Small cosmetic description
pub node_description: String,
/// ID used to reference and use the node
pub node_id: u32,
}
/// A struct defining the system at any point in time
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct PipewireState {
/// A list of all the detected devices
pub devices: Vec<PipewireDevice>,
/// The default sink (mic)
pub default_sink: Option<PipewireDevice>,
/// The default source (bg)
pub default_source: Option<PipewireDevice>,
}
#[derive(Default)]
struct DefaultNames {
sink: Option<String>,
source: Option<String>,
}
#[derive(Deserialize)]
struct MetadataDeviceName {
name: String,
}
/// Main manager, allowing to directly communicate with pipewire, and poll state of system.
#[derive(Clone, Debug)]
pub struct PipewireManager {
state_receiver: watch::Receiver<PipewireState>,
}
impl PipewireManager {
/// Method to create a new instance of manager, which initialises a few channels and a thread,
/// which will commnicate with pipewire itself
pub fn new() -> Result<Self, WayclipError> {
pipewire::init();
let (state_sender, state_receiver) = watch::channel(PipewireState::default());
let (init_sender, init_receiver) = mpsc::sync_channel::<Result<(), WayclipError>>(1);
std::thread::spawn(move || {
let run_loop = || -> Result<(), WayclipError> {
let mainloop = MainLoopRc::new(None)
.map_err(|e| WayclipError::Pipewire(e.to_string().into()))?;
let context = ContextRc::new(&mainloop, None)
.map_err(|e| WayclipError::Pipewire(e.to_string().into()))?;
let mut connection_attempts = 0;
let core = loop {
match context.connect_rc(None) {
Ok(core) => break core,
Err(error) if connection_attempts < MAX_CONNECT_ATTEMPTS => {
log::warn!(
"PipeWire connection failed (attempt {}/{}): {}",
connection_attempts + 1,
MAX_CONNECT_ATTEMPTS,
error
);
connection_attempts += 1;
std::thread::sleep(RETRY_INTERVAL);
}
Err(error) => {
return Err(WayclipError::Pipewire(
format!(
"Failed to connect to PipeWire after {} attempts: {}",
connection_attempts, error
)
.into(),
));
}
}
};
let registry = core
.get_registry_rc()
.map_err(|e| WayclipError::Pipewire(e.to_string().into()))?;
let listeners: Rc<RefCell<Vec<Box<dyn Any>>>> = Rc::new(RefCell::new(Vec::new()));
let listeners_weak = Rc::downgrade(&listeners);
// Tracks target default node names from metadata
let default_names = Rc::new(RefCell::new(DefaultNames::default()));
let pending_seq: Rc<Cell<Option<AsyncSeq>>> = Rc::new(Cell::new(None));
let init_signal = Rc::new(RefCell::new(Some(init_sender.clone())));
let core_listener = core
.add_listener_local()
.done({
let pending_seq = Rc::clone(&pending_seq);
let init_signal = Rc::clone(&init_signal);
move |_id, seq| {
if pending_seq.get() != Some(seq) {
return;
}
pending_seq.set(None);
if let Some(sender) = init_signal.borrow_mut().take() {
let _ = sender.send(Ok(()));
}
}
})
.register();
listeners.borrow_mut().push(Box::new(core_listener));
let registry_for_metadata = registry.clone();
let core_for_metadata = core.clone();
let state_sender_global = state_sender.clone();
let state_sender_remove = state_sender.clone();
let registry_listener = registry
.add_listener_local()
.global({
let default_names = Rc::clone(&default_names);
let pending_seq = Rc::clone(&pending_seq);
move |global| {
if global.type_ == pipewire::types::ObjectType::Node
&& let Some(props) = global.props.as_ref()
&& let Some(media_class) = props.get("media.class")
&& let Some(node_name) = props.get("node.name")
&& media_class.starts_with("Audio/")
&& !media_class.contains("Stream")
{
let node_description =
props.get("node.description").unwrap_or(node_name);
let device = PipewireDevice {
node_type: media_class.into(),
node_name: node_name.to_owned(),
node_description: node_description.to_owned(),
node_id: global.id,
};
let names = default_names.borrow();
let is_default_sink = names.sink.as_deref() == Some(node_name);
let is_default_source = names.source.as_deref() == Some(node_name);
drop(names);
state_sender_global.send_if_modified(|state| {
if state.devices.iter().any(|d| d.node_id == global.id) {
return false;
}
if is_default_sink {
state.default_sink = Some(device.clone());
}
if is_default_source {
state.default_source = Some(device.clone());
}
state.devices.push(device);
log::debug!(
"Discovered audio node: id={}, class={}, name={}",
global.id,
media_class,
node_name
);
true
});
}
if global.type_ == pipewire::types::ObjectType::Metadata
&& let Some(props) = global.props.as_ref()
&& props.get("metadata.name") == Some("default")
{
let metadata = match registry_for_metadata
.bind::<pipewire::metadata::Metadata, _>(
global,
) {
Ok(m) => m,
Err(err) => {
log::error!(
"Failed to bind PipeWire default metadata: {err}"
);
return;
}
};
let default_names_metadata = Rc::clone(&default_names);
let state_sender_metadata = state_sender_global.clone();
let metadata_listener = metadata
.add_listener_local()
.property(move |_subject, key, _type, value| {
let Some(key) = key else { return 0 };
if key != "default.audio.sink"
&& key != "default.audio.source"
{
return 0;
}
let parsed_name = value.and_then(|val| {
serde_json::from_str::<MetadataDeviceName>(val)
.ok()
.map(|d| d.name)
});
match key {
"default.audio.sink" => {
default_names_metadata.borrow_mut().sink =
parsed_name.clone();
}
"default.audio.source" => {
default_names_metadata.borrow_mut().source =
parsed_name.clone();
}
_ => return 0,
}
state_sender_metadata.send_if_modified(|state| {
let target = match key {
"default.audio.sink" => &mut state.default_sink,
"default.audio.source" => &mut state.default_source,
_ => return false,
};
let new_device =
parsed_name.as_deref().and_then(|name| {
state
.devices
.iter()
.find(|d| d.node_name == name)
.cloned()
});
if *target != new_device {
log::debug!(
"PipeWire {key} changed: {new_device:?}"
);
*target = new_device;
true
} else {
false
}
});
0
})
.register();
if let Some(listeners_rc) = listeners_weak.upgrade() {
let mut l = listeners_rc.borrow_mut();
l.push(Box::new(metadata_listener));
l.push(Box::new(metadata));
}
match core_for_metadata.sync(0) {
Ok(seq) => pending_seq.set(Some(seq)),
Err(err) => {
log::error!(
"Failed to synchronize PipeWire metadata: {err}"
)
}
}
}
}
})
.global_remove(move |id| {
state_sender_remove.send_if_modified(|state| {
let mut modified = false;
let old_len = state.devices.len();
state.devices.retain(|d| d.node_id != id);
if state.devices.len() != old_len {
modified = true;
log::debug!("Removed PipeWire node id={id}");
}
if state.default_sink.as_ref().map(|d| d.node_id) == Some(id) {
state.default_sink = None;
modified = true;
}
if state.default_source.as_ref().map(|d| d.node_id) == Some(id) {
state.default_source = None;
modified = true;
}
modified
});
})
.register();
listeners.borrow_mut().push(Box::new(registry_listener));
let seq = core
.sync(0)
.map_err(|e| WayclipError::Pipewire(e.to_string().into()))?;
pending_seq.set(Some(seq));
mainloop.run();
Ok(())
};
if let Err(error) = run_loop() {
let _ = init_sender.send(Err(error));
}
});
init_receiver.recv_timeout(INIT_TIMEOUT).map_err(|_| {
WayclipError::Pipewire("PipeWire did not finish initial sync in time".into())
})??;
Ok(Self { state_receiver })
}
/// Method to extract node ID by a node name from current state
pub fn get_node_id_from_node_name(&self, node_name: &str) -> Option<u32> {
self.state_receiver
.borrow()
.devices
.iter()
.find(|d| d.node_name == node_name)
.map(|d| d.node_id)
}
/// Checks if provided node name matches the node type and exists as a node in the state
pub fn is_node_name_valid(&self, node_name: &str, node_type: PipewireNodeType) -> bool {
self.state_receiver
.borrow()
.devices
.iter()
.any(|d| d.node_name == node_name && d.node_type == node_type)
}
/// Allows to subscrive to events
pub fn subscribe(&self) -> watch::Receiver<PipewireState> {
self.state_receiver.clone()
}
/// Gets the currents tate
pub fn current_state(&self) -> PipewireState {
self.state_receiver.borrow().clone()
}
/// Allows us to get the full default sink device
pub fn get_default_sink(&self) -> Option<PipewireDevice> {
self.state_receiver.borrow().default_sink.clone()
}
/// Allows us to get the full default source device
pub fn get_default_source(&self) -> Option<PipewireDevice> {
self.state_receiver.borrow().default_source.clone()
}
}