mirror of
https://github.com/arabianq/pipewire-soundpad.git
synced 2026-04-27 22:11:22 +00:00
fix(daemon): Remove .expect() panics from PipeWire initialization (#89)
This commit addresses a security and stability vulnerability where failures in PipeWire context setup, connection, or registry acquisition would crash the entire thread (or daemon) via `.expect()` panics. We now gracefully capture and propagate initialization errors up the call stack. A `sync_channel(0)` is used to signal the success or failure of the initial pipewire setup back to the calling functions (`get_all_devices`, `create_virtual_mic`, `create_link`). This prevents unexpected crashes and improves error resilience. Also removed unneeded `pw_sender` panics on channel termination by simply dropping/ignoring the result. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1a37729cf1
commit
fc2cd5e2da
+8
-2
@@ -9,7 +9,10 @@ use std::path::PathBuf;
|
|||||||
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
|
fn chord_from_event(modifiers: &Modifiers, key: &Key) -> Option<String> {
|
||||||
let key_name = key.name();
|
let key_name = key.name();
|
||||||
let is_valid = (key_name.len() == 1
|
let is_valid = (key_name.len() == 1
|
||||||
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
|
&& key_name
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.is_some_and(|c| c.is_ascii_alphanumeric()))
|
||||||
|| (key_name.starts_with('F')
|
|| (key_name.starts_with('F')
|
||||||
&& key_name.len() > 1
|
&& key_name.len() > 1
|
||||||
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
||||||
@@ -60,7 +63,10 @@ pub fn parse_chord(chord: &str) -> Option<(Modifiers, Key)> {
|
|||||||
|
|
||||||
let key_name = parts[parts.len() - 1];
|
let key_name = parts[parts.len() - 1];
|
||||||
let is_valid = (key_name.len() == 1
|
let is_valid = (key_name.len() == 1
|
||||||
&& key_name.chars().next().is_some_and(|c| c.is_ascii_alphanumeric()))
|
&& key_name
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.is_some_and(|c| c.is_ascii_alphanumeric()))
|
||||||
|| (key_name.starts_with('F')
|
|| (key_name.starts_with('F')
|
||||||
&& key_name.len() > 1
|
&& key_name.len() > 1
|
||||||
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
&& key_name[1..].chars().all(|c| c.is_ascii_digit()));
|
||||||
|
|||||||
+112
-33
@@ -9,11 +9,11 @@ use tokio::{
|
|||||||
time::{Duration, timeout},
|
time::{Duration, timeout},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn setup_pipewire_context() -> (MainLoopRc, ContextRc) {
|
pub fn setup_pipewire_context() -> Result<(MainLoopRc, ContextRc), String> {
|
||||||
pipewire::init();
|
pipewire::init();
|
||||||
let main_loop = MainLoopRc::new(None).expect("Failed to initialize pipewire main loop");
|
let main_loop = MainLoopRc::new(None).map_err(|e| e.to_string())?;
|
||||||
let context = ContextRc::new(&main_loop, None).expect("Failed to create pipewire context");
|
let context = ContextRc::new(&main_loop, None).map_err(|e| e.to_string())?;
|
||||||
(main_loop, context)
|
Ok((main_loop, context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_global_object(
|
fn parse_global_object(
|
||||||
@@ -85,8 +85,15 @@ fn parse_global_object(
|
|||||||
async fn pw_get_global_objects_thread(
|
async fn pw_get_global_objects_thread(
|
||||||
main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>,
|
main_sender: mpsc::Sender<(Option<AudioDevice>, Option<Port>)>,
|
||||||
pw_receiver: pipewire::channel::Receiver<Terminate>,
|
pw_receiver: pipewire::channel::Receiver<Terminate>,
|
||||||
|
init_sender: std::sync::mpsc::SyncSender<Result<(), String>>,
|
||||||
) {
|
) {
|
||||||
let (main_loop, context) = setup_pipewire_context();
|
let (main_loop, context) = match setup_pipewire_context() {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = init_sender.send(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Stop main loop on Terminate message
|
// Stop main loop on Terminate message
|
||||||
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
||||||
@@ -94,12 +101,24 @@ async fn pw_get_global_objects_thread(
|
|||||||
move |_| _main_loop.quit()
|
move |_| _main_loop.quit()
|
||||||
});
|
});
|
||||||
|
|
||||||
let core = context
|
let core = match context.connect(None) {
|
||||||
.connect(None)
|
Ok(core) => core,
|
||||||
.expect("Failed to connect to pipewire context");
|
Err(e) => {
|
||||||
let registry = core
|
let _ = init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
|
||||||
.get_registry()
|
return;
|
||||||
.expect("Failed to get registry from pipewire context");
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let registry = match core.get_registry() {
|
||||||
|
Ok(registry) => registry,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = init_sender.send(Err(format!(
|
||||||
|
"Failed to get registry from pipewire context: {}",
|
||||||
|
e
|
||||||
|
)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let _listener = registry
|
let _listener = registry
|
||||||
.add_listener_local()
|
.add_listener_local()
|
||||||
@@ -115,6 +134,11 @@ async fn pw_get_global_objects_thread(
|
|||||||
})
|
})
|
||||||
.register();
|
.register();
|
||||||
|
|
||||||
|
// Signal successful initialization
|
||||||
|
if init_sender.send(Ok(())).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +146,17 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
|
|||||||
// Channels to communicate with pipewire thread
|
// Channels to communicate with pipewire thread
|
||||||
let (main_sender, mut main_receiver) = mpsc::channel(10);
|
let (main_sender, mut main_receiver) = mpsc::channel(10);
|
||||||
let (pw_sender, pw_receiver) = pipewire::channel::channel();
|
let (pw_sender, pw_receiver) = pipewire::channel::channel();
|
||||||
|
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
|
||||||
|
|
||||||
// Spawn pipewire thread in background
|
// Spawn pipewire thread in background
|
||||||
let _pw_thread =
|
let _pw_thread = tokio::spawn(async move {
|
||||||
tokio::spawn(async move { pw_get_global_objects_thread(main_sender, pw_receiver).await });
|
pw_get_global_objects_thread(main_sender, pw_receiver, init_sender).await
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for initialization to complete
|
||||||
|
if let Err(e) = init_receiver.recv()? {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
let mut input_devices: HashMap<u32, AudioDevice> = HashMap::new();
|
let mut input_devices: HashMap<u32, AudioDevice> = HashMap::new();
|
||||||
let mut output_devices: HashMap<u32, AudioDevice> = HashMap::new();
|
let mut output_devices: HashMap<u32, AudioDevice> = HashMap::new();
|
||||||
@@ -150,9 +181,7 @@ pub async fn get_all_devices() -> Result<(Vec<AudioDevice>, Vec<AudioDevice>), B
|
|||||||
}
|
}
|
||||||
Ok(None) | Err(_) => {
|
Ok(None) | Err(_) => {
|
||||||
// Pipewire thread is finished and we can collect our devices
|
// Pipewire thread is finished and we can collect our devices
|
||||||
pw_sender
|
let _ = pw_sender.send(Terminate {});
|
||||||
.send(Terminate {})
|
|
||||||
.expect("Failed to terminate pipewire thread");
|
|
||||||
|
|
||||||
for port in ports {
|
for port in ports {
|
||||||
let node_id = port.node_id;
|
let node_id = port.node_id;
|
||||||
@@ -226,12 +255,24 @@ pub async fn get_device(device_name: &str) -> Result<AudioDevice, Box<dyn Error>
|
|||||||
|
|
||||||
pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
|
pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
|
||||||
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
|
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
|
||||||
|
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
|
||||||
|
|
||||||
let _pw_thread = thread::spawn(move || {
|
let _pw_thread = thread::spawn(move || {
|
||||||
let (main_loop, context) = setup_pipewire_context();
|
let (main_loop, context) = match setup_pipewire_context() {
|
||||||
let core = context
|
Ok(res) => res,
|
||||||
.connect(None)
|
Err(e) => {
|
||||||
.expect("Failed to connect to pipewire context");
|
let _ = init_sender.send(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let core = match context.connect(None) {
|
||||||
|
Ok(core) => core,
|
||||||
|
Err(e) => {
|
||||||
|
let _ =
|
||||||
|
init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let props = properties!(
|
let props = properties!(
|
||||||
"factory.name" => "support.null-audio-sink",
|
"factory.name" => "support.null-audio-sink",
|
||||||
@@ -243,9 +284,13 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
|
|||||||
"object.linger" => "false", // Destroy the node on app exit
|
"object.linger" => "false", // Destroy the node on app exit
|
||||||
);
|
);
|
||||||
|
|
||||||
let _node = core
|
let _node = match core.create_object::<pipewire::node::Node>("adapter", &props) {
|
||||||
.create_object::<pipewire::node::Node>("adapter", &props)
|
Ok(node) => node,
|
||||||
.expect("Failed to create virtual mic");
|
Err(e) => {
|
||||||
|
let _ = init_sender.send(Err(format!("Failed to create virtual mic: {}", e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
||||||
let _main_loop = main_loop.clone();
|
let _main_loop = main_loop.clone();
|
||||||
@@ -253,9 +298,16 @@ pub fn create_virtual_mic() -> Result<pipewire::channel::Sender<Terminate>, Box<
|
|||||||
});
|
});
|
||||||
|
|
||||||
println!("Virtual mic created");
|
println!("Virtual mic created");
|
||||||
|
if init_sender.send(Ok(())).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Err(e) = init_receiver.recv()? {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(pw_sender)
|
Ok(pw_sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,12 +356,24 @@ pub fn create_link(
|
|||||||
input_fr: Port,
|
input_fr: Port,
|
||||||
) -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
|
) -> Result<pipewire::channel::Sender<Terminate>, Box<dyn Error>> {
|
||||||
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
|
let (pw_sender, pw_receiver) = pipewire::channel::channel::<Terminate>();
|
||||||
|
let (init_sender, init_receiver) = std::sync::mpsc::sync_channel(0);
|
||||||
|
|
||||||
let _pw_thread = thread::spawn(move || {
|
let _pw_thread = thread::spawn(move || {
|
||||||
let (main_loop, context) = setup_pipewire_context();
|
let (main_loop, context) = match setup_pipewire_context() {
|
||||||
let core = context
|
Ok(res) => res,
|
||||||
.connect(None)
|
Err(e) => {
|
||||||
.expect("Failed to connect to pipewire context");
|
let _ = init_sender.send(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let core = match context.connect(None) {
|
||||||
|
Ok(core) => core,
|
||||||
|
Err(e) => {
|
||||||
|
let _ =
|
||||||
|
init_sender.send(Err(format!("Failed to connect to pipewire context: {}", e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let props_fl = properties! {
|
let props_fl = properties! {
|
||||||
"link.output.node" => format!("{}", output_fl.node_id).as_str(),
|
"link.output.node" => format!("{}", output_fl.node_id).as_str(),
|
||||||
@@ -324,12 +388,20 @@ pub fn create_link(
|
|||||||
"link.input.port" => format!("{}", input_fr.port_id).as_str(),
|
"link.input.port" => format!("{}", input_fr.port_id).as_str(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _link_fl = core
|
let _link_fl = match core.create_object::<Link>("link-factory", &props_fl) {
|
||||||
.create_object::<Link>("link-factory", &props_fl)
|
Ok(link) => link,
|
||||||
.expect("Failed to create link FL");
|
Err(e) => {
|
||||||
let _link_fr = core
|
let _ = init_sender.send(Err(format!("Failed to create link FL: {}", e)));
|
||||||
.create_object::<Link>("link-factory", &props_fr)
|
return;
|
||||||
.expect("Failed to create link FR");
|
}
|
||||||
|
};
|
||||||
|
let _link_fr = match core.create_object::<Link>("link-factory", &props_fr) {
|
||||||
|
Ok(link) => link,
|
||||||
|
Err(e) => {
|
||||||
|
let _ = init_sender.send(Err(format!("Failed to create link FR: {}", e)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
let _receiver = pw_receiver.attach(main_loop.loop_(), {
|
||||||
let _main_loop = main_loop.clone();
|
let _main_loop = main_loop.clone();
|
||||||
@@ -340,8 +412,15 @@ pub fn create_link(
|
|||||||
"Link created: FL: {}-{} FR: {}-{}",
|
"Link created: FL: {}-{} FR: {}-{}",
|
||||||
output_fl.node_id, input_fl.node_id, output_fr.node_id, input_fr.node_id
|
output_fl.node_id, input_fl.node_id, output_fr.node_id, input_fr.node_id
|
||||||
);
|
);
|
||||||
|
if init_sender.send(Ok(())).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Err(e) = init_receiver.recv()? {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(pw_sender)
|
Ok(pw_sender)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user