summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkotontrion <[email protected]>2024-08-20 10:15:16 +0200
committerkotontrion <[email protected]>2024-08-20 10:15:16 +0200
commit339124618aa57ee7c42cf39e8172ece02e9a54db (patch)
treec30a97d9eeac82373319c34fa34b978f87292993
parentc898e652c9a19debc34556d4a3e5f3a1f0da76d7 (diff)
better handling of multiple channels
-rw-r--r--include/astal/wireplumber/endpoint.h2
-rw-r--r--src/audio.c65
-rw-r--r--src/device.c75
-rw-r--r--src/endpoint.c92
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);