diff options
Diffstat (limited to 'lib/wireplumber/src/device.c')
-rw-r--r-- | lib/wireplumber/src/device.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/lib/wireplumber/src/device.c b/lib/wireplumber/src/device.c new file mode 100644 index 0000000..af0760c --- /dev/null +++ b/lib/wireplumber/src/device.c @@ -0,0 +1,371 @@ +#include <wp/wp.h> + +#include "device-private.h" +#include "profile.h" + +struct _AstalWpDevice { + GObject parent_instance; + + guint id; + gchar *description; + gchar *icon; + gint active_profile; + AstalWpDeviceType type; +}; + +typedef struct { + WpDevice *device; + GHashTable *profiles; +} AstalWpDevicePrivate; + +G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpDevice, astal_wp_device, G_TYPE_OBJECT); + +G_DEFINE_ENUM_TYPE(AstalWpDeviceType, astal_wp_device_type, + G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_AUDIO, "Audio/Device"), + G_DEFINE_ENUM_VALUE(ASTAL_WP_DEVICE_TYPE_VIDEO, "Video/Device")); + +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_PROP_DEVICE_TYPE, + ASTAL_WP_DEVICE_N_PROPERTIES, +} AstalWpDeviceProperties; + +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); + + 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_builder_unref(builder); +} + +/** + * astal_wp_device_get_profile: + * @self: the AstalWpDevice object + * @id: the id of the profile + * + * 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); + + return g_hash_table_lookup(priv->profiles, GINT_TO_POINTER(id)); +} + +/** + * astal_wp_device_get_profiles: + * @self: the AstalWpDevice object + * + * 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); + 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_DEVICE_TYPE: + g_value_set_enum(value, astal_wp_device_get_device_type(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); + if (iter == NULL) return; + 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)); + g_value_unset(&profile); + } + 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); + if (iter == NULL) return; + 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; + g_value_unset(&profile); + } + 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 (icon == NULL) { + icon = "audio-card-symbolic"; + } + g_free(self->icon); + self->icon = g_strdup(icon); + + const gchar *type = + wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "media.class"); + GEnumClass *enum_class = g_type_class_ref(ASTAL_WP_TYPE_DEVICE_TYPE); + 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); + + 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), "device-type"); + g_object_notify(G_OBJECT(self), "icon"); + g_object_notify(G_OBJECT(self), "description"); +} + +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; + /** + * 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); + /** + * AstalWpDevice:device-type: (type AstalWpDeviceType) + * + * The type of this device + */ + astal_wp_device_properties[ASTAL_WP_DEVICE_PROP_DEVICE_TYPE] = + g_param_spec_enum("device-type", "device-type", "device-type", ASTAL_WP_TYPE_DEVICE_TYPE, 1, + G_PARAM_READABLE); + /** + * AstalWpDevice:profiles: (type GList(AstalWpProfile)) (transfer container) + * + * 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); + + g_object_class_install_properties(object_class, ASTAL_WP_DEVICE_N_PROPERTIES, + astal_wp_device_properties); +} |