From 84f46bf9b7e05f6e5ce5a7784056a84f1ef3fa1b Mon Sep 17 00:00:00 2001 From: Rudra Pratap Singh Date: Fri, 14 Nov 2025 01:35:22 +0530 Subject: [PATCH 1/3] audio-device: Add 'role' property The 'media-role' associated with this device. For now, these roles are the same as given in the feedbackd repository: https://gitlab.freedesktop.org/agx/feedbackd/-/blob/main/data/media-role-nodes.conf Signed-off-by: Rudra Pratap Singh Part-of: --- src/ms-audio-device.c | 29 ++++++++++++++++++++++++++++- src/ms-audio-device.h | 6 +++++- src/ms-audio-devices.c | 12 +++++++++--- src/ms-enums.h | 11 +++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/ms-audio-device.c b/src/ms-audio-device.c index 82ffd457..131e43f6 100644 --- a/src/ms-audio-device.c +++ b/src/ms-audio-device.c @@ -11,6 +11,8 @@ #include "mobile-settings-config.h" #include "ms-audio-device.h" +#include "ms-util.h" +#include "ms-enum-types.h" /** * MsAudioDevice: @@ -24,6 +26,7 @@ enum { PROP_STREAM, PROP_ICON_NAME, PROP_DESCRIPTION, + PROP_ROLE, PROP_LAST_PROP }; static GParamSpec *props[PROP_LAST_PROP]; @@ -35,6 +38,7 @@ struct _MsAudioDevice { GvcMixerStream *stream; char *icon_name; char *description; + MsMediaRole role; }; G_DEFINE_TYPE (MsAudioDevice, ms_audio_device, G_TYPE_OBJECT) @@ -60,6 +64,9 @@ ms_audio_device_set_property (GObject *object, case PROP_DESCRIPTION: self->description = g_value_dup_string (value); break; + case PROP_ROLE: + self->role = g_value_get_enum (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -88,6 +95,9 @@ ms_audio_device_get_property (GObject *object, case PROP_DESCRIPTION: g_value_set_string (value, self->description); break; + case PROP_ROLE: + g_value_set_enum (value, self->role); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; @@ -137,6 +147,12 @@ ms_audio_device_class_init (MsAudioDeviceClass *klass) NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); + props[PROP_ROLE] = + g_param_spec_enum ("role", "", "", + MS_TYPE_MEDIA_ROLE, + MS_MEDIA_ROLE_DEFAULT, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); } @@ -151,13 +167,15 @@ MsAudioDevice * ms_audio_device_new (guint id, GvcMixerStream *stream, const char *icon_name, - const char *description) + const char *description, + MsMediaRole role) { return g_object_new (MS_TYPE_AUDIO_DEVICE, "id", id, "stream", stream, "icon-name", icon_name, "description", description, + "role", role, NULL); } @@ -187,3 +205,12 @@ ms_audio_device_get_stream (MsAudioDevice *self) return self->stream; } + + +MsMediaRole +ms_audio_device_get_role (MsAudioDevice *self) +{ + g_return_val_if_fail (MS_IS_AUDIO_DEVICE (self), MS_MEDIA_ROLE_DEFAULT); + + return self->role; +} diff --git a/src/ms-audio-device.h b/src/ms-audio-device.h index cdb56fce..ef9eafa5 100644 --- a/src/ms-audio-device.h +++ b/src/ms-audio-device.h @@ -6,6 +6,8 @@ #pragma once +#include "ms-enums.h" + #include #include "gvc-mixer-stream.h" @@ -20,9 +22,11 @@ G_DECLARE_FINAL_TYPE (MsAudioDevice, ms_audio_device, MS, AUDIO_DEVICE, GObject) MsAudioDevice *ms_audio_device_new (guint id, GvcMixerStream *stream, const char *icon_name, - const char *description); + const char *description, + MsMediaRole role); guint ms_audio_device_get_id (MsAudioDevice *self); const char * ms_audio_device_get_description (MsAudioDevice *self); +MsMediaRole ms_audio_device_get_role (MsAudioDevice *self); GvcMixerStream * ms_audio_device_get_stream (MsAudioDevice *self); G_END_DECLS diff --git a/src/ms-audio-devices.c b/src/ms-audio-devices.c index abaee3ba..3f38e210 100644 --- a/src/ms-audio-devices.c +++ b/src/ms-audio-devices.c @@ -140,6 +140,7 @@ on_device_added (MsAudioDevices *self, guint id) const char *icon_name; const char *origin; const char *name; + MsMediaRole role; g_autoptr (MsAudioDevice) audio_device = NULL; guint stream_id; @@ -176,26 +177,31 @@ on_device_added (MsAudioDevices *self, guint id) if (g_str_equal (name, "input.loopback.sink.role.multimedia")) { description = g_strdup (_("Media Volume")); + role = MS_MEDIA_ROLE_MULTIMEDIA; } else if (g_str_equal (name, "input.loopback.sink.role.notification")) { description = g_strdup (_("Notification Volume")); + role = MS_MEDIA_ROLE_NOTIFICATION; } else if (g_str_equal (name, "input.loopback.sink.role.phone")) { description = g_strdup (_("Voice Call Volume")); + role = MS_MEDIA_ROLE_PHONE; } else if (g_str_equal (name, "input.loopback.sink.role.ringtone")) { description = g_strdup (_("Ring Tone Volume")); - } else if (g_str_equal (name, "input.loopback.sink.role.call")) { - description = g_strdup (_("Call volume")); + role = MS_MEDIA_ROLE_RINGTONE; } else if (g_str_equal (name, "input.loopback.sink.role.alarm")) { description = g_strdup (_("Alarm Volume")); + role = MS_MEDIA_ROLE_ALARM; } else if (g_str_equal (name, "input.loopback.sink.role.alert")) { description = g_strdup (_("Emergency Alerts Volume")); + role = MS_MEDIA_ROLE_ALERT; } else { g_warning ("Unknown stream name '%s'", name); description = g_strdup (gvc_mixer_ui_device_get_description (device)); + role = MS_MEDIA_ROLE_DEFAULT; } g_debug ("Adding audio device %d: %s", id, description); - audio_device = ms_audio_device_new (id, stream, icon_name, description); + audio_device = ms_audio_device_new (id, stream, icon_name, description, role); g_list_store_append (self->devices, audio_device); } diff --git a/src/ms-enums.h b/src/ms-enums.h index d954f8f8..87cfde29 100644 --- a/src/ms-enums.h +++ b/src/ms-enums.h @@ -31,4 +31,15 @@ typedef enum { MS_PHOSH_NOTIFICATION_URGENCY_CRITICAL, } MsPhoshNotificationUrgency; + +typedef enum { + MS_MEDIA_ROLE_ALARM, + MS_MEDIA_ROLE_ALERT, + MS_MEDIA_ROLE_MULTIMEDIA, + MS_MEDIA_ROLE_NOTIFICATION, + MS_MEDIA_ROLE_PHONE, + MS_MEDIA_ROLE_RINGTONE, + MS_MEDIA_ROLE_DEFAULT = MS_MEDIA_ROLE_MULTIMEDIA +} MsMediaRole; + G_END_DECLS -- GitLab From e146a7f20c68421fc61069f9c599935c3888d592 Mon Sep 17 00:00:00 2001 From: Rudra Pratap Singh Date: Sat, 15 Nov 2025 02:37:32 +0530 Subject: [PATCH 2/3] audio-device-row: Add 'volume-changed' signal Emitted whenever volume slider gets adjusted. Useful for e.g., playing a feedback sound upon any adjustment. Signed-off-by: Rudra Pratap Singh Part-of: --- src/ms-audio-device-row.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ms-audio-device-row.c b/src/ms-audio-device-row.c index 9a7ea6b3..a75ad384 100644 --- a/src/ms-audio-device-row.c +++ b/src/ms-audio-device-row.c @@ -30,6 +30,12 @@ enum { }; static GParamSpec *props[PROP_LAST_PROP]; +enum { + VOLUME_CHANGED, + N_SIGNALS +}; +static uint signals[N_SIGNALS]; + struct _MsAudioDeviceRow { AdwPreferencesRow parent; @@ -64,6 +70,8 @@ on_volume_changed (MsAudioDeviceRow *self) gvc_mixer_stream_push_volume (stream); gvc_mixer_stream_change_is_muted (stream, (int) rounded == 0); + + g_signal_emit (self, signals[VOLUME_CHANGED], 0); } @@ -198,6 +206,17 @@ ms_audio_device_row_class_init (MsAudioDeviceRowClass *klass) MS_TYPE_AUDIO_DEVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY); + signals[VOLUME_CHANGED] = + g_signal_new ("volume-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, + NULL, + NULL, + G_TYPE_NONE, + 0); + g_object_class_install_properties (object_class, PROP_LAST_PROP, props); gtk_widget_class_set_template_from_resource (widget_class, -- GitLab From dc6a3e93f1693ba979528cefe2a0098038fac020 Mon Sep 17 00:00:00 2001 From: Rudra Pratap Singh Date: Sun, 16 Nov 2025 01:31:21 +0530 Subject: [PATCH 3/3] feedback-panel: Play sound on adjusting sliders Signed-off-by: Rudra Pratap Singh Part-of: --- src/ms-feedback-panel.c | 147 +++++++++++++++++++++++++++++++++--- src/ms-util.c | 62 +++++++++++++++ src/ms-util.h | 2 + src/ui/ms-feedback-panel.ui | 6 ++ 4 files changed, 205 insertions(+), 12 deletions(-) diff --git a/src/ms-feedback-panel.c b/src/ms-feedback-panel.c index 0d8c1db5..81daf109 100644 --- a/src/ms-feedback-panel.c +++ b/src/ms-feedback-panel.c @@ -36,6 +36,8 @@ #define FEEDBACKD_KEY_MAX_HAPTIC_STRENGTH "max-haptic-strength" #define APP_SCHEMA FEEDBACKD_SCHEMA_ID ".application" #define APP_PREFIX "/org/sigxcpu/feedbackd/application/" +#define GNOME_SOUND_SCHEMA_ID "org.gnome.desktop.sound" +#define GNOME_SOUND_KEY_THEME_NAME "theme-name" #define NOTIFICATIONS_SCHEMA "sm.puri.phosh.notifications" #define NOTIFICATIONS_URGENCY_ENUM "sm.puri.phosh.NotificationUrgency" @@ -106,10 +108,16 @@ struct _MsFeedbackPanel { MsPhoshNotificationUrgency notifications_urgency; /* Audio Settings */ + GSettings *sound_settings; GvcMixerControl *mixer_control; MsAudioDevices *audio_devices; GtkListBox *audio_devices_listbox; + + /* Volume sliders */ AdwPreferencesGroup *sound_settings_group; + uint update_id; + MsMediaRole last_volume_slider_role; + GtkToggleButton *volume_slider_mute_btn; GStrv notifications_wakeup_categories; @@ -119,12 +127,107 @@ struct _MsFeedbackPanel { G_DEFINE_TYPE (MsFeedbackPanel, ms_feedback_panel, MS_TYPE_PANEL) +static void +stop_playback (MsFeedbackPanel *self) +{ + g_cancellable_cancel (self->sound_cancel); + g_clear_object (&self->sound_cancel); + if (self->toast) { + adw_toast_dismiss (self->toast); + g_clear_object (&self->toast); + } +} + + +static void +on_volume_slider_sound_finished (GObject *source_object, GAsyncResult *res, gpointer user_data) +{ + MsFeedbackPanel *self = MS_FEEDBACK_PANEL (user_data); + gboolean success; + g_autoptr (GError) err = NULL; + + g_assert (MS_IS_FEEDBACK_PANEL (self)); + + success = gsound_context_play_full_finish (GSOUND_CONTEXT (source_object), res, &err); + + if (!success && !g_error_matches (err, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + const char *role = ms_get_media_role_as_string (self->last_volume_slider_role); + + g_warning ("Failed to play sound: %s", err->message); + self->toast = adw_toast_new_format ("Failed to play sound for %s slider", role); + adw_toast_set_timeout (self->toast, 3); + adw_toast_overlay_add_toast (self->toast_overlay, g_object_ref (self->toast)); + } + + /* Clear cancellable if unused, if used it's cleared in stop_playback */ + g_clear_object (&self->sound_cancel); +} + + +static void +play_volume_slider_sound (gpointer user_data) +{ + MsFeedbackPanel *self = MS_FEEDBACK_PANEL (user_data); + const char *event_id = ms_get_event_id_for_media_role (self->last_volume_slider_role); + const char *role = ms_get_media_role_as_string (self->last_volume_slider_role); + + g_return_if_fail (GSOUND_IS_CONTEXT (self->sound_context)); + + self->sound_cancel = g_cancellable_new (); + + gsound_context_play_full (self->sound_context, + self->sound_cancel, + on_volume_slider_sound_finished, + self, + GSOUND_ATTR_EVENT_ID, event_id, + GSOUND_ATTR_EVENT_DESCRIPTION, "Volume slider sound", + GSOUND_ATTR_MEDIA_ROLE, role, + NULL); + + self->update_id = 0; +} + + +static void +on_audio_device_row_volume_changed (MsFeedbackPanel *self, MsAudioDeviceRow *row) +{ + MsAudioDevice *device; + + g_assert (MS_IS_AUDIO_DEVICE_ROW (row)); + + if (gtk_toggle_button_get_active (self->volume_slider_mute_btn)) + return; + + device = ms_audio_device_row_get_audio_device (row); + self->last_volume_slider_role = ms_audio_device_get_role (device); + + /* Temporary, while we add the sound file */ + if (self->last_volume_slider_role == MS_MEDIA_ROLE_PHONE) + return; + + g_clear_handle_id (&self->update_id, g_source_remove); + stop_playback (self); + + /* Small timeout as MsAudioDeviceRow might emit + * 'volume-changed' more than once */ + self->update_id = g_timeout_add_once (300, play_volume_slider_sound, self); +} + + static GtkWidget * create_audio_device_row (gpointer item, gpointer user_data) { + MsFeedbackPanel *self = MS_FEEDBACK_PANEL (user_data); MsAudioDevice *audio_device = MS_AUDIO_DEVICE (item); + GtkWidget *row = GTK_WIDGET (ms_audio_device_row_new (audio_device)); - return GTK_WIDGET (ms_audio_device_row_new (audio_device)); + g_signal_connect_object (row, + "volume-changed", + G_CALLBACK (on_audio_device_row_volume_changed), + self, + G_CONNECT_SWAPPED); + + return row; } @@ -148,17 +251,6 @@ on_haptic_strength_changed (void) g_timeout_add_once (200, wait_a_bit, NULL); } -static void -stop_playback (MsFeedbackPanel *self) -{ - g_cancellable_cancel (self->sound_cancel); - g_clear_object (&self->sound_cancel); - if (self->toast) { - adw_toast_dismiss (self->toast); - g_clear_object (&self->toast); - } -} - static void update_sound_row_playing_state (MsFeedbackPanel *self) @@ -527,6 +619,25 @@ on_notifications_settings_changed (MsFeedbackPanel *self) } +static void +on_sound_theme_name_changed (MsFeedbackPanel *self, const char *key, GSettings *settings) +{ + gboolean ok; + g_autoptr (GError) error = NULL; + g_autofree char *name = NULL; + + name = g_settings_get_string (settings, key); + ok = gsound_context_set_attributes (self->sound_context, + &error, + GSOUND_ATTR_CANBERRA_XDG_THEME_NAME, + name, + NULL); + + if (!ok) + g_warning ("Failed to set sound theme name to %s: %s", key, error->message); +} + + static void change_notifications_settings (MsFeedbackPanel *self) { @@ -763,6 +874,7 @@ ms_feedback_panel_dispose (GObject *object) g_clear_object (&self->notifications_settings); g_strfreev (self->notifications_wakeup_categories); g_clear_pointer (&self->known_applications, g_hash_table_unref); + g_clear_object (&self->sound_settings); g_clear_object (&self->audio_devices); g_clear_object (&self->mixer_control); @@ -800,6 +912,8 @@ ms_feedback_panel_class_init (MsFeedbackPanelClass *klass) gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, haptic_strenth_row); gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, prefer_flash); gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, sound_settings_group); + gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, sound_settings_group); + gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, volume_slider_mute_btn); gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, sounds_listbox); gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, quick_silent_switch); gtk_widget_class_bind_template_child (widget_class, MsFeedbackPanel, toast_overlay); @@ -828,6 +942,15 @@ ms_feedback_panel_class_init (MsFeedbackPanelClass *klass) static void ms_feedback_panel_init_audio (MsFeedbackPanel *self) { + self->sound_settings = g_settings_new (GNOME_SOUND_SCHEMA_ID); + + g_signal_connect_object (self->sound_settings, + "changed::" GNOME_SOUND_KEY_THEME_NAME, + G_CALLBACK (on_sound_theme_name_changed), + self, + G_CONNECT_SWAPPED); + on_sound_theme_name_changed (self, GNOME_SOUND_KEY_THEME_NAME, self->sound_settings); + self->mixer_control = gvc_mixer_control_new (_("Mobile Settings Volume Control")); g_return_if_fail (self->mixer_control); gvc_mixer_control_open (self->mixer_control); diff --git a/src/ms-util.c b/src/ms-util.c index 205653fb..1d9f0e10 100644 --- a/src/ms-util.c +++ b/src/ms-util.c @@ -419,3 +419,65 @@ ms_get_casefolded_string_list (GtkStringList *strlist) return casefolded_strlist; } + + +const char * +ms_get_event_id_for_media_role (MsMediaRole role) +{ + const char *event_id; + + switch (role) { + case MS_MEDIA_ROLE_ALARM: + event_id = "alarm-clock-elapsed"; + break; + case MS_MEDIA_ROLE_ALERT: + event_id = "cellbroadcast"; + break; + case MS_MEDIA_ROLE_NOTIFICATION: + event_id = "message-new-instant"; + break; + case MS_MEDIA_ROLE_PHONE: + event_id = ""; + break; + case MS_MEDIA_ROLE_RINGTONE: + event_id = "phone-incoming-call"; + break; + case MS_MEDIA_ROLE_MULTIMEDIA: + default: + event_id = "complete"; + break; + } + + return event_id; +} + + +const char * +ms_get_media_role_as_string (MsMediaRole role) +{ + const char *media_role; + + switch (role) { + case MS_MEDIA_ROLE_ALARM: + media_role = "Alarm"; + break; + case MS_MEDIA_ROLE_ALERT: + media_role = "Alert"; + break; + case MS_MEDIA_ROLE_NOTIFICATION: + media_role = "Notification"; + break; + case MS_MEDIA_ROLE_PHONE: + media_role = "Phone"; + break; + case MS_MEDIA_ROLE_RINGTONE: + media_role = "Ringtone"; + break; + case MS_MEDIA_ROLE_MULTIMEDIA: + default: + media_role = "Multimedia"; + break; + } + + return media_role; +} diff --git a/src/ms-util.h b/src/ms-util.h index f4f33b42..938f1d17 100644 --- a/src/ms-util.h +++ b/src/ms-util.h @@ -41,5 +41,7 @@ GVariant * ms_bool_to_picture_mode (const GValue *in_value, gpointer data); char *ms_normalize_casefold_and_unaccent (const char *str); GtkStringList *ms_get_casefolded_string_list (GtkStringList *strlist); +const char *ms_get_event_id_for_media_role (MsMediaRole role); +const char *ms_get_media_role_as_string (MsMediaRole role); G_END_DECLS diff --git a/src/ui/ms-feedback-panel.ui b/src/ui/ms-feedback-panel.ui index f7202ef1..980cccd9 100644 --- a/src/ui/ms-feedback-panel.ui +++ b/src/ui/ms-feedback-panel.ui @@ -11,6 +11,12 @@ Sound Settings + + + audio-volume-muted-symbolic + Play sound on adjusting volume sliders + + none -- GitLab