Skip to content

Commit 093559f

Browse files
committed
feat(sound): add output test button with playback fallbacks
1 parent 0020132 commit 093559f

3 files changed

Lines changed: 86 additions & 1 deletion

File tree

cosmic-settings/src/pages/sound/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub enum Message {
3434
SetSinkVolume(u32),
3535
/// Request to change the input volume.
3636
SetSourceVolume(u32),
37+
TestOutput,
3738
/// Messages handled by the sound module in cosmic-settings-subscriptions
3839
Subscription(subscription::Message),
3940
/// Surface Action
@@ -212,6 +213,10 @@ impl Page {
212213
.map(|message| Message::Subscription(message).into());
213214
}
214215

216+
Message::TestOutput => {
217+
self.model.test_output();
218+
}
219+
215220
Message::ToggleOverAmplificationSink(enabled) => {
216221
self.amplification_sink = enabled;
217222

@@ -330,6 +335,7 @@ fn output() -> Section<crate::pages::Message> {
330335
crate::slab!(descriptions {
331336
volume = fl!("sound-output", "volume");
332337
device = fl!("sound-output", "device");
338+
test = fl!("sound-output", "test");
333339
_level = fl!("sound-output", "level");
334340
balance = fl!("sound-output", "balance");
335341
left = fl!("sound-output", "left");
@@ -382,13 +388,25 @@ fn output() -> Section<crate::pages::Message> {
382388
.apply(Element::from)
383389
.map(crate::pages::Message::from);
384390

391+
let test_output = widget::button::standard(&*section.descriptions[test])
392+
.on_press_maybe(page.model.active_sink().map(|_| Message::TestOutput.into()));
393+
394+
let output_device_controls = widget::row::with_capacity(3)
395+
.align_y(Alignment::Center)
396+
.push(devices)
397+
.push(widget::horizontal_space().width(8))
398+
.push(test_output);
399+
385400
let mut controls = settings::section()
386401
.title(&section.title)
387402
.add(settings::flex_item(
388403
&*section.descriptions[volume],
389404
volume_control,
390405
))
391-
.add(settings::item(&*section.descriptions[device], devices))
406+
.add(settings::item(
407+
&*section.descriptions[device],
408+
output_device_controls,
409+
))
392410
.add(settings::item(
393411
&*section.descriptions[balance],
394412
widget::row::with_capacity(4)

i18n/en/cosmic_settings.ftl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,7 @@ sound = Sound
588588
sound-output = Output
589589
.volume = Output volume
590590
.device = Output device
591+
.test = Test
591592
.level = Output level
592593
.config = Configuration
593594
.balance = Balance

subscriptions/sound/src/lib.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ impl Model {
135135
&self.sources
136136
}
137137

138+
pub fn test_output(&self) {
139+
if self.active_sink_node.is_none() {
140+
tracing::warn!(target: "sound", "cannot play output test sound without an active sink");
141+
return;
142+
}
143+
144+
tokio::spawn(async {
145+
if !play_output_test_sound().await {
146+
tracing::warn!(target: "sound", "failed to play output test sound using available backends");
147+
}
148+
});
149+
}
150+
138151
pub fn clear(&mut self) {
139152
if let Some(handle) = self.subscription_handle.take() {
140153
_ = handle.cancel_tx.send(());
@@ -953,3 +966,56 @@ pub async fn set_profile(id: u32, index: u32, save: bool) {
953966
.status()
954967
.await;
955968
}
969+
970+
async fn play_output_test_sound() -> bool {
971+
if run_command("canberra-gtk-play", &["-i", "audio-test-signal"]).await {
972+
return true;
973+
}
974+
975+
let test_sound_paths = [
976+
"/usr/share/sounds/freedesktop/stereo/audio-test-signal.oga",
977+
"/usr/share/sounds/freedesktop/stereo/audio-test-signal.ogg",
978+
"/usr/share/sounds/freedesktop/stereo/audio-test-signal.wav",
979+
"/usr/share/sounds/freedesktop/stereo/bell.oga",
980+
"/usr/share/sounds/freedesktop/stereo/bell.ogg",
981+
"/usr/share/sounds/freedesktop/stereo/bell.wav",
982+
"/usr/share/sounds/freedesktop/stereo/audio-test.ogg",
983+
];
984+
985+
for sound_path in test_sound_paths {
986+
if run_command("paplay", &[sound_path]).await {
987+
return true;
988+
}
989+
}
990+
991+
for sound_path in test_sound_paths {
992+
if run_command("pw-play", &[sound_path]).await {
993+
return true;
994+
}
995+
}
996+
997+
false
998+
}
999+
1000+
async fn run_command(command: &str, args: &[&str]) -> bool {
1001+
match tokio::time::timeout(
1002+
Duration::from_secs(5),
1003+
tokio::process::Command::new(command)
1004+
.args(args)
1005+
.stdout(Stdio::null())
1006+
.stderr(Stdio::null())
1007+
.status(),
1008+
)
1009+
.await
1010+
{
1011+
Ok(Ok(status)) => status.success(),
1012+
Ok(Err(why)) => {
1013+
tracing::debug!(target: "sound", ?why, command, "failed to run test sound command");
1014+
false
1015+
}
1016+
Err(why) => {
1017+
tracing::warn!(target: "sound", ?why, command, "test sound command timed out");
1018+
false
1019+
}
1020+
}
1021+
}

0 commit comments

Comments
 (0)