diff options
author | kotontrion <[email protected]> | 2024-08-20 10:15:16 +0200 |
---|---|---|
committer | kotontrion <[email protected]> | 2024-08-20 10:15:16 +0200 |
commit | 339124618aa57ee7c42cf39e8172ece02e9a54db (patch) | |
tree | c30a97d9eeac82373319c34fa34b978f87292993 | |
parent | c898e652c9a19debc34556d4a3e5f3a1f0da76d7 (diff) |
better handling of multiple channels
-rw-r--r-- | include/astal/wireplumber/endpoint.h | 2 | ||||
-rw-r--r-- | src/audio.c | 65 | ||||
-rw-r--r-- | src/device.c | 75 | ||||
-rw-r--r-- | src/endpoint.c | 92 |
4 files changed, 206 insertions, 28 deletions
diff --git a/include/astal/wireplumber/endpoint.h b/include/astal/wireplumber/endpoint.h index 6ae8b94..6ef0329 100644 --- a/include/astal/wireplumber/endpoint.h +++ b/include/astal/wireplumber/endpoint.h @@ -26,6 +26,8 @@ void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume); void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute); gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self); void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default); +gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self); +void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels); AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self); guint astal_wp_endpoint_get_id(AstalWpEndpoint *self); diff --git a/src/audio.c b/src/audio.c index e57f1d0..9c2841b 100644 --- a/src/audio.c +++ b/src/audio.c @@ -54,7 +54,9 @@ static GParamSpec *astal_wp_audio_properties[ASTAL_WP_AUDIO_N_PROPERTIES] = { * @self: the AstalWpAudio object * @id: the id of the endpoint * - * Returns: (transfer none) (nullable): the speaker with the given id + * gets the speaker with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -70,7 +72,9 @@ AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id) { * @self: the AstalWpAudio object * @id: the id of the endpoint * - * Returns: (transfer none) (nullable): the microphone with the given id + * gets the microphone with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -86,7 +90,9 @@ AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id) { * @self: the AstalWpAudio object * @id: the id of the endpoint * - * Returns: (transfer none) (nullable): the recorder with the given id + * gets the recorder with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -102,7 +108,9 @@ AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id) { * @self: the AstalWpAudio object * @id: the id of the endpoint * - * Returns: (transfer none) (nullable): the stream with the given id + * gets the stream with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -118,7 +126,9 @@ AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) { * @self: the AstalWpAudio object * @id: the id of the device * - * Returns: (transfer none) (nullable): the device with the given id + * gets the device with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -130,8 +140,9 @@ AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id) { * astal_wp_audio_get_microphones: * @self: the AstalWpAudio object * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * microphones + * a GList containing the microphones + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) */ GList *astal_wp_audio_get_microphones(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -151,8 +162,9 @@ GList *astal_wp_audio_get_microphones(AstalWpAudio *self) { * astal_wp_audio_get_speakers: * @self: the AstalWpAudio object * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * speaker + * a GList containing the speakers + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) */ GList *astal_wp_audio_get_speakers(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -172,8 +184,9 @@ GList *astal_wp_audio_get_speakers(AstalWpAudio *self) { * astal_wp_audio_get_recorders: * @self: the AstalWpAudio object * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * recorders + * a GList containing the recorders + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) */ GList *astal_wp_audio_get_recorders(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -193,8 +206,9 @@ GList *astal_wp_audio_get_recorders(AstalWpAudio *self) { * astal_wp_audio_get_streams: * @self: the AstalWpAudio object * - * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the - * streams + * a GList containing the streams + * + * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)) */ GList *astal_wp_audio_get_streams(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -214,8 +228,9 @@ GList *astal_wp_audio_get_streams(AstalWpAudio *self) { * astal_wp_audio_get_devices: * @self: the AstalWpAudio object * - * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)): a GList containing the - * devices + * a GList containing the devices + * + * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)) */ GList *astal_wp_audio_get_devices(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -236,7 +251,9 @@ GList *astal_wp_audio_get_devices(AstalWpAudio *self) { * @self: the AstalWpAudio object * @id: the id of the endpoint * - * Returns: (transfer none) (nullable): the endpoint with the given id + * the endpoint with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -248,7 +265,9 @@ AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) { /** * astal_wp_audio_get_default_speaker * - * Returns: (nullable) (transfer none): gets the default speaker object + * gets the default speaker object + * + * Returns: (nullable) (transfer none) */ AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -258,7 +277,9 @@ AstalWpEndpoint *astal_wp_audio_get_default_speaker(AstalWpAudio *self) { /** * astal_wp_audio_get_default_microphone * - * Returns: (nullable) (transfer none): gets the default microphone object + * gets the default microphone object + * + * Returns: (nullable) (transfer none) */ AstalWpEndpoint *astal_wp_audio_get_default_microphone(AstalWpAudio *self) { AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self); @@ -364,7 +385,9 @@ static void astal_wp_audio_object_removed(AstalWpAudio *self, gpointer object) { /** * astal_wp_audio_get_default * - * Returns: (nullable) (transfer none): gets the default audio object. + * gets the default audio object. + * + * Returns: (nullable) (transfer none) */ AstalWpAudio *astal_wp_audio_get_default() { static AstalWpAudio *self = NULL; @@ -377,7 +400,9 @@ AstalWpAudio *astal_wp_audio_get_default() { /** * astal_wp_get_default_audio * - * Returns: (nullable) (transfer none): gets the default audio object. + * gets the default audio object. This function does the same as [[email protected]_default] + * + * Returns: (nullable) (transfer none) */ AstalWpAudio *astal_wp_get_default_audio() { return astal_wp_audio_get_default(); } diff --git a/src/device.c b/src/device.c index c54e850..af0760c 100644 --- a/src/device.c +++ b/src/device.c @@ -38,19 +38,62 @@ static GParamSpec *astal_wp_device_properties[ASTAL_WP_DEVICE_N_PROPERTIES] = { NULL, }; +/** + * astal_wp_device_get_id + * @self: the AstalWpDevice object + * + * gets the id of this device + * + */ guint astal_wp_device_get_id(AstalWpDevice *self) { return self->id; } +/** + * astal_wp_device_get_description + * @self: the AstalWpDevice object + * + * gets the description of this device + * + */ const gchar *astal_wp_device_get_description(AstalWpDevice *self) { return self->description; } +/** + * astal_wp_device_get_icon + * @self: the AstalWpDevice object + * + * gets the icon of this device + * + */ const gchar *astal_wp_device_get_icon(AstalWpDevice *self) { g_return_val_if_fail(self != NULL, "audio-card-symbolic"); return self->icon; } +/** + * astal_wp_device_get_device_type + * @self: the AstalWpDevice object + * + * gets the type of this device + * + */ AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self) { return self->type; } +/** + * astal_wp_device_get_active_profile + * @self: the AstalWpDevice object + * + * gets the currently active profile of this device + * + */ gint astal_wp_device_get_active_profile(AstalWpDevice *self) { return self->active_profile; } +/** + * astal_wp_device_set_active_profile + * @self: the AstalWpDevice object + * @profile_id: the id of the profile + * + * sets the profile for this device + * + */ void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id) { AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); @@ -69,7 +112,9 @@ void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id) { * @self: the AstalWpDevice object * @id: the id of the profile * - * Returns: (transfer none) (nullable): the profile with the given id + * gets the profile with the given id + * + * Returns: (transfer none) (nullable) */ AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id) { AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); @@ -81,8 +126,9 @@ AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id) { * astal_wp_device_get_profiles: * @self: the AstalWpDevice object * - * Returns: (transfer container) (nullable) (type GList(AstalWpProfile)): a GList containing the - * profiles + * gets a GList containing the profiles + * + * Returns: (transfer container) (nullable) (type GList(AstalWpProfile)) */ GList *astal_wp_device_get_profiles(AstalWpDevice *self) { AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self); @@ -275,11 +321,25 @@ static void astal_wp_device_class_init(AstalWpDeviceClass *class) { object_class->finalize = astal_wp_device_finalize; object_class->get_property = astal_wp_device_get_property; object_class->set_property = astal_wp_device_set_property; - + /** + * AstalWpDevice:id + * + * The id of this device. + */ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ID] = g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE); + /** + * AstalWpDevice:description + * + * The description of this device. + */ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DESCRIPTION] = g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE); + /** + * AstalWpDevice:icon + * + * The icon name for this device. + */ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ICON] = g_param_spec_string("icon", "icon", "icon", NULL, G_PARAM_READABLE); /** @@ -293,10 +353,15 @@ static void astal_wp_device_class_init(AstalWpDeviceClass *class) { /** * AstalWpDevice:profiles: (type GList(AstalWpProfile)) (transfer container) * - * A list of AstalWpProfile objects + * A list of available profiles */ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_PROFILES] = g_param_spec_pointer("profiles", "profiles", "profiles", G_PARAM_READABLE); + /** + * AstalWpDevice:active-profile-id + * + * The id of the currently active profile. + */ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE] = g_param_spec_int("active-profile-id", "active-profile-id", "active-profile-id", G_MININT, G_MAXINT, 0, G_PARAM_READWRITE); diff --git a/src/endpoint.c b/src/endpoint.c index 31ac616..5ea1671 100644 --- a/src/endpoint.c +++ b/src/endpoint.c @@ -4,6 +4,7 @@ #include "device.h" #include "endpoint-private.h" +#include "glib.h" #include "wp.h" struct _AstalWpEndpoint { @@ -16,6 +17,7 @@ struct _AstalWpEndpoint { gchar *name; AstalWpMediaClass type; gboolean is_default; + gboolean lock_channels; gchar *icon; }; @@ -55,6 +57,7 @@ typedef enum { ASTAL_WP_ENDPOINT_PROP_DEFAULT, ASTAL_WP_ENDPOINT_PROP_ICON, ASTAL_WP_ENDPOINT_PROP_VOLUME_ICON, + ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS, ASTAL_WP_ENDPOINT_N_PROPERTIES, } AstalWpEndpointProperties; @@ -65,9 +68,10 @@ static GParamSpec *astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_N_PROPERTIES] void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) { AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); - gdouble volume; + gdouble volume = 0; gboolean mute; GVariant *variant = NULL; + GVariantIter *channels = NULL; g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); @@ -75,6 +79,20 @@ void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) { g_variant_lookup(variant, "volume", "d", &volume); g_variant_lookup(variant, "mute", "b", &mute); + g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); + + if (channels != NULL) { + const gchar *key; + const gchar *channel_str; + gdouble channel_volume; + GVariant *varvol; + + while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { + g_variant_lookup(varvol, "volume", "d", &channel_volume); + g_variant_lookup(varvol, "channel", "&s", &channel_str); + if (channel_volume > volume) volume = channel_volume; + } + } if (mute != self->mute) { self->mute = mute; @@ -89,15 +107,68 @@ void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) { g_object_notify(G_OBJECT(self), "volume-icon"); } +/** + * astal_wp_endpoint_set_volume: + * @self: the AstalWpEndpoint object + * @volume: The new volume level to set. + * + * Sets the volume level for this endpoint. The volume is clamped to be between + * 0 and 1.5. + */ void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume) { AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); gboolean ret; if (volume >= 1.5) volume = 1.5; - GVariant *variant = g_variant_new_double(volume); - g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant, &ret); + if (volume <= 0) volume = 0; + + gboolean mute; + GVariant *variant = NULL; + GVariantIter *channels = NULL; + + g_auto(GVariantBuilder) vol_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_signal_emit_by_name(priv->mixer, "get-volume", self->id, &variant); + + if (variant == NULL) return; + + g_variant_lookup(variant, "mute", "b", &mute); + g_variant_lookup(variant, "channelVolumes", "a{sv}", &channels); + + if (channels != NULL && !self->lock_channels) { + g_auto(GVariantBuilder) channel_volumes_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + + const gchar *key; + const gchar *channel_str; + gdouble channel_volume; + GVariant *varvol; + + while (g_variant_iter_loop(channels, "{&sv}", &key, &varvol)) { + g_auto(GVariantBuilder) channel_b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT); + g_variant_lookup(varvol, "volume", "d", &channel_volume); + g_variant_lookup(varvol, "channel", "&s", &channel_str); + gdouble vol = self->volume == 0 ? volume : channel_volume * volume / self->volume; + g_variant_builder_add(&channel_b, "{sv}", "volume", g_variant_new_double(vol)); + g_variant_builder_add(&channel_volumes_b, "{sv}", key, + g_variant_builder_end(&channel_b)); + } + + g_variant_builder_add(&vol_b, "{sv}", "channelVolumes", + g_variant_builder_end(&channel_volumes_b)); + } else { + GVariant *volume_variant = g_variant_new_double(volume); + g_variant_builder_add(&vol_b, "{sv}", "volume", volume_variant); + } + + g_signal_emit_by_name(priv->mixer, "set-volume", self->id, g_variant_builder_end(&vol_b), &ret); } +/** + * astal_wp_endpoint_set_mute: + * @self: the AstalWpEndpoint instance. + * @mute: A boolean indicating whether to mute the endpoint. + * + * Sets the mute status for the endpoint. + */ void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute) { AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self); @@ -139,6 +210,13 @@ void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default &ret); } +gboolean astal_wp_endpoint_get_lock_channels(AstalWpEndpoint *self) { return self->lock_channels; } + +void astal_wp_endpoint_set_lock_channels(AstalWpEndpoint *self, gboolean lock_channels) { + self->lock_channels = lock_channels; + astal_wp_endpoint_set_volume(self, self->volume); +} + const gchar *astal_wp_endpoint_get_volume_icon(AstalWpEndpoint *self) { if (self->mute) return "audio-volume-muted-symbolic"; if (self->volume <= 0.33) return "audio-volume-low-symbolic"; @@ -179,6 +257,9 @@ static void astal_wp_endpoint_get_property(GObject *object, guint property_id, G case ASTAL_WP_ENDPOINT_PROP_DEFAULT: g_value_set_boolean(value, self->is_default); break; + case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: + g_value_set_boolean(value, self->lock_channels); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -203,6 +284,9 @@ static void astal_wp_endpoint_set_property(GObject *object, guint property_id, c g_free(self->icon); self->icon = g_strdup(g_value_get_string(value)); break; + case ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS: + astal_wp_endpoint_set_lock_channels(self, g_value_get_boolean(value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); break; @@ -424,6 +508,8 @@ static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) { G_PARAM_READABLE); astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DEFAULT] = g_param_spec_boolean("is_default", "is_default", "is_default", FALSE, G_PARAM_READWRITE); + astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_LOCK_CHANNELS] = g_param_spec_boolean( + "lock_channels", "lock_channels", "lock channels", FALSE, G_PARAM_READWRITE); g_object_class_install_properties(object_class, ASTAL_WP_ENDPOINT_N_PROPERTIES, astal_wp_endpoint_properties); |