Skip to content

Commit 303c5ea

Browse files
committed
Add pipewire loopback workaround
1 parent e841f3d commit 303c5ea

File tree

1 file changed

+136
-7
lines changed

1 file changed

+136
-7
lines changed

src/pipewire.rs

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::cell::RefCell;
1+
use std::cell::{Cell, RefCell};
22
use std::collections::{HashMap, HashSet};
3+
use std::io::Cursor;
34
use std::mem::MaybeUninit;
45
use std::rc::Rc;
56
use std::str::FromStr;
@@ -19,17 +20,20 @@ use ::pipewire::{
1920
properties::properties,
2021
proxy::{Listener, ProxyListener, ProxyT},
2122
spa::{
22-
param::ParamType,
23+
param::{ParamType, audio::AudioInfoRaw},
2324
pod::{
24-
Pod, Value, ValueArray, builder::Builder as PodBuilder, deserialize::PodDeserializer,
25+
Object, Pod, Value, ValueArray, builder::Builder as PodBuilder,
26+
deserialize::PodDeserializer, serialize::PodSerializer,
2527
},
2628
sys::{
27-
SPA_DIRECTION_INPUT, SPA_DIRECTION_OUTPUT, SPA_PARAM_ROUTE_device,
28-
SPA_PARAM_ROUTE_direction, SPA_PARAM_ROUTE_index, SPA_PARAM_ROUTE_name,
29-
SPA_PARAM_ROUTE_props, SPA_PARAM_ROUTE_save, SPA_PROP_channelVolumes, SPA_PROP_mute,
29+
SPA_DIRECTION_INPUT, SPA_DIRECTION_OUTPUT, SPA_PARAM_EnumFormat,
30+
SPA_PARAM_ROUTE_device, SPA_PARAM_ROUTE_direction, SPA_PARAM_ROUTE_index,
31+
SPA_PARAM_ROUTE_name, SPA_PARAM_ROUTE_props, SPA_PARAM_ROUTE_save,
32+
SPA_PROP_channelVolumes, SPA_PROP_mute, SPA_TYPE_OBJECT_Format,
3033
},
3134
utils::{SpaTypes, dict::DictRef},
3235
},
36+
stream::{StreamBox, StreamFlags},
3337
types::ObjectType,
3438
};
3539
use bitflags::bitflags;
@@ -639,10 +643,17 @@ impl Client {
639643
update_copy.replace_with(|v| *v | EventKind::NODE_ADDED);
640644
}
641645
ObjectType::Link => {
646+
let mut client_data = client.data.lock().unwrap();
642647
let Some(link) = Link::new(global_props) else {
643648
return;
644649
};
645-
client.data.lock().unwrap().links.insert(global_id, link);
650+
if let Some(node) = client_data.nodes.get(&link.link_input_node)
651+
&& node.name == env!("CARGO_PKG_NAME")
652+
{
653+
return;
654+
}
655+
656+
client_data.links.insert(global_id, link);
646657
update_copy.replace_with(|v| *v | EventKind::LINK_ADDED);
647658
}
648659
ObjectType::Port => {
@@ -825,6 +836,124 @@ impl Client {
825836
})
826837
.register();
827838

839+
/* START WORKAROUND FOR PIPEWIRE LOOPBACK BUG
840+
841+
This workaround fixes an issue caused by the loopback device.
842+
Without this workaround, if nothing has interacted with the sink/source then setting
843+
the volume/mute will fail.
844+
845+
This workaround creates input and output streams which forces pipewire
846+
to use the real sink/source instead of the loopback device.
847+
848+
For context see:
849+
850+
* https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4949
851+
* https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/4610
852+
* https://gitlab.freedesktop.org/pipewire/wireplumber/-/issues/822
853+
*/
854+
855+
let output_done = Rc::new(Cell::new(false));
856+
let output_done_clone = output_done.clone();
857+
let input_done = Rc::new(Cell::new(false));
858+
let input_done_clone = input_done.clone();
859+
860+
let values: Vec<u8> = PodSerializer::serialize(
861+
Cursor::new(Vec::new()),
862+
&Value::Object(Object {
863+
type_: SPA_TYPE_OBJECT_Format,
864+
id: SPA_PARAM_EnumFormat,
865+
properties: AudioInfoRaw::new().into(),
866+
}),
867+
)
868+
.expect("Failed to serialize pod")
869+
.0
870+
.into_inner();
871+
872+
let mut params = [Pod::from_bytes(&values).expect("Failed to create pod")];
873+
874+
let output_stream = StreamBox::new(
875+
&core,
876+
"i3status_pipewire_workaround_output_stream",
877+
properties! {
878+
*keys::MEDIA_TYPE => "Audio",
879+
*keys::MEDIA_ROLE => "Music",
880+
*keys::MEDIA_CATEGORY => "Playback",
881+
*keys::AUDIO_CHANNELS => "2",
882+
},
883+
)
884+
.expect("Could not create output_stream");
885+
886+
let output_stream_listener = output_stream
887+
.add_local_listener()
888+
.process(move |_stream, _acc: &mut f64| {
889+
output_done_clone.set(true);
890+
})
891+
.register()
892+
.expect("Could not add output_stream listener");
893+
894+
output_stream
895+
.connect(
896+
::pipewire::spa::utils::Direction::Output,
897+
None,
898+
StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS | StreamFlags::RT_PROCESS,
899+
&mut params,
900+
)
901+
.expect("Could not connect output_stream");
902+
903+
let input_stream = StreamBox::new(
904+
&core,
905+
"i3status_pipewire_workaround_input_stream",
906+
properties! {
907+
*keys::MEDIA_TYPE => "Audio",
908+
*keys::MEDIA_ROLE => "Music",
909+
*keys::MEDIA_CATEGORY => "Playback",
910+
*keys::AUDIO_CHANNELS => "2",
911+
},
912+
)
913+
.expect("Could not create input_stream");
914+
let input_stream_listener = input_stream
915+
.add_local_listener()
916+
.process(move |_stream, _acc: &mut f64| {
917+
input_done_clone.set(true);
918+
})
919+
.register()
920+
.expect("Could not add input_stream listener");
921+
922+
input_stream
923+
.connect(
924+
::pipewire::spa::utils::Direction::Input,
925+
None,
926+
StreamFlags::AUTOCONNECT | StreamFlags::MAP_BUFFERS | StreamFlags::RT_PROCESS,
927+
&mut params,
928+
)
929+
.expect("Could not connect input_stream");
930+
931+
while !output_done.get() {
932+
main_loop.loop_().iterate(Duration::from_secs(1));
933+
let event = update.take();
934+
if !event.is_empty() {
935+
client.send_update_event(event);
936+
}
937+
}
938+
output_stream
939+
.disconnect()
940+
.expect("Unable to disconnect output_stream");
941+
output_stream_listener.unregister();
942+
943+
while !input_done.get() {
944+
main_loop.loop_().iterate(Duration::from_secs(1));
945+
let event = update.take();
946+
if !event.is_empty() {
947+
client.send_update_event(event);
948+
}
949+
}
950+
input_stream
951+
.disconnect()
952+
.expect("Unable to disconnect input_stream");
953+
input_stream_listener.unregister();
954+
955+
// END WORKAROUND FOR PIPEWIRE LOOPBACK BUG
956+
828957
loop {
829958
main_loop.loop_().iterate(Duration::from_hours(24));
830959
let event = update.take();

0 commit comments

Comments
 (0)