summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkotontrion <[email protected]>2024-07-20 15:53:07 +0200
committerkotontrion <[email protected]>2024-07-20 15:53:07 +0200
commitaf2ae01f168d3eb1f622de0112c959e1390883e0 (patch)
tree4629758737435217bf4725ecbf647ce9125bddb5 /src
parent86bff65898178fe6e58e8d02c9b6e588003621bc (diff)
add audio devices and profiles
Diffstat (limited to 'src')
-rw-r--r--src/audio.c86
-rw-r--r--src/device.c285
-rw-r--r--src/endpoint.c9
-rw-r--r--src/meson.build2
-rw-r--r--src/profile.c83
-rw-r--r--src/wireplumber.c109
6 files changed, 539 insertions, 35 deletions
diff --git a/src/audio.c b/src/audio.c
index 53224a7..081bb6c 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -2,6 +2,7 @@
#include <wp/wp.h>
+#include "device.h"
#include "endpoint.h"
#include "wp.h"
@@ -25,6 +26,8 @@ typedef enum {
ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED,
ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED,
ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED,
+ ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED,
+ ASTAL_WP_AUDIO_SIGNAL_DEVICE_REMOVED,
ASTAL_WP_AUDIO_N_SIGNALS
} AstalWpWpSignals;
@@ -37,6 +40,7 @@ typedef enum {
ASTAL_WP_AUDIO_PROP_SPEAKERS,
ASTAL_WP_AUDIO_PROP_STREAMS,
ASTAL_WP_AUDIO_PROP_RECORDERS,
+ ASTAL_WP_AUDIO_PROP_DEVICES,
ASTAL_WP_AUDIO_PROP_DEFAULT_SPEAKER,
ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE,
ASTAL_WP_AUDIO_N_PROPERTIES,
@@ -111,6 +115,19 @@ AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) {
}
/**
+ * astal_wp_audio_get_device:
+ * @self: the AstalWpAudio object
+ * @id: the id of the device
+ *
+ * Returns: (transfer none) (nullable): the device with the given id
+ */
+AstalWpDevice *astal_wp_audio_get_device(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ return astal_wp_wp_get_device(priv->wp, id);
+}
+
+/**
* astal_wp_audio_get_microphones:
* @self: the AstalWpAudio object
*
@@ -195,6 +212,28 @@ 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
+ */
+GList *astal_wp_audio_get_devices(AstalWpAudio *self) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_devices(priv->wp);
+ // GList *mics = NULL;
+
+ // for (GList *l = eps; l != NULL; l = l->next) {
+ // if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE) {
+ // mics = g_list_append(mics, l->data);
+ // }
+ // }
+ // g_list_free(eps);
+ // return mics;
+ return eps;
+}
+
+/**
* astal_wp_audio_get_endpoint:
* @self: the AstalWpAudio object
* @id: the id of the endpoint
@@ -248,6 +287,9 @@ static void astal_wp_audio_get_property(GObject *object, guint property_id, GVal
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_DEVICES:
+ g_value_set_pointer(value, astal_wp_audio_get_devices(self));
+ break;
case ASTAL_WP_AUDIO_PROP_DEFAULT_MICROPHONE:
g_value_set_object(value, astal_wp_audio_get_default_microphone(self));
break;
@@ -257,6 +299,20 @@ static void astal_wp_audio_get_property(GObject *object, guint property_id, GVal
}
}
+static void astal_wp_audio_device_added(AstalWpAudio *self, gpointer object) {
+ AstalWpDevice *endpoint = ASTAL_WP_DEVICE(object);
+ g_signal_emit_by_name(self, "device-added", endpoint);
+ g_object_notify(G_OBJECT(self), "devices");
+ g_signal_emit_by_name(self, "changed");
+}
+
+static void astal_wp_audio_device_removed(AstalWpAudio *self, gpointer object) {
+ AstalWpDevice *endpoint = ASTAL_WP_DEVICE(object);
+ g_signal_emit_by_name(self, "device-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "devices");
+ g_signal_emit_by_name(self, "changed");
+}
+
static void astal_wp_audio_object_added(AstalWpAudio *self, gpointer object) {
AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object);
switch (astal_wp_endpoint_get_media_class(endpoint)) {
@@ -325,16 +381,6 @@ AstalWpAudio *astal_wp_audio_get_default() {
*/
AstalWpAudio *astal_wp_get_default_audio() { return astal_wp_audio_get_default(); }
-static void astal_wp_audio_dispose(GObject *object) {
- AstalWpAudio *self = ASTAL_WP_AUDIO(object);
- AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
-}
-
-static void astal_wp_audio_finalize(GObject *object) {
- AstalWpAudio *self = ASTAL_WP_AUDIO(object);
- AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
-}
-
static void astal_wp_audio_init(AstalWpAudio *self) {
AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
@@ -344,12 +390,14 @@ static void astal_wp_audio_init(AstalWpAudio *self) {
self);
g_signal_connect_swapped(priv->wp, "endpoint-removed",
G_CALLBACK(astal_wp_audio_object_removed), self);
+ g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_audio_device_added),
+ self);
+ g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_audio_device_removed),
+ self);
}
static void astal_wp_audio_class_init(AstalWpAudioClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS(class);
- object_class->finalize = astal_wp_audio_finalize;
- object_class->dispose = astal_wp_audio_dispose;
object_class->get_property = astal_wp_audio_get_property;
/**
@@ -381,6 +429,13 @@ 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:devices: (type GList(AstalWpDevice)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_DEVICES] =
+ g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE);
+ /**
* AstalWpAudio:default-speaker:
*
* The AstalWndpoint object representing the default speaker
@@ -424,6 +479,13 @@ static void astal_wp_audio_class_init(AstalWpAudioClass *class) {
astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED] =
g_signal_new("recorder-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_DEVICE_ADDED] =
+ g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
+ astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED] =
+ g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
+
astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_CHANGED] =
g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);
diff --git a/src/device.c b/src/device.c
new file mode 100644
index 0000000..7b6629f
--- /dev/null
+++ b/src/device.c
@@ -0,0 +1,285 @@
+#include "device-private.h"
+
+#include <wp/wp.h>
+#include "profile.h"
+
+
+struct _AstalWpDevice {
+ GObject parent_instance;
+
+ guint id;
+ gchar *description;
+ gchar *icon;
+ gint active_profile;
+};
+
+typedef struct {
+ WpDevice *device;
+ GHashTable *profiles;
+} AstalWpDevicePrivate;
+
+G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpDevice, astal_wp_device, G_TYPE_OBJECT);
+
+typedef enum {
+ ASTAL_WP_DEVICE_PROP_ID = 1,
+ ASTAL_WP_DEVICE_PROP_DESCRIPTION,
+ ASTAL_WP_DEVICE_PROP_ICON,
+ ASTAL_WP_DEVICE_PROP_PROFILES,
+ ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE,
+ ASTAL_WP_DEVICE_N_PROPERTIES,
+} AstalWpDeviceProperties;
+
+typedef enum { ASTAL_WP_DEVICE_SIGNAL_CHANGED, ASTAL_WP_DEVICE_N_SIGNALS } AstalWpDeviceSignals;
+
+static guint astal_wp_device_signals[ASTAL_WP_DEVICE_N_SIGNALS] = {
+ 0,
+};
+static GParamSpec *astal_wp_device_properties[ASTAL_WP_DEVICE_N_PROPERTIES] = {
+ NULL,
+};
+
+guint astal_wp_device_get_id(AstalWpDevice *self) { return self->id; }
+
+const gchar *astal_wp_device_get_description(AstalWpDevice *self) { return self->description; }
+
+const gchar *astal_wp_device_get_icon(AstalWpDevice *self) { return self->icon; }
+
+gint astal_wp_device_get_active_profile(AstalWpDevice *self) { return self->active_profile; }
+
+void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+
+ WpSpaPodBuilder *builder =
+ wp_spa_pod_builder_new_object("Spa:Pod:Object:Param:Profile", "Profile");
+ wp_spa_pod_builder_add_property(builder, "index");
+ wp_spa_pod_builder_add_int(builder, profile_id);
+ WpSpaPod *pod = wp_spa_pod_builder_end(builder);
+ wp_pipewire_object_set_param(WP_PIPEWIRE_OBJECT(priv->device), "Profile", 0, pod);
+
+ wp_spa_pod_unref(pod);
+ wp_spa_pod_builder_unref(builder);
+}
+
+/**
+ * astal_wp_device_get_profile:
+ * @self: the AstalWpDevice object
+ * @id: the id of the profile
+ *
+ * Returns: (transfer none) (nullable): the profile with the given id
+ */
+AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+
+ return g_hash_table_lookup(priv->profiles, GINT_TO_POINTER(id));
+}
+
+/**
+ * astal_wp_device_get_profiles:
+ * @self: the AstalWpDevice object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpProfile)): a GList containing the
+ * profiles
+ */
+GList *astal_wp_device_get_profiles(AstalWpDevice *self) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+ return g_hash_table_get_values(priv->profiles);
+}
+
+static void astal_wp_device_get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *pspec) {
+ AstalWpDevice *self = ASTAL_WP_DEVICE(object);
+
+ switch (property_id) {
+ case ASTAL_WP_DEVICE_PROP_ID:
+ g_value_set_uint(value, self->id);
+ break;
+ case ASTAL_WP_DEVICE_PROP_DESCRIPTION:
+ g_value_set_string(value, self->description);
+ break;
+ case ASTAL_WP_DEVICE_PROP_ICON:
+ g_value_set_string(value, self->icon);
+ break;
+ case ASTAL_WP_DEVICE_PROP_PROFILES:
+ g_value_set_pointer(value, astal_wp_device_get_profiles(self));
+ break;
+ case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE:
+ g_value_set_int(value, self->active_profile);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_wp_device_set_property(GObject *object, guint property_id, const GValue *value,
+ GParamSpec *pspec) {
+ AstalWpDevice *self = ASTAL_WP_DEVICE(object);
+
+ switch (property_id) {
+ case ASTAL_WP_DEVICE_PROP_ACTIVE_PROFILE:
+ astal_wp_device_set_active_profile(self, g_value_get_int(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_wp_device_update_profiles(AstalWpDevice *self) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+ g_hash_table_remove_all(priv->profiles);
+
+ WpIterator *iter =
+ wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "EnumProfile", NULL);
+ GValue profile = G_VALUE_INIT;
+ while (wp_iterator_next(iter, &profile)) {
+ WpSpaPod *pod = g_value_get_boxed(&profile);
+
+ gint index;
+ gchar *description;
+ wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description,
+ NULL);
+
+ g_hash_table_insert(
+ priv->profiles, GINT_TO_POINTER(index),
+ g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL));
+ }
+ wp_iterator_unref(iter);
+
+ g_object_notify(G_OBJECT(self), "profiles");
+}
+
+static void astal_wp_device_update_active_profile(AstalWpDevice *self) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+
+ WpIterator *iter =
+ wp_pipewire_object_enum_params_sync(WP_PIPEWIRE_OBJECT(priv->device), "Profile", NULL);
+ GValue profile = G_VALUE_INIT;
+ while (wp_iterator_next(iter, &profile)) {
+ WpSpaPod *pod = g_value_get_boxed(&profile);
+
+ gint index;
+ gchar *description;
+ wp_spa_pod_get_object(pod, NULL, "index", "i", &index, "description", "s", &description,
+ NULL);
+
+ g_hash_table_insert(
+ priv->profiles, GINT_TO_POINTER(index),
+ g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL));
+
+ self->active_profile = index;
+ }
+ wp_iterator_unref(iter);
+
+ g_object_notify(G_OBJECT(self), "active-profile-id");
+}
+
+static void astal_wp_device_params_changed(AstalWpDevice *self, const gchar *prop) {
+ if (g_strcmp0(prop, "EnumProfile") == 0) {
+ astal_wp_device_update_profiles(self);
+ } else if (g_strcmp0(prop, "Profile") == 0) {
+ astal_wp_device_update_active_profile(self);
+ }
+}
+
+static void astal_wp_device_update_properties(AstalWpDevice *self) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+ if (priv->device == NULL) return;
+ self->id = wp_proxy_get_bound_id(WP_PROXY(priv->device));
+
+ const gchar *description =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.description");
+ if (description == NULL) {
+ description =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.name");
+ }
+ if (description == NULL) {
+ description = "unknown";
+ }
+ g_free(self->description);
+ self->description = g_strdup(description);
+
+ const gchar *icon =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.icon-name");
+ if (description == NULL) {
+ icon = "soundcard-symbolic";
+ }
+ g_free(self->icon);
+ self->icon = g_strdup(icon);
+
+ astal_wp_device_update_profiles(self);
+ astal_wp_device_update_active_profile(self);
+
+ g_object_notify(G_OBJECT(self), "id");
+ g_object_notify(G_OBJECT(self), "icon");
+ g_object_notify(G_OBJECT(self), "description");
+ g_signal_emit_by_name(self, "changed");
+}
+
+AstalWpDevice *astal_wp_device_create(WpDevice *device) {
+ AstalWpDevice *self = g_object_new(ASTAL_WP_TYPE_DEVICE, NULL);
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+
+ priv->device = g_object_ref(device);
+
+ g_signal_connect_swapped(priv->device, "params-changed",
+ G_CALLBACK(astal_wp_device_params_changed), self);
+
+ astal_wp_device_update_properties(self);
+ return self;
+}
+
+static void astal_wp_device_init(AstalWpDevice *self) {
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+ priv->device = NULL;
+
+ priv->profiles = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
+
+ self->description = NULL;
+ self->icon = NULL;
+}
+
+static void astal_wp_device_dispose(GObject *object) {
+ AstalWpDevice *self = ASTAL_WP_DEVICE(object);
+ AstalWpDevicePrivate *priv = astal_wp_device_get_instance_private(self);
+
+ g_clear_object(&priv->device);
+}
+
+static void astal_wp_device_finalize(GObject *object) {
+ AstalWpDevice *self = ASTAL_WP_DEVICE(object);
+ g_free(self->description);
+ g_free(self->icon);
+}
+
+static void astal_wp_device_class_init(AstalWpDeviceClass *class) {
+ GObjectClass *object_class = G_OBJECT_CLASS(class);
+ object_class->dispose = astal_wp_device_dispose;
+ 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;
+
+ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ID] =
+ g_param_spec_uint("id", "id", "id", 0, UINT_MAX, 0, G_PARAM_READABLE);
+ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DESCRIPTION] =
+ g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE);
+ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_ICON] =
+ g_param_spec_string("icon", "icon", "icon", NULL, G_PARAM_READABLE);
+ /**
+ * AstalWpDevice:profiles: (type GList(AstalWpProfile)) (transfer container)
+ *
+ * A list of AstalWpProfile objects
+ */
+ astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_PROFILES] =
+ g_param_spec_pointer("profiles", "profiles", "profiles", G_PARAM_READABLE);
+ 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);
+
+ g_object_class_install_properties(object_class, ASTAL_WP_DEVICE_N_PROPERTIES,
+ astal_wp_device_properties);
+
+ astal_wp_device_signals[ASTAL_WP_DEVICE_SIGNAL_CHANGED] =
+ g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
diff --git a/src/endpoint.c b/src/endpoint.c
index 7d7a275..d6f4ccb 100644
--- a/src/endpoint.c
+++ b/src/endpoint.c
@@ -203,8 +203,9 @@ static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) {
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;
+ GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_MEDIA_CLASS);
+ if (g_enum_get_value_by_nick(enum_class, type) != NULL)
+ 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");
@@ -216,7 +217,7 @@ static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) {
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);
+ 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);
@@ -318,8 +319,6 @@ static void astal_wp_endpoint_dispose(GObject *object) {
g_signal_handler_disconnect(priv->defaults, priv->default_signal_handler_id);
g_signal_handler_disconnect(priv->mixer, priv->mixer_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/meson.build b/src/meson.build
index ae56c6f..ce27c3d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,6 +1,8 @@
srcs = files(
'wireplumber.c',
'endpoint.c',
+ 'device.c',
+ 'profile.c',
'audio.c',
)
diff --git a/src/profile.c b/src/profile.c
new file mode 100644
index 0000000..cb32240
--- /dev/null
+++ b/src/profile.c
@@ -0,0 +1,83 @@
+#include <wp/wp.h>
+#include "profile.h"
+
+struct _AstalWpProfile {
+ GObject parent_instance;
+
+ gint index;
+ gchar *description;
+};
+
+G_DEFINE_FINAL_TYPE(AstalWpProfile, astal_wp_profile, G_TYPE_OBJECT);
+
+typedef enum {
+ ASTAL_WP_PROFILE_PROP_INDEX = 1,
+ ASTAL_WP_PROFILE_PROP_DESCRIPTION,
+ ASTAL_WP_PROFILE_N_PROPERTIES,
+} AstalWpProfileProperties;
+
+static GParamSpec *astal_wp_profile_properties[ASTAL_WP_PROFILE_N_PROPERTIES] = {
+ NULL,
+};
+
+gint astal_wp_profile_get_index(AstalWpProfile *self) { return self->index; }
+
+const gchar *astal_wp_profile_get_description(AstalWpProfile *self) { return self->description; }
+
+static void astal_wp_profile_get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *pspec) {
+ AstalWpProfile *self = ASTAL_WP_PROFILE(object);
+
+ switch (property_id) {
+ case ASTAL_WP_PROFILE_PROP_INDEX:
+ g_value_set_int(value, self->index);
+ break;
+ case ASTAL_WP_PROFILE_PROP_DESCRIPTION:
+ g_value_set_string(value, self->description);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_wp_profile_set_property(GObject *object, guint property_id, const GValue *value,
+ GParamSpec *pspec) {
+ AstalWpProfile *self = ASTAL_WP_PROFILE(object);
+
+ switch (property_id) {
+ case ASTAL_WP_PROFILE_PROP_INDEX:
+ self->index = g_value_get_int(value);
+ break;
+ case ASTAL_WP_PROFILE_PROP_DESCRIPTION:
+ g_free(self->description);
+ self->description = g_strdup(g_value_get_string(value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void astal_wp_profile_init(AstalWpProfile *self) { self->description = NULL; }
+
+static void astal_wp_profile_finalize(GObject *object) {
+ AstalWpProfile *self = ASTAL_WP_PROFILE(object);
+ g_free(self->description);
+}
+
+static void astal_wp_profile_class_init(AstalWpProfileClass *class) {
+ GObjectClass *object_class = G_OBJECT_CLASS(class);
+ object_class->finalize = astal_wp_profile_finalize;
+ object_class->get_property = astal_wp_profile_get_property;
+ object_class->set_property = astal_wp_profile_set_property;
+
+ astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_DESCRIPTION] =
+ g_param_spec_string("description", "description", "description", NULL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ astal_wp_profile_properties[ASTAL_WP_PROFILE_PROP_INDEX] =
+ g_param_spec_int("index", "index", "index", G_MININT, G_MAXINT, 0,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_properties(object_class, ASTAL_WP_PROFILE_N_PROPERTIES,
+ astal_wp_profile_properties);
+}
diff --git a/src/wireplumber.c b/src/wireplumber.c
index 6bf712a..527f020 100644
--- a/src/wireplumber.c
+++ b/src/wireplumber.c
@@ -1,6 +1,7 @@
#include <wp/wp.h>
#include "audio.h"
+#include "device-private.h"
#include "endpoint-private.h"
#include "wp.h"
@@ -20,6 +21,7 @@ typedef struct {
gint pending_plugins;
GHashTable *endpoints;
+ GHashTable *devices;
} AstalWpWpPrivate;
G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpWp, astal_wp_wp, G_TYPE_OBJECT);
@@ -28,6 +30,8 @@ typedef enum {
ASTAL_WP_WP_SIGNAL_CHANGED,
ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED,
ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED,
+ ASTAL_WP_WP_SIGNAL_DEVICE_ADDED,
+ ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED,
ASTAL_WP_WP_N_SIGNALS
} AstalWpWpSignals;
@@ -38,6 +42,7 @@ 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_DEVICES,
ASTAL_WP_WP_PROP_DEFAULT_SPEAKER,
ASTAL_WP_WP_PROP_DEFAULT_MICROPHONE,
ASTAL_WP_WP_N_PROPERTIES,
@@ -74,6 +79,32 @@ GList *astal_wp_wp_get_endpoints(AstalWpWp *self) {
}
/**
+ * astal_wp_wp_get_device:
+ * @self: the AstalWpWp object
+ * @id: the id of the device
+ *
+ * Returns: (transfer none) (nullable): the device with the given id
+ */
+AstalWpDevice *astal_wp_wp_get_device(AstalWpWp *self, guint id) {
+ AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
+
+ AstalWpDevice *device = g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id));
+ return device;
+}
+
+/**
+ * astal_wp_wp_get_devices:
+ * @self: the AstalWpWp object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpDevice)): a GList containing the
+ * devices
+ */
+GList *astal_wp_wp_get_devices(AstalWpWp *self) {
+ AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
+ return g_hash_table_get_values(priv->devices);
+}
+
+/**
* astal_wp_wp_get_audio
*
* Returns: (nullable) (transfer none): gets the audio object
@@ -108,6 +139,9 @@ 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_DEVICES:
+ g_value_set_pointer(value, g_hash_table_get_values(priv->devices));
+ break;
case ASTAL_WP_WP_PROP_DEFAULT_SPEAKER:
g_value_set_object(value, self->default_speaker);
break;
@@ -136,30 +170,49 @@ static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) {
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
- WpNode *node = WP_NODE(object);
- AstalWpEndpoint *endpoint = astal_wp_endpoint_create(node, priv->mixer, priv->defaults);
-
- g_hash_table_insert(priv->endpoints, GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))),
- endpoint);
-
- g_signal_emit_by_name(self, "endpoint-added", endpoint);
- g_object_notify(G_OBJECT(self), "endpoints");
+ if (WP_IS_NODE(object)) {
+ WpNode *node = WP_NODE(object);
+ AstalWpEndpoint *endpoint = astal_wp_endpoint_create(node, priv->mixer, priv->defaults);
+
+ g_hash_table_insert(priv->endpoints,
+ GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(node))), endpoint);
+
+ g_signal_emit_by_name(self, "endpoint-added", endpoint);
+ g_object_notify(G_OBJECT(self), "endpoints");
+ } else if (WP_IS_DEVICE(object)) {
+ WpDevice *node = WP_DEVICE(object);
+ AstalWpDevice *device = astal_wp_device_create(node);
+ g_hash_table_insert(priv->devices,
+ GUINT_TO_POINTER(wp_proxy_get_bound_id(WP_PROXY(device))), device);
+ g_signal_emit_by_name(self, "device-added", device);
+ g_object_notify(G_OBJECT(self), "devices");
+ }
g_signal_emit_by_name(self, "changed");
}
static void astal_wp_wp_object_removed(AstalWpWp *self, gpointer object) {
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
- WpNode *node = WP_NODE(object);
-
- guint id = wp_proxy_get_bound_id(WP_PROXY(node));
-
- AstalWpEndpoint *endpoint = g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id));
-
- g_hash_table_remove(priv->endpoints, GUINT_TO_POINTER(id));
-
- g_signal_emit_by_name(self, "endpoint-removed", endpoint);
- g_object_notify(G_OBJECT(self), "endpoints");
+ if (WP_IS_NODE(object)) {
+ guint id = wp_proxy_get_bound_id(WP_PROXY(object));
+ AstalWpEndpoint *endpoint =
+ g_object_ref(g_hash_table_lookup(priv->endpoints, GUINT_TO_POINTER(id)));
+
+ g_hash_table_remove(priv->endpoints, GUINT_TO_POINTER(id));
+
+ g_signal_emit_by_name(self, "endpoint-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "endpoints");
+ g_object_unref(endpoint);
+ } else if (WP_IS_DEVICE(object)) {
+ guint id = wp_proxy_get_bound_id(WP_PROXY(object));
+ AstalWpDevice *device =
+ g_object_ref(g_hash_table_lookup(priv->devices, GUINT_TO_POINTER(id)));
+ g_hash_table_remove(priv->devices, GUINT_TO_POINTER(id));
+
+ g_signal_emit_by_name(self, "device-removed", device);
+ g_object_notify(G_OBJECT(self), "devices");
+ g_object_unref(device);
+ }
g_signal_emit_by_name(self, "changed");
}
@@ -261,6 +314,7 @@ static void astal_wp_wp_init(AstalWpWp *self) {
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
priv->endpoints = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
+ priv->devices = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_object_unref);
wp_init(WP_INIT_ALL);
priv->core = wp_core_new(NULL, NULL, NULL);
@@ -272,6 +326,8 @@ static void astal_wp_wp_init(AstalWpWp *self) {
priv->obj_manager = wp_object_manager_new();
wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_NODE,
WP_OBJECT_FEATURES_ALL);
+ wp_object_manager_request_object_features(priv->obj_manager, WP_TYPE_GLOBAL_PROXY,
+ WP_OBJECT_FEATURES_ALL);
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
"media.class", "=s", "Audio/Sink", NULL);
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
@@ -280,6 +336,10 @@ static void astal_wp_wp_init(AstalWpWp *self) {
"media.class", "=s", "Stream/Output/Audio", NULL);
wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
"media.class", "=s", "Stream/Input/Audio", NULL);
+ wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE,
+ WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s",
+ "Audio/Device", NULL);
+ // wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_CLIENT, NULL);
g_signal_connect_swapped(priv->obj_manager, "installed", (GCallback)astal_wp_wp_objm_installed,
self);
@@ -312,6 +372,13 @@ 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);
/**
+ * AstalWpWp:devices: (type GList(AstalWpDevice)) (transfer container)
+ *
+ * A list of AstalWpDevice objects
+ */
+ astal_wp_wp_properties[ASTAL_WP_WP_PROP_DEVICES] =
+ g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE);
+ /**
* AstalWpWp:default-speaker:
*
* The AstalWndpoint object representing the default speaker
@@ -337,6 +404,12 @@ static void astal_wp_wp_class_init(AstalWpWpClass *class) {
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_REMOVED] =
g_signal_new("endpoint-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL,
NULL, NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_ADDED] =
+ g_signal_new("device-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
+ astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_DEVICE_REMOVED] =
+ g_signal_new("device-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_DEVICE);
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_CHANGED] =
g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL,
G_TYPE_NONE, 0);