summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorkotontrion <[email protected]>2024-07-18 17:43:56 +0200
committerkotontrion <[email protected]>2024-07-18 17:43:56 +0200
commit9eb113163187e1eec0d2e0ac763cd9eeb45cc9ae (patch)
tree71a9cc11c732ef09311c262959b0b2035b792333 /src
parent35cd0fb43d30be311cfdfca05f8a6d98d844d284 (diff)
add Audio object and default nodes api
Diffstat (limited to 'src')
-rw-r--r--src/audio.c388
-rw-r--r--src/endpoint.c115
-rw-r--r--src/meson.build1
-rw-r--r--src/wireplumber.c83
4 files changed, 559 insertions, 28 deletions
diff --git a/src/audio.c b/src/audio.c
new file mode 100644
index 0000000..630a09c
--- /dev/null
+++ b/src/audio.c
@@ -0,0 +1,388 @@
+#include "audio.h"
+
+#include <wp/wp.h>
+
+#include "endpoint.h"
+#include "glib-object.h"
+#include "glib.h"
+#include "wp.h"
+
+struct _AstalWpAudio {
+ GObject parent_instance;
+};
+
+typedef struct {
+ AstalWpWp *wp;
+} AstalWpAudioPrivate;
+
+G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpAudio, astal_wp_audio, G_TYPE_OBJECT);
+
+typedef enum {
+ ASTAL_WP_AUDIO_SIGNAL_CHANGED,
+ ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED,
+ ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_REMOVED,
+ ASTAL_WP_AUDIO_SIGNAL_SPEAKER_ADDED,
+ ASTAL_WP_AUDIO_SIGNAL_SPEAKER_REMOVED,
+ ASTAL_WP_AUDIO_SIGNAL_STREAM_ADDED,
+ ASTAL_WP_AUDIO_SIGNAL_STREAM_REMOVED,
+ ASTAL_WP_AUDIO_SIGNAL_RECORDER_ADDED,
+ ASTAL_WP_AUDIO_SIGNAL_RECORDER_REMOVED,
+ ASTAL_WP_AUDIO_N_SIGNALS
+} AstalWpWpSignals;
+
+static guint astal_wp_audio_signals[ASTAL_WP_AUDIO_N_SIGNALS] = {
+ 0,
+};
+
+typedef enum {
+ ASTAL_WP_AUDIO_PROP_MICROPHONES = 1,
+ ASTAL_WP_AUDIO_PROP_SPEAKERS,
+ ASTAL_WP_AUDIO_PROP_STREAMS,
+ ASTAL_WP_AUDIO_PROP_RECORDERS,
+ ASTAL_WP_AUDIO_N_PROPERTIES,
+} AstalWpAudioProperties;
+
+static GParamSpec *astal_wp_audio_properties[ASTAL_WP_AUDIO_N_PROPERTIES] = {
+ NULL,
+};
+
+/**
+ * astal_wp_audio_get_speaker:
+ * @self: the AstalWpAudio object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the speaker with the given id
+ */
+AstalWpEndpoint *astal_wp_audio_get_speaker(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id);
+ if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_audio_get_microphone:
+ * @self: the AstalWpAudio object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the microphone with the given id
+ */
+AstalWpEndpoint *astal_wp_audio_get_microphone(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id);
+ if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_audio_get_recorder:
+ * @self: the AstalWpAudio object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the recorder with the given id
+ */
+AstalWpEndpoint *astal_wp_audio_get_recorder(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id);
+ if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_audio_get_stream:
+ * @self: the AstalWpAudio object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the stream with the given id
+ */
+AstalWpEndpoint *astal_wp_audio_get_stream(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id);
+ if (astal_wp_endpoint_get_media_class(endpoint) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_audio_get_microphones:
+ * @self: the AstalWpAudio object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * microphones
+ */
+GList *astal_wp_audio_get_microphones(AstalWpAudio *self) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(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;
+}
+
+/**
+ * astal_wp_audio_get_speakers:
+ * @self: the AstalWpAudio object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * speaker
+ */
+GList *astal_wp_audio_get_speakers(AstalWpAudio *self) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *speakers = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER) {
+ speakers = g_list_append(speakers, l->data);
+ }
+ }
+ g_list_free(eps);
+ return speakers;
+}
+
+/**
+ * astal_wp_audio_get_recorders:
+ * @self: the AstalWpAudio object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * recorders
+ */
+GList *astal_wp_audio_get_recorders(AstalWpAudio *self) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *recorders = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER) {
+ recorders = g_list_append(recorders, l->data);
+ }
+ }
+ g_list_free(eps);
+ return recorders;
+}
+
+/**
+ * astal_wp_audio_get_streams:
+ * @self: the AstalWpAudio object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * streams
+ */
+GList *astal_wp_audio_get_streams(AstalWpAudio *self) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *streams = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM) {
+ streams = g_list_append(streams, l->data);
+ }
+ }
+ g_list_free(eps);
+ return streams;
+}
+
+/**
+ * astal_wp_audio_get_endpoint:
+ * @self: the AstalWpAudio object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the endpoint with the given id
+ */
+AstalWpEndpoint *astal_wp_audio_get_endpoint(AstalWpAudio *self, guint id) {
+ AstalWpAudioPrivate *priv = astal_wp_audio_get_instance_private(self);
+
+ AstalWpEndpoint *endpoint = astal_wp_wp_get_endpoint(priv->wp, id);
+ return endpoint;
+}
+
+static void astal_wp_audio_get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *pspec) {
+ AstalWpAudio *self = ASTAL_WP_AUDIO(object);
+
+ switch (property_id) {
+ case ASTAL_WP_AUDIO_PROP_MICROPHONES:
+ g_value_set_pointer(value, astal_wp_audio_get_microphones(self));
+ break;
+ case ASTAL_WP_AUDIO_PROP_SPEAKERS:
+ g_value_set_pointer(value, astal_wp_audio_get_speakers(self));
+ break;
+ case ASTAL_WP_AUDIO_PROP_STREAMS:
+ g_value_set_pointer(value, astal_wp_audio_get_streams(self));
+ break;
+ case ASTAL_WP_AUDIO_PROP_RECORDERS:
+ g_value_set_pointer(value, astal_wp_audio_get_recorders(self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+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)) {
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE:
+ g_signal_emit_by_name(self, "microphone-added", endpoint);
+ g_object_notify(G_OBJECT(self), "microphones");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER:
+ g_signal_emit_by_name(self, "speaker-added", endpoint);
+ g_object_notify(G_OBJECT(self), "speakers");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM:
+ g_signal_emit_by_name(self, "stream-added", endpoint);
+ g_object_notify(G_OBJECT(self), "streams");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER:
+ g_signal_emit_by_name(self, "recorder-added", endpoint);
+ g_object_notify(G_OBJECT(self), "recorders");
+ break;
+ }
+
+ g_signal_emit_by_name(self, "changed");
+}
+
+static void astal_wp_audio_object_removed(AstalWpAudio *self, gpointer object) {
+ AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object);
+ switch (astal_wp_endpoint_get_media_class(endpoint)) {
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE:
+ g_signal_emit_by_name(self, "microphone-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "microphones");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER:
+ g_signal_emit_by_name(self, "speaker-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "speakers");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM:
+ g_signal_emit_by_name(self, "stream-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "streams");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER:
+ g_signal_emit_by_name(self, "recorder-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "recorders");
+ break;
+ }
+
+ g_signal_emit_by_name(self, "changed");
+}
+
+/**
+ * astal_wp_audio_get_default
+ *
+ * Returns: (nullable) (transfer none): gets the default audio object.
+ */
+AstalWpAudio *astal_wp_audio_get_default() {
+ static AstalWpAudio *self = NULL;
+
+ if (self == NULL) self = g_object_new(ASTAL_WP_TYPE_AUDIO, NULL);
+
+ return self;
+}
+
+/**
+ * astal_wp_get_default_audio
+ *
+ * Returns: (nullable) (transfer none): gets the default audio object.
+ */
+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);
+
+ priv->wp = astal_wp_wp_get_default();
+
+ g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_audio_object_added),
+ self);
+ g_signal_connect_swapped(priv->wp, "endpoint-removed",
+ G_CALLBACK(astal_wp_audio_object_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;
+
+ /**
+ * AstalWpAudio:microphones: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_MICROPHONES] =
+ g_param_spec_pointer("microphones", "microphones", "microphones", G_PARAM_READABLE);
+ /**
+ * AstalWpAudio:speakers: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_SPEAKERS] =
+ g_param_spec_pointer("speakers", "speakers", "speakers", G_PARAM_READABLE);
+ /**
+ * AstalWpAudio:recorders: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_RECORDERS] =
+ g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE);
+ /**
+ * AstalWpAudio:streams: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_audio_properties[ASTAL_WP_AUDIO_PROP_STREAMS] =
+ g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE);
+
+ g_object_class_install_properties(object_class, ASTAL_WP_AUDIO_N_PROPERTIES,
+ astal_wp_audio_properties);
+
+ astal_wp_audio_signals[ASTAL_WP_AUDIO_SIGNAL_MICROPHONE_ADDED] =
+ g_signal_new("microphone-added", 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_MICROPHONE_REMOVED] =
+ g_signal_new("microphone-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_SPEAKER_ADDED] =
+ g_signal_new("speaker-added", 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_SPEAKER_REMOVED] =
+ g_signal_new("speaker-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_STREAM_ADDED] =
+ g_signal_new("stream-added", 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_STREAM_REMOVED] =
+ g_signal_new("stream-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_RECORDER_ADDED] =
+ g_signal_new("recorder-added", 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_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_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 6dadda8..0f5550c 100644
--- a/src/endpoint.c
+++ b/src/endpoint.c
@@ -1,9 +1,12 @@
+#include "endpoint.h"
+
#include <limits.h>
#include <wp/wp.h>
#include "endpoint-private.h"
#include "glib-object.h"
#include "glib.h"
+#include "glibconfig.h"
struct _AstalWpEndpoint {
GObject parent_instance;
@@ -11,22 +14,35 @@ struct _AstalWpEndpoint {
guint id;
gdouble volume;
gboolean mute;
- const gchar *description;
+ gchar *description;
+ AstalWpMediaClass type;
+ gboolean is_default;
};
typedef struct {
WpNode *node;
WpPlugin *mixer;
+ WpPlugin *defaults;
+
+ gulong signal_handler_id;
} AstalWpEndpointPrivate;
G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpEndpoint, astal_wp_endpoint, G_TYPE_OBJECT);
+G_DEFINE_ENUM_TYPE(AstalWpMediaClass, astal_wp_media_class,
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_MICROPHONE, "Audio/Source"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER, "Audio/Sink"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER, "Stream/Input/Audio"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, "Stream/Output/Audio"));
+
typedef enum {
ASTAL_WP_ENDPOINT_PROP_ID = 1,
ASTAL_WP_ENDPOINT_PROP_VOLUME,
ASTAL_WP_ENDPOINT_PROP_MUTE,
ASTAL_WP_ENDPOINT_PROP_DESCRIPTION,
+ ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS,
+ ASTAL_WP_ENDPOINT_PROP_DEFAULT,
ASTAL_WP_ENDPOINT_N_PROPERTIES,
} AstalWpEndpointProperties;
@@ -72,28 +88,50 @@ void astal_wp_endpoint_update_volume(AstalWpEndpoint *self) {
void astal_wp_endpoint_set_volume(AstalWpEndpoint *self, gdouble volume) {
AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
- GVariant *variant = NULL;
- GVariantBuilder b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT);
- g_variant_builder_add(&b, "{sv}", "volume", g_variant_new_double(volume));
- variant = g_variant_builder_end(&b);
-
- g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant);
- g_variant_unref(variant);
+ gboolean ret;
+ GVariant *variant = g_variant_new_double(volume);
+ g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant, &ret);
}
void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute) {
AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
+ gboolean ret;
GVariant *variant = NULL;
GVariantBuilder b = G_VARIANT_BUILDER_INIT(G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&b, "{sv}", "mute", g_variant_new_boolean(mute));
variant = g_variant_builder_end(&b);
- g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant);
+ g_signal_emit_by_name(priv->mixer, "set-volume", self->id, variant, &ret);
g_variant_unref(variant);
}
+AstalWpMediaClass astal_wp_endpoint_get_media_class(AstalWpEndpoint *self) { return self->type; }
+
+guint astal_wp_endpoint_get_id(AstalWpEndpoint *self) { return self->id; }
+
+gboolean astal_wp_endpoint_get_mute(AstalWpEndpoint *self) { return self->mute; }
+
+gdouble astal_wp_endpoint_get_volume(AstalWpEndpoint *self) { return self->volume; }
+
+const gchar *astal_wp_endpoint_get_description(AstalWpEndpoint *self) { return self->description; }
+
+gboolean astal_wp_endpoint_get_is_default(AstalWpEndpoint *self) { return self->is_default; }
+
+void astal_wp_endpoint_set_is_default(AstalWpEndpoint *self, gboolean is_default) {
+ AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
+
+ if (!is_default) return;
+ gboolean ret;
+ const gchar *name =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "node.name");
+ const gchar *media_class =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class");
+ g_signal_emit_by_name(priv->defaults, "set-default-configured-node-name", media_class, name,
+ &ret);
+}
+
static void astal_wp_endpoint_get_property(GObject *object, guint property_id, GValue *value,
GParamSpec *pspec) {
AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object);
@@ -111,6 +149,12 @@ static void astal_wp_endpoint_get_property(GObject *object, guint property_id, G
case ASTAL_WP_ENDPOINT_PROP_DESCRIPTION:
g_value_set_string(value, self->description);
break;
+ case ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS:
+ g_value_set_enum(value, self->type);
+ break;
+ case ASTAL_WP_ENDPOINT_PROP_DEFAULT:
+ g_value_set_boolean(value, self->is_default);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
@@ -128,17 +172,40 @@ static void astal_wp_endpoint_set_property(GObject *object, guint property_id, c
case ASTAL_WP_ENDPOINT_PROP_VOLUME:
astal_wp_endpoint_set_volume(self, g_value_get_double(value));
break;
+ case ASTAL_WP_ENDPOINT_PROP_DEFAULT:
+ astal_wp_endpoint_set_is_default(self, g_value_get_boolean(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
}
}
-AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer) {
+static void astal_wp_endpoint_default_changed(AstalWpEndpoint *self) {
+ AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
+
+ guint defaultId;
+ const gchar *media_class =
+ wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->node), "media.class");
+ g_signal_emit_by_name(priv->defaults, "get-default-node", media_class, &defaultId);
+
+ if (self->is_default && defaultId != self->id) {
+ self->is_default = FALSE;
+ g_object_notify(G_OBJECT(self), "is-default");
+ g_signal_emit_by_name(self, "changed");
+ } else if (!self->is_default && defaultId == self->id) {
+ self->is_default = TRUE;
+ g_object_notify(G_OBJECT(self), "is-default");
+ g_signal_emit_by_name(self, "changed");
+ }
+}
+
+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);
self->id = wp_proxy_get_bound_id(WP_PROXY(node));
@@ -156,9 +223,15 @@ AstalWpEndpoint *astal_wp_endpoint_create(WpNode *node, WpPlugin *mixer) {
if (description == NULL) {
description = "unknown";
}
-
self->description = g_strdup(description);
+ 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;
+
+ priv->signal_handler_id = g_signal_connect_swapped(
+ priv->defaults, "changed", G_CALLBACK(astal_wp_endpoint_default_changed), self);
+
+ astal_wp_endpoint_default_changed(self);
return self;
}
@@ -166,6 +239,7 @@ static void astal_wp_endpoint_init(AstalWpEndpoint *self) {
AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
priv->node = NULL;
priv->mixer = NULL;
+ priv->defaults = NULL;
self->volume = 0;
self->mute = TRUE;
@@ -175,11 +249,18 @@ static void astal_wp_endpoint_init(AstalWpEndpoint *self) {
static void astal_wp_endpoint_dispose(GObject *object) {
AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object);
AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
+
+ g_signal_handler_disconnect(priv->defaults, priv->signal_handler_id);
+
g_clear_object(&priv->node);
g_clear_object(&priv->mixer);
+ g_clear_object(&priv->defaults);
}
-static void astal_wp_endpoint_finalize(GObject *object) {}
+static void astal_wp_endpoint_finalize(GObject *object) {
+ AstalWpEndpoint *self = ASTAL_WP_ENDPOINT(object);
+ g_free(self->description);
+}
static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS(class);
@@ -196,6 +277,16 @@ static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) {
g_param_spec_boolean("mute", "mute", "mute", TRUE, G_PARAM_READWRITE);
astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_DESCRIPTION] =
g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE);
+ /**
+ * AstalWpEndpoint:media-class: (type AstalWpMediaClass)
+ *
+ * The media class of this endpoint
+ */
+ astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_MEDIA_CLASS] =
+ g_param_spec_enum("media-class", "media-class", "media-class", ASTAL_WP_TYPE_MEDIA_CLASS, 1,
+ 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);
g_object_class_install_properties(object_class, ASTAL_WP_ENDPOINT_N_PROPERTIES,
astal_wp_endpoint_properties);
diff --git a/src/meson.build b/src/meson.build
index bdd5512..63415c4 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,6 +1,7 @@
srcs = files(
'wireplumber.c',
'endpoint.c',
+ 'audio.c',
)
deps = [
diff --git a/src/wireplumber.c b/src/wireplumber.c
index 8c52bf2..d50da83 100644
--- a/src/wireplumber.c
+++ b/src/wireplumber.c
@@ -1,9 +1,9 @@
-#include "wireplumber.h"
-
#include <wp/wp.h>
+#include "audio.h"
#include "endpoint-private.h"
#include "glib-object.h"
+#include "glib.h"
#include "wp.h"
struct _AstalWpWp {
@@ -34,6 +34,16 @@ static guint astal_wp_wp_signals[ASTAL_WP_WP_N_SIGNALS] = {
0,
};
+typedef enum {
+ ASTAL_WP_WP_PROP_AUDIO = 1,
+ ASTAL_WP_WP_PROP_ENDPOINTS,
+ ASTAL_WP_WP_N_PROPERTIES,
+} AstalWpWpProperties;
+
+static GParamSpec *astal_wp_wp_properties[ASTAL_WP_WP_N_PROPERTIES] = {
+ NULL,
+};
+
/**
* astal_wp_wp_get_endpoint:
* @self: the AstalWpWp object
@@ -48,18 +58,41 @@ AstalWpEndpoint *astal_wp_wp_get_endpoint(AstalWpWp *self, guint id) {
return endpoint;
}
-static void astal_wp_wp_default_changed(AstalWpWp *self) {
+/**
+ * astal_wp_wp_get_endpoints:
+ * @self: the AstalWpWp object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * endpoints
+ */
+GList *astal_wp_wp_get_endpoints(AstalWpWp *self) {
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
+ return g_hash_table_get_values(priv->endpoints);
+}
- guint defaultSinkId;
- guint defaultSourceId;
-
- g_signal_emit_by_name(priv->defaults, "get-default-node", "Audio/Sink", &defaultSinkId);
- g_signal_emit_by_name(priv->defaults, "get-default-node", "Audio/Source", &defaultSourceId);
+/**
+ * astal_wp_wp_get_audio
+ *
+ * Returns: (nullable) (transfer none): gets the audio object
+ */
+AstalWpAudio *astal_wp_wp_get_audio() { return astal_wp_audio_get_default(); }
- g_print("default nodes: sink: %d, source: %d\n", defaultSinkId, defaultSourceId);
+static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *pspec) {
+ AstalWpWp *self = ASTAL_WP_WP(object);
+ AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
- g_signal_emit_by_name(self, "changed");
+ switch (property_id) {
+ case ASTAL_WP_WP_PROP_AUDIO:
+ g_value_set_object(value, astal_wp_wp_get_audio());
+ break;
+ case ASTAL_WP_WP_PROP_ENDPOINTS:
+ g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
}
static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) {
@@ -79,12 +112,13 @@ 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);
+ 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");
g_signal_emit_by_name(self, "changed");
}
@@ -100,6 +134,7 @@ static void astal_wp_wp_object_removed(AstalWpWp *self, gpointer object) {
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_signal_emit_by_name(self, "changed");
}
@@ -135,8 +170,8 @@ static void astal_wp_wp_plugin_activated(WpObject *obj, GAsyncResult *result, As
g_signal_connect_swapped(priv->mixer, "changed", (GCallback)astal_wp_wp_mixer_changed,
self);
- g_signal_connect_swapped(priv->defaults, "changed", (GCallback)astal_wp_wp_default_changed,
- self);
+ // g_signal_connect_swapped(priv->defaults, "changed",
+ // (GCallback)astal_wp_wp_default_changed, self);
g_signal_connect_swapped(priv->obj_manager, "object-added",
G_CALLBACK(astal_wp_wp_object_added), self);
@@ -214,13 +249,14 @@ 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_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,
"media.class", "=s", "Audio/Source", NULL);
- wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE,
- WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "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/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);
@@ -239,6 +275,21 @@ static void astal_wp_wp_class_init(AstalWpWpClass *class) {
GObjectClass *object_class = G_OBJECT_CLASS(class);
object_class->finalize = astal_wp_wp_finalize;
object_class->dispose = astal_wp_wp_dispose;
+ object_class->get_property = astal_wp_wp_get_property;
+
+ astal_wp_wp_properties[ASTAL_WP_WP_PROP_AUDIO] =
+ g_param_spec_object("audio", "audio", "audio", ASTAL_WP_TYPE_AUDIO, G_PARAM_READABLE);
+
+ /**
+ * AstalWpWp:endpoints: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_wp_properties[ASTAL_WP_WP_PROP_ENDPOINTS] =
+ g_param_spec_pointer("endpoints", "endpoints", "endpoints", G_PARAM_READABLE);
+
+ g_object_class_install_properties(object_class, ASTAL_WP_WP_N_PROPERTIES,
+ astal_wp_wp_properties);
astal_wp_wp_signals[ASTAL_WP_WP_SIGNAL_ENDPOINT_ADDED] =
g_signal_new("endpoint-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,