From dc1d4730ba47ce599a3d4db5922613470ffd34d5 Mon Sep 17 00:00:00 2001 From: Sean Rhodes Date: Sat, 3 Oct 2026 10:16:40 +0100 Subject: [PATCH 1/8] Sound: Add noise cancellation controls Add a noise cancellation switch for the selected input. Let settings-daemon own processing so it continues after Settings closes. Keep its internal streams out of Applications. Test=Private PulseAudio/PipeWire: InputPanel switch and device changes. --- po/POTFILES | 1 + src/EchoCancellation.vala | 111 +++++ src/InputPanel.vala | 69 ++- src/PulseAudioManager.vala | 636 +++++++++++++++++++----- src/meson.build | 1 + tests/aec-regression/audio-owner.vala | 16 + tests/aec-regression/echo-client.vala | 66 +++ tests/aec-regression/run-functional.py | 356 +++++++++++++ tests/aec-regression/stream-client.vala | 65 +++ tests/aec-regression/ui-client.vala | 384 ++++++++++++++ tests/run-aec.sh | 26 + vapi/libpulse-ext.vapi | 8 + 12 files changed, 1601 insertions(+), 138 deletions(-) create mode 100644 src/EchoCancellation.vala create mode 100644 tests/aec-regression/audio-owner.vala create mode 100644 tests/aec-regression/echo-client.vala create mode 100644 tests/aec-regression/run-functional.py create mode 100644 tests/aec-regression/stream-client.vala create mode 100644 tests/aec-regression/ui-client.vala create mode 100755 tests/run-aec.sh diff --git a/po/POTFILES b/po/POTFILES index 22037789..cde11a67 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -5,6 +5,7 @@ src/TestPopover.vala src/PulseAudioManager.vala src/Plug.vala src/OutputPanel.vala +src/EchoCancellation.vala src/InputPanel.vala src/InputDeviceMonitor.vala src/DeviceRow.vala diff --git a/src/EchoCancellation.vala b/src/EchoCancellation.vala new file mode 100644 index 00000000..240627dc --- /dev/null +++ b/src/EchoCancellation.vala @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: LGPL-2.0-or-later + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +public class Sound.EchoCancellation : Object { + public const string SOURCE_NAME = "elementary_echo_cancel_source"; + public const string SINK_NAME = "elementary_echo_cancel_sink"; + + public struct Status { + public bool enabled; + public bool available; + public bool busy; + public string error; + public string source_master; + public string sink_master; + } + + [DBus (name = "io.elementary.settings_daemon.Audio")] + private interface Audio : DBusProxy { + public abstract async Status get_status () throws DBusError, IOError; + public signal void state_changed (); + } + + public bool requested { get; set; } + public bool enabled { get; private set; } + public bool available { get; private set; } + public bool busy { get; private set; } + public string? error { get; private set; } + public string? source_master { get; private set; } + public string? sink_master { get; private set; } + + private Settings? settings; + private Audio? audio; + private uint owner_generation; + + construct { + var schema = SettingsSchemaSource.get_default ().lookup ("io.elementary.settings-daemon.audio", true); + if (schema == null) { + error = _("The settings audio service is unavailable."); + return; + } + settings = new Settings.full (schema, null, null); + settings.bind ("echo-cancellation", this, "requested", SettingsBindFlags.DEFAULT); + Bus.watch_name (SESSION, "io.elementary.settings-daemon", NONE, (connection) => { + var generation = ++owner_generation; + connection.get_proxy.begin