From 31776f401a9ff3124ca3feb1271cd17fb751081e Mon Sep 17 00:00:00 2001 From: kotontrion Date: Fri, 19 Jul 2024 09:31:05 +0200 Subject: add default speaker/microphone objects --- src/audio.c | 48 ++++++++++++++++++++++++-- src/endpoint.c | 101 ++++++++++++++++++++++++++++++++++++++++++++---------- src/wireplumber.c | 60 ++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/audio.c b/src/audio.c index 630a09c..3782b2e 100644 --- a/src/audio.c +++ b/src/audio.c @@ -3,9 +3,7 @@ #include #include "endpoint.h" -#include "glib-object.h" -#include "glib.h" -#include "wp.h" +#include "wireplumber.h" struct _AstalWpAudio { GObject parent_instance; @@ -39,6 +37,8 @@ typedef enum { ASTAL_WP_AUDIO_PROP_SPEAKERS, ASTAL_WP_AUDIO_PROP_STREAMS, ASTAL_WP_AUDIO_PROP_RECORDERS, + ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER, + ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE, ASTAL_WP_AUDIO_N_PROPERTIES, } AstalWpAudioProperties; @@ -208,6 +208,26 @@ AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) { return endpoint; } +/** + * astal_wp_audio_get_default_speaker + * + * Returns: (nullable) (transfer none): gets the default speaker object + */ +AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + return astal_wp_wp_get_default_speaker(priv->wp); +} + +/** + * astal_wp_audio_get_default_microphone + * + * Returns: (nullable) (transfer none): gets the default microphone object + */ +AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self) { + AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); + return astal_wp_wp_get_default_microphone(priv->wp); +} + static void astal_wp_audio_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { AstalWpAudio *self = ASTAL_WP_AUDIO(object); @@ -225,6 +245,12 @@ static void astal_wp_audio_get_property(GObject *object, guint property_id, GVal case ASTAL_WP_AUDIO_PROP_RECORDERS: g_value_set_pointer(value, astal_wp_audio_get_recorders(self)); break; + case ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER: + g_value_set_object(value, astal_wp_audio_get_default_speaker(self)); + break; + case ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE: + g_value_set_object(value, astal_wp_audio_get_default_microphone(self)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -354,6 +380,22 @@ static void astal_wp_audio_class_init(AstalWpAudioClass *class) { */ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_STREAMS] = g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE); + /** + * AstalWpAudio:default-speaker: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER] = + g_param_spec_object("default-speaker", "default-speaker", "default-speaker", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + /** + * AstalWpAudio:default-microphone: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE] = + g_param_spec_object("default-microphone", "default-microphone", "default-microphone", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); g_object_class_install_properties(object_class, ASTAL_WP_AUDIO_N_PROPERTIES, astal_wp_audio_properties); diff --git a/src/endpoint.c b/src/endpoint.c index 0f5550c..2fbab5f 100644 --- a/src/endpoint.c +++ b/src/endpoint.c @@ -7,6 +7,7 @@ #include "glib-object.h" #include "glib.h" #include "glibconfig.h" +#include "wp.h" struct _AstalWpEndpoint { GObject parent_instance; @@ -24,6 +25,9 @@ typedef struct { WpPlugin *mixer; WpPlugin *defaults; + gboolean is_default_node; + AstalWpMediaClass media_class; + gulong signal_handler_id; } AstalWpEndpointPrivate; @@ -181,6 +185,62 @@ static void astal_wp_endpoint_set_property(GObject *object, guint property_id, c } } +static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + if (priv->node == NULL) return; + self->id = wp_proxy_get_bound_id(WP_PROXY(priv->node)); + + astal_wp_endpoint_update_volume(self); + + const gchar *description = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.description"); + if (description == NULL) { + description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.nick"); + } + if (description == NULL) { + description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name"); + } + if (description == NULL) { + description = "unknown"; + } + g_free(self->description); + self->description = g_strdup(description); + + const gchar *type = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class"); + const GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); + self->type = g_enum_get_value_by_nick(enum_class, type)->value; + g_type_class_unref(enum_class); + + g_object_notify(G_OBJECT(self), "id"); + g_object_notify(G_OBJECT(self), "description"); + g_object_notify(G_OBJECT(self), "type"); + g_signal_emit_by_name(self, "changed"); +} + +static void astal_wp_endpoint_default_changed_as_default(AstalWpEndpoint *self) { + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + const GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS); + const gchar *media_class = g_enum_get_value(enum_class, priv->media_class)->value_nick; + guint defaultId; + g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId); + g_type_class_unref(enum_class); + + if (defaultId != self->id) { + if (priv->node != NULL) g_object_unref(priv->node); + AstalWpEndpoint *default_endpoint = + astal_wp_wp_get_endpoint(astal_wp_wp_get_default(), defaultId); + if (default_endpoint != NULL && + astal_wp_endpoint_get_media_class(default_endpoint) == priv->media_class) { + AstalWpEndpointPrivate *default_endpoint_priv = + astal_wp_endpoint_get_instance_private(default_endpoint); + priv->node = g_object_ref(default_endpoint_priv->node); + astal_wp_endpoint_update_properties(self); + } + } +} + static void astal_wp_endpoint_default_changed(AstalWpEndpoint *self) { AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); @@ -200,37 +260,38 @@ static void astal_wp_endpoint_default_changed(AstalWpEndpoint *self) { } } -AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults) { - AstalWpEndpoint *self = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); +AstalWpEndpoint *astal_wp_endpoint_init_as_default(AstalWpEndpoint *self, WpPlugin *mixer, + WpPlugin *defaults, AstalWpMediaClass type) { AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); priv->mixer = g_object_ref(mixer); priv->defaults = g_object_ref(defaults); - priv->node = g_object_ref(node); - self->id = wp_proxy_get_bound_id(WP_PROXY(node)); + priv->media_class = type; + priv->is_default_node = TRUE; + self->is_default = TRUE; - astal_wp_endpoint_update_volume(self); + priv->signal_handler_id = g_signal_connect_swapped( + priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed_as_default), self); - const gchar *description = - wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.description"); - if (description == NULL) { - description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.nick"); - } - if (description == NULL) { - description = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name"); - } - if (description == NULL) { - description = "unknown"; - } - self->description = g_strdup(description); + astal_wp_endpoint_default_changed_as_default(self); + astal_wp_endpoint_update_properties(self); + return self; +} - const gchar *type = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "media.class"); - self->type = g_enum_get_value_by_nick(g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS), type)->value; +AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer, WpPlugin *defaults) { + AstalWpEndpoint *self = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); + + priv->mixer = g_object_ref(mixer); + priv->defaults = g_object_ref(defaults); + priv->node = g_object_ref(node); + priv->is_default_node = FALSE; priv->signal_handler_id = g_signal_connect_swapped( priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed), self); + astal_wp_endpoint_update_properties(self); astal_wp_endpoint_default_changed(self); return self; } @@ -252,6 +313,8 @@ static void astal_wp_endpoint_dispose(GObject *object) { g_signal_handler_disconnect(priv->defaults, priv->signal_handler_id); + g_print("dispose: id: %u, name: %s\n", self->id, self->description); + g_clear_object(&priv->node); g_clear_object(&priv->mixer); g_clear_object(&priv->defaults); diff --git a/src/wireplumber.c b/src/wireplumber.c index d50da83..74794e6 100644 --- a/src/wireplumber.c +++ b/src/wireplumber.c @@ -2,12 +2,16 @@ #include "audio.h" #include "endpoint-private.h" +#include "endpoint.h" #include "glib-object.h" #include "glib.h" #include "wp.h" struct _AstalWpWp { GObject parent_instance; + + AstalWpEndpoint *default_speaker; + AstalWpEndpoint *default_microphone; }; typedef struct { @@ -37,6 +41,8 @@ static guint astal_wp_wp_signals[ASTAL_WP_WP_N_SIGNALS] = { typedef enum { ASTAL_WP_WP_PROP_AUDIO = 1, ASTAL_WP_WP_PROP_ENDPOINTS, + ASTAL_WP_WP_PROP_DEFAULT_SPEAKER, + ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE, ASTAL_WP_WP_N_PROPERTIES, } AstalWpWpProperties; @@ -77,6 +83,22 @@ GList *astal_wp_wp_get_endpoints(AstalWpWp *self) { */ AstalWpAudio *astal_wp_wp_get_audio() { return astal_wp_audio_get_default(); } +/** + * astal_wp_wp_get_default_speaker + * + * Returns: (nullable) (transfer none): gets the default speaker object + */ +AstalWpEndpoint *astal_wp_wp_get_default_speaker(AstalWpWp *self) { return self->default_speaker; } + +/** + * astal_wp_wp_get_default_microphone + * + * Returns: (nullable) (transfer none): gets the default microphone object + */ +AstalWpEndpoint *astal_wp_wp_get_default_microphone(AstalWpWp *self) { + return self->default_microphone; +} + static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { AstalWpWp *self = ASTAL_WP_WP(object); @@ -89,6 +111,12 @@ static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue case ASTAL_WP_WP_PROP_ENDPOINTS: g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints)); break; + case ASTAL_WP_WP_PROP_DEFAULT_SPEAKER: + g_value_set_object(value, self->default_speaker); + break; + case ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE: + g_value_set_object(value, self->default_microphone); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -147,11 +175,21 @@ static void astal_wp_wp_mixer_changed(AstalWpWp *self, guint node_id) { astal_wp_endpoint_update_volume(endpoint); + if (astal_wp_endpoint_get_id(self->default_speaker) == node_id) + astal_wp_endpoint_update_volume(self->default_speaker); + if (astal_wp_endpoint_get_id(self->default_microphone) == node_id) + astal_wp_endpoint_update_volume(self->default_microphone); + g_signal_emit_by_name(self, "changed"); } static void astal_wp_wp_objm_installed(AstalWpWp *self) { AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); + + astal_wp_endpoint_init_as_default(self->default_speaker, priv->mixer, priv->defaults, + ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER); + astal_wp_endpoint_init_as_default(self->default_microphone, priv->mixer, priv->defaults, + ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE); } static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, AstalWpWp *self) { @@ -177,6 +215,7 @@ static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, As G_CALLBACK(astal_wp_wp_object_added), self); g_signal_connect_swapped(priv->obj_manager, "object-removed", G_CALLBACK(astal_wp_wp_object_removed), self); + wp_core_install_object_manager(priv->core, priv->obj_manager); } } @@ -220,6 +259,8 @@ static void astal_wp_wp_dispose(GObject *object) { AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self); wp_core_disconnect(priv->core); + g_clear_object(&self->default_speaker); + g_clear_object(&self->default_microphone); g_clear_object(&priv->mixer); g_clear_object(&priv->defaults); g_clear_object(&priv->obj_manager); @@ -263,6 +304,9 @@ static void astal_wp_wp_init(AstalWpWp *self) { g_signal_connect_swapped(priv->obj_manager, "installed", (GCallback)astal_wp_wp_objm_installed, self); + self->default_speaker = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + self->default_microphone = g_object_new(ASTAL_WP_TYPE_ENDPOINT, NULL); + priv->pending_plugins = 2; wp_core_load_component(priv->core, "libwireplumber-module-default-nodes-api", "module", NULL, "default-nodes-api", NULL, @@ -287,6 +331,22 @@ static void astal_wp_wp_class_init(AstalWpWpClass *class) { */ astal_wp_wp_properties[ASTAL_WP_WP_PROP_ENDPOINTS] = g_param_spec_pointer("endpoints", "endpoints", "endpoints", G_PARAM_READABLE); + /** + * AstalWpAudio:default-speaker: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_SPEAKER] = + g_param_spec_object("default-speaker", "default-speaker", "default-speaker", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); + /** + * AstalWpAudio:default-microphone: + * + * The AstalWndpoint object representing the default speaker + */ + astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE] = + g_param_spec_object("default-microphone", "default-microphone", "default-microphone", + ASTAL_WP_TYPE_ENDPOINT, G_PARAM_READABLE); g_object_class_install_properties(object_class, ASTAL_WP_WP_N_PROPERTIES, astal_wp_wp_properties); -- cgit v1.2.3