summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkotontrion <[email protected]>2024-07-22 14:29:40 +0200
committerkotontrion <[email protected]>2024-07-22 14:29:40 +0200
commit4a7ecd70a476f48901391e2011490c21a1bd41a2 (patch)
treec9a996e9a492d1eb839232942287ab571ba29347
parentd5596c421da581479382394a6ead9bb422779564 (diff)
add video device support
-rw-r--r--include/astal/wireplumber/device.h5
-rw-r--r--include/astal/wireplumber/endpoint.h5
-rw-r--r--include/astal/wireplumber/meson.build1
-rw-r--r--include/astal/wireplumber/video.h32
-rw-r--r--src/audio.c39
-rw-r--r--src/device.c41
-rw-r--r--src/endpoint.c23
-rw-r--r--src/meson.build1
-rw-r--r--src/video.c447
-rw-r--r--src/wireplumber.c30
10 files changed, 590 insertions, 34 deletions
diff --git a/include/astal/wireplumber/device.h b/include/astal/wireplumber/device.h
index 6e5f6d4..9f633e3 100644
--- a/include/astal/wireplumber/device.h
+++ b/include/astal/wireplumber/device.h
@@ -11,6 +11,10 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(AstalWpDevice, astal_wp_device, ASTAL_WP, DEVICE, GObject)
+#define ASTAL_WP_TYPE_DEVICE_TYPE (astal_wp_device_type_get_type())
+
+typedef enum { ASTAL_WP_DEVICE_TYPE_AUDIO, ASTAL_WP_DEVICE_TYPE_VIDEO } AstalWpDeviceType;
+
guint astal_wp_device_get_id(AstalWpDevice *self);
const gchar *astal_wp_device_get_description(AstalWpDevice *self);
const gchar *astal_wp_device_get_icon(AstalWpDevice *self);
@@ -18,6 +22,7 @@ AstalWpProfile *astal_wp_device_get_profile(AstalWpDevice *self, gint id);
GList *astal_wp_device_get_profiles(AstalWpDevice *self);
void astal_wp_device_set_active_profile(AstalWpDevice *self, int profile_id);
gint astal_wp_device_get_active_profile(AstalWpDevice *self);
+AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self);
G_END_DECLS
diff --git a/include/astal/wireplumber/endpoint.h b/include/astal/wireplumber/endpoint.h
index 391fafc..dcf6601 100644
--- a/include/astal/wireplumber/endpoint.h
+++ b/include/astal/wireplumber/endpoint.h
@@ -16,7 +16,10 @@ typedef enum {
ASTAL_WP_MEDIA_CLASS_AUDIO_SPEAKER,
ASTAL_WP_MEDIA_CLASS_AUDIO_RECORDER,
ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM,
- ASTAL_WP_MEDIA_CLASS_AUDIO_DEVICE,
+ ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE,
+ ASTAL_WP_MEDIA_CLASS_VIDEO_SINK,
+ ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER,
+ ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM,
} AstalWpMediaClass;
void astal_wp_endpoint_update_volume(AstalWpEndpoint *self);
diff --git a/include/astal/wireplumber/meson.build b/include/astal/wireplumber/meson.build
index 6cd0147..d02563c 100644
--- a/include/astal/wireplumber/meson.build
+++ b/include/astal/wireplumber/meson.build
@@ -2,6 +2,7 @@ astal_wireplumber_subheaders = files(
'wp.h',
'endpoint.h',
'device.h',
+ 'video.h',
'audio.h',
'profile.h',
)
diff --git a/include/astal/wireplumber/video.h b/include/astal/wireplumber/video.h
new file mode 100644
index 0000000..8b17e31
--- /dev/null
+++ b/include/astal/wireplumber/video.h
@@ -0,0 +1,32 @@
+#ifndef ASTAL_WIREPLUMBER_VIDEO_H
+#define ASTAL_WIREPLUMBER_VIDEO_H
+
+#include <glib-object.h>
+
+#include "device.h"
+#include "endpoint.h"
+
+G_BEGIN_DECLS
+
+#define ASTAL_WP_TYPE_VIDEO (astal_wp_video_get_type())
+
+G_DECLARE_FINAL_TYPE(AstalWpVideo, astal_wp_video, ASTAL_WP, VIDEO, GObject)
+
+AstalWpEndpoint *astal_wp_video_get_source(AstalWpVideo *self, guint id);
+AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id);
+AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id);
+AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id);
+AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id);
+
+GList *astal_wp_video_get_sources(AstalWpVideo *self);
+GList *astal_wp_video_get_sinks(AstalWpVideo *self);
+GList *astal_wp_video_get_recorders(AstalWpVideo *self);
+GList *astal_wp_video_get_streams(AstalWpVideo *self);
+GList *astal_wp_video_get_devices(AstalWpVideo *self);
+
+AstalWpVideo *astal_wp_video_get_default();
+AstalWpVideo *astal_wp_get_default_video();
+
+G_END_DECLS
+
+#endif // !ASTAL_WIREPLUMBER_VIDEO_H
diff --git a/src/audio.c b/src/audio.c
index 75ccf97..dde96bd 100644
--- a/src/audio.c
+++ b/src/audio.c
@@ -221,16 +221,15 @@ GList *astal_wp_audio_get_streams(AstalWpAudio *self) {
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;
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_AUDIO) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
}
/**
@@ -300,17 +299,21 @@ 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");
+ AstalWpDevice *device = ASTAL_WP_DEVICE(object);
+ if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) {
+ 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_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");
+ AstalWpDevice *device = ASTAL_WP_DEVICE(object);
+ if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_AUDIO) {
+ g_signal_emit_by_name(self, "device-removed", device);
+ 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) {
diff --git a/src/device.c b/src/device.c
index ef24ecd..e90b803 100644
--- a/src/device.c
+++ b/src/device.c
@@ -10,6 +10,7 @@ struct _AstalWpDevice {
gchar *description;
gchar *icon;
gint active_profile;
+ AstalWpDeviceType type;
};
typedef struct {
@@ -19,12 +20,17 @@ typedef struct {
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;
@@ -41,7 +47,12 @@ 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; }
+const gchar *astal_wp_device_get_icon(AstalWpDevice *self) {
+ g_return_val_if_fail(self != NULL, "audio-card-symbolic");
+ return self->icon;
+}
+
+AstalWpDeviceType astal_wp_device_get_device_type(AstalWpDevice *self) { return self->type; }
gint astal_wp_device_get_active_profile(AstalWpDevice *self) { return self->active_profile; }
@@ -55,7 +66,6 @@ void astal_wp_device_set_active_profile(AstalWpDevice *self, int 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);
}
@@ -101,6 +111,9 @@ static void astal_wp_device_get_property(GObject *object, guint property_id, GVa
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;
@@ -130,6 +143,7 @@ static void astal_wp_device_update_profiles(AstalWpDevice *self) {
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);
@@ -142,6 +156,7 @@ static void astal_wp_device_update_profiles(AstalWpDevice *self) {
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);
@@ -153,6 +168,7 @@ static void astal_wp_device_update_active_profile(AstalWpDevice *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);
@@ -167,6 +183,7 @@ static void astal_wp_device_update_active_profile(AstalWpDevice *self) {
g_object_new(ASTAL_WP_TYPE_PROFILE, "index", index, "description", description, NULL));
self->active_profile = index;
+ g_value_unset(&profile);
}
wp_iterator_unref(iter);
@@ -199,16 +216,24 @@ static void astal_wp_device_update_properties(AstalWpDevice *self) {
const gchar *icon =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(priv->device), "device.icon-name");
- if (description == NULL) {
- icon = "soundcard-symbolic";
+ 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");
g_signal_emit_by_name(self, "changed");
@@ -264,6 +289,14 @@ static void astal_wp_device_class_init(AstalWpDeviceClass *class) {
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 AstalWpProfile objects
diff --git a/src/endpoint.c b/src/endpoint.c
index 2ce5768..e479d5b 100644
--- a/src/endpoint.c
+++ b/src/endpoint.c
@@ -4,9 +4,7 @@
#include "device.h"
#include "endpoint-private.h"
-#include "glib.h"
#include "wp.h"
-#include "wp/proxy-interfaces.h"
struct _AstalWpEndpoint {
GObject parent_instance;
@@ -41,7 +39,11 @@ 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"));
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_AUDIO_STREAM, "Stream/Output/Audio"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE, "Video/Source"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_SINK, "Video/Sink"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER, "Stream/Input/Video"),
+ G_DEFINE_ENUM_VALUE(ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM, "Stream/Output/Video"));
typedef enum {
ASTAL_WP_ENDPOINT_PROP_ID = 1,
@@ -112,8 +114,6 @@ void astal_wp_endpoint_set_mute(AstalWpEndpoint *self, gboolean mute) {
variant = g_variant_builder_end(&b);
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; }
@@ -194,6 +194,10 @@ static void astal_wp_endpoint_set_property(GObject *object, guint property_id, c
case ASTAL_WP_ENDPOINT_PROP_DEFAULT:
astal_wp_endpoint_set_is_default(self, g_value_get_boolean(value));
break;
+ case ASTAL_WP_ENDPOINT_PROP_ICON:
+ g_free(self->icon);
+ self->icon = g_strdup(g_value_get_string(value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
@@ -204,7 +208,6 @@ static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) {
AstalWpEndpointPrivate *priv = astal_wp_endpoint_get_instance_private(self);
if (priv->node == NULL) return;
self->id = wp_proxy_get_bound_id(WP_PROXY(priv->node));
-
astal_wp_endpoint_update_volume(self);
const gchar *description =
@@ -258,7 +261,7 @@ static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) {
if (icon == NULL) icon = "application-x-executable-symbolic";
break;
default:
- icon = "audio-card-symbolc";
+ icon = "audio-card-symbolic";
}
g_free(self->icon);
self->icon = g_strdup(icon);
@@ -267,7 +270,7 @@ static void astal_wp_endpoint_update_properties(AstalWpEndpoint *self) {
g_object_notify(G_OBJECT(self), "description");
g_object_notify(G_OBJECT(self), "name");
g_object_notify(G_OBJECT(self), "icon");
- g_object_notify(G_OBJECT(self), "type");
+ g_object_notify(G_OBJECT(self), "media-class");
g_signal_emit_by_name(self, "changed");
}
@@ -405,8 +408,8 @@ static void astal_wp_endpoint_class_init(AstalWpEndpointClass *class) {
g_param_spec_string("description", "description", "description", NULL, G_PARAM_READABLE);
astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_NAME] =
g_param_spec_string("name", "name", "name", NULL, G_PARAM_READABLE);
- astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ICON] =
- g_param_spec_string("icon", "icon", "icon", NULL, G_PARAM_READABLE);
+ astal_wp_endpoint_properties[ASTAL_WP_ENDPOINT_PROP_ICON] = g_param_spec_string(
+ "icon", "icon", "icon", "audio-card-symbolic", G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
/**
* AstalWpEndpoint:media-class: (type AstalWpMediaClass)
*
diff --git a/src/meson.build b/src/meson.build
index ce27c3d..87a5ae8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -2,6 +2,7 @@ srcs = files(
'wireplumber.c',
'endpoint.c',
'device.c',
+ 'video.c',
'profile.c',
'audio.c',
)
diff --git a/src/video.c b/src/video.c
new file mode 100644
index 0000000..0e52d20
--- /dev/null
+++ b/src/video.c
@@ -0,0 +1,447 @@
+#include "video.h"
+
+#include <wp/wp.h>
+
+#include "device.h"
+#include "endpoint.h"
+#include "wp.h"
+
+struct _AstalWpVideo {
+ GObject parent_instance;
+};
+
+typedef struct {
+ AstalWpWp *wp;
+} AstalWpVideoPrivate;
+
+G_DEFINE_FINAL_TYPE_WITH_PRIVATE(AstalWpVideo, astal_wp_video, G_TYPE_OBJECT);
+
+typedef enum {
+ ASTAL_WP_VIDEO_SIGNAL_CHANGED,
+ ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED,
+ ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED,
+ ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED,
+ ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED,
+ ASTAL_WP_VIDEO_SIGNAL_STREAM_ADDED,
+ ASTAL_WP_VIDEO_SIGNAL_STREAM_REMOVED,
+ ASTAL_WP_VIDEO_SIGNAL_RECORDER_ADDED,
+ ASTAL_WP_VIDEO_SIGNAL_RECORDER_REMOVED,
+ ASTAL_WP_VIDEO_SIGNAL_DEVICE_ADDED,
+ ASTAL_WP_VIDEO_SIGNAL_DEVICE_REMOVED,
+ ASTAL_WP_VIDEO_N_SIGNALS
+} AstalWpWpSignals;
+
+static guint astal_wp_video_signals[ASTAL_WP_VIDEO_N_SIGNALS] = {
+ 0,
+};
+
+typedef enum {
+ ASTAL_WP_VIDEO_PROP_SOURCE = 1,
+ ASTAL_WP_VIDEO_PROP_SINK,
+ ASTAL_WP_VIDEO_PROP_STREAMS,
+ ASTAL_WP_VIDEO_PROP_RECORDERS,
+ ASTAL_WP_VIDEO_PROP_DEVICES,
+ ASTAL_WP_VIDEO_N_PROPERTIES,
+} AstalWpVideoProperties;
+
+static GParamSpec *astal_wp_video_properties[ASTAL_WP_VIDEO_N_PROPERTIES] = {
+ NULL,
+};
+
+/**
+ * astal_wp_video_get_source:
+ * @self: the AstalWpVideo object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the source with the given id
+ */
+AstalWpEndpoint *astal_wp_video_get_speaker(AstalWpVideo *self, guint id) {
+ AstalWpVideoPrivate *priv = astal_wp_video_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_VIDEO_SOURCE)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_video_get_sink:
+ * @self: the AstalWpVideo object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the sink with the given id
+ */
+AstalWpEndpoint *astal_wp_video_get_sink(AstalWpVideo *self, guint id) {
+ AstalWpVideoPrivate *priv = astal_wp_video_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_VIDEO_SINK)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_video_get_stream:
+ * @self: the AstalWpVideo object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the stream with the given id
+ */
+AstalWpEndpoint *astal_wp_video_get_stream(AstalWpVideo *self, guint id) {
+ AstalWpVideoPrivate *priv = astal_wp_video_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_VIDEO_STREAM)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_video_get_recorder:
+ * @self: the AstalWpVideo object
+ * @id: the id of the endpoint
+ *
+ * Returns: (transfer none) (nullable): the recorder with the given id
+ */
+AstalWpEndpoint *astal_wp_video_get_recorder(AstalWpVideo *self, guint id) {
+ AstalWpVideoPrivate *priv = astal_wp_video_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_VIDEO_RECORDER)
+ return endpoint;
+ return NULL;
+}
+
+/**
+ * astal_wp_video_get_device:
+ * @self: the AstalWpVideo object
+ * @id: the id of the device
+ *
+ * Returns: (transfer none) (nullable): the device with the given id
+ */
+AstalWpDevice *astal_wp_video_get_device(AstalWpVideo *self, guint id) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+
+ AstalWpDevice *device = astal_wp_wp_get_device(priv->wp, id);
+ if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) return device;
+ return NULL;
+}
+
+/**
+ * astal_wp_video_get_sources:
+ * @self: the AstalWpVideo object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * video sources
+ */
+GList *astal_wp_video_get_sources(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
+}
+
+/**
+ * astal_wp_video_get_sinks
+ * @self: the AstalWpVideo object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * video sinks
+ */
+GList *astal_wp_video_get_sinks(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_SINK) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
+}
+
+/**
+ * astal_wp_video_get_recorders:
+ * @self: the AstalWpVideo object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * video recorders
+ */
+GList *astal_wp_video_get_recorders(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
+}
+
+/**
+ * astal_wp_video_get_streams:
+ * @self: the AstalWpVideo object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpEndpoint)): a GList containing the
+ * video streams
+ */
+GList *astal_wp_video_get_streams(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_endpoints(priv->wp);
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_endpoint_get_media_class(l->data) == ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
+}
+
+/**
+ * astal_wp_video_get_devices:
+ * @self: the AstalWpAudio object
+ *
+ * Returns: (transfer container) (nullable) (type GList(AstalWpVideo)): a GList containing the
+ * devices
+ */
+GList *astal_wp_video_get_devices(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+ GList *eps = astal_wp_wp_get_devices(priv->wp);
+ GList *list = NULL;
+
+ for (GList *l = eps; l != NULL; l = l->next) {
+ if (astal_wp_device_get_device_type(l->data) == ASTAL_WP_DEVICE_TYPE_VIDEO) {
+ list = g_list_append(list, l->data);
+ }
+ }
+ g_list_free(eps);
+ return list;
+}
+
+static void astal_wp_video_get_property(GObject *object, guint property_id, GValue *value,
+ GParamSpec *pspec) {
+ AstalWpVideo *self = ASTAL_WP_VIDEO(object);
+
+ switch (property_id) {
+ case ASTAL_WP_VIDEO_PROP_SOURCE:
+ g_value_set_pointer(value, astal_wp_video_get_sources(self));
+ break;
+ case ASTAL_WP_VIDEO_PROP_SINK:
+ g_value_set_pointer(value, astal_wp_video_get_sinks(self));
+ break;
+ case ASTAL_WP_VIDEO_PROP_RECORDERS:
+ g_value_set_pointer(value, astal_wp_video_get_recorders(self));
+ break;
+ case ASTAL_WP_VIDEO_PROP_STREAMS:
+ g_value_set_pointer(value, astal_wp_video_get_streams(self));
+ break;
+ case ASTAL_WP_VIDEO_PROP_DEVICES:
+ g_value_set_pointer(value, astal_wp_video_get_devices(self));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+void astal_wp_video_device_added(AstalWpVideo *self, gpointer object) {
+ AstalWpDevice *device = ASTAL_WP_DEVICE(object);
+ if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) {
+ 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_video_device_removed(AstalWpVideo *self, gpointer object) {
+ AstalWpDevice *device = ASTAL_WP_DEVICE(object);
+ if (astal_wp_device_get_device_type(device) == ASTAL_WP_DEVICE_TYPE_VIDEO) {
+ g_signal_emit_by_name(self, "device-removed", device);
+ g_object_notify(G_OBJECT(self), "devices");
+ g_signal_emit_by_name(self, "changed");
+ }
+}
+
+static void astal_wp_video_object_added(AstalWpVideo *self, gpointer object) {
+ AstalWpEndpoint *endpoint = ASTAL_WP_ENDPOINT(object);
+ switch (astal_wp_endpoint_get_media_class(endpoint)) {
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_SOURCE:
+ g_signal_emit_by_name(self, "source-added", endpoint);
+ g_object_notify(G_OBJECT(self), "sources");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK:
+ g_signal_emit_by_name(self, "sink-added", endpoint);
+ g_object_notify(G_OBJECT(self), "sinks");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM:
+ g_signal_emit_by_name(self, "stream-added", endpoint);
+ g_object_notify(G_OBJECT(self), "streams");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER:
+ g_signal_emit_by_name(self, "recorder-added", endpoint);
+ g_object_notify(G_OBJECT(self), "recorders");
+ break;
+ default:
+ break;
+ }
+
+ g_signal_emit_by_name(self, "changed");
+}
+
+static void astal_wp_video_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_VIDEO_SOURCE:
+ g_signal_emit_by_name(self, "source-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "sources");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_SINK:
+ g_signal_emit_by_name(self, "sink-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "sinks");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_STREAM:
+ g_signal_emit_by_name(self, "stream-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "streams");
+ break;
+ case ASTAL_WP_MEDIA_CLASS_VIDEO_RECORDER:
+ g_signal_emit_by_name(self, "recorder-removed", endpoint);
+ g_object_notify(G_OBJECT(self), "recorders");
+ break;
+ default:
+ break;
+ }
+
+ g_signal_emit_by_name(self, "changed");
+}
+
+/**
+ * astal_wp_video_get_default
+ *
+ * Returns: (nullable) (transfer none): gets the default video object.
+ */
+AstalWpVideo *astal_wp_video_get_default() {
+ static AstalWpVideo *self = NULL;
+
+ if (self == NULL) self = g_object_new(ASTAL_WP_TYPE_VIDEO, NULL);
+
+ return self;
+}
+
+/**
+ * astal_wp_get_default_video
+ *
+ * Returns: (nullable) (transfer none): gets the default video object.
+ */
+AstalWpVideo *astal_wp_get_default_video() { return astal_wp_video_get_default(); }
+
+static void astal_wp_video_init(AstalWpVideo *self) {
+ AstalWpVideoPrivate *priv = astal_wp_video_get_instance_private(self);
+
+ priv->wp = astal_wp_wp_get_default();
+
+ g_signal_connect_swapped(priv->wp, "endpoint-added", G_CALLBACK(astal_wp_video_object_added),
+ self);
+ g_signal_connect_swapped(priv->wp, "endpoint-removed",
+ G_CALLBACK(astal_wp_video_object_removed), self);
+ g_signal_connect_swapped(priv->wp, "device-added", G_CALLBACK(astal_wp_video_device_added),
+ self);
+ g_signal_connect_swapped(priv->wp, "device-removed", G_CALLBACK(astal_wp_video_device_removed),
+ self);
+}
+
+static void astal_wp_video_class_init(AstalWpVideoClass *class) {
+ GObjectClass *object_class = G_OBJECT_CLASS(class);
+ object_class->get_property = astal_wp_video_get_property;
+
+ /**
+ * AstalWpVideo:sources: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SOURCE] =
+ g_param_spec_pointer("sources", "sources", "sources", G_PARAM_READABLE);
+
+ /**
+ * AstalWpVideo:sinks: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_SINK] =
+ g_param_spec_pointer("sinks", "sinks", "sinks", G_PARAM_READABLE);
+
+ /**
+ * AstalWpVideo:recorder: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_RECORDERS] =
+ g_param_spec_pointer("recorders", "recorders", "recorders", G_PARAM_READABLE);
+
+ /**
+ * AstalWpVideo:streams: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_STREAMS] =
+ g_param_spec_pointer("streams", "streams", "streams", G_PARAM_READABLE);
+
+ /**
+ * AstalWpVideo:devices: (type GList(AstalWpEndpoint)) (transfer container)
+ *
+ * A list of AstalWpEndpoint objects
+ */
+ astal_wp_video_properties[ASTAL_WP_VIDEO_PROP_DEVICES] =
+ g_param_spec_pointer("devices", "devices", "devices", G_PARAM_READABLE);
+
+ g_object_class_install_properties(object_class, ASTAL_WP_VIDEO_N_PROPERTIES,
+ astal_wp_video_properties);
+
+ astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_ADDED] =
+ g_signal_new("source-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_REMOVED] =
+ g_signal_new("source-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_ADDED] =
+ g_signal_new("sink-added", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_video_signals[ASTAL_WP_VIDEO_SIGNAL_SINK_REMOVED] =
+ g_signal_new("sink-removed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, 0, NULL, NULL,
+ NULL, G_TYPE_NONE, 1, ASTAL_WP_TYPE_ENDPOINT);
+ astal_wp_video_signals[ASTAL_WP_VIDEO_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_video_signals[ASTAL_WP_VIDEO_SIGNAL_SOURCE_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_video_signals[ASTAL_WP_VIDEO_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_video_signals[ASTAL_WP_VIDEO_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_video_signals[ASTAL_WP_VIDEO_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_video_signals[ASTAL_WP_VIDEO_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_video_signals[ASTAL_WP_VIDEO_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/wireplumber.c b/src/wireplumber.c
index f6bf948..3454a23 100644
--- a/src/wireplumber.c
+++ b/src/wireplumber.c
@@ -3,6 +3,7 @@
#include "audio.h"
#include "device-private.h"
#include "endpoint-private.h"
+#include "video.h"
#include "wp.h"
struct _AstalWpWp {
@@ -41,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_VIDEO,
ASTAL_WP_WP_PROP_ENDPOINTS,
ASTAL_WP_WP_PROP_DEVICES,
ASTAL_WP_WP_PROP_DEFAULT_SPEAKER,
@@ -112,6 +114,13 @@ GList *astal_wp_wp_get_devices(AstalWpWp *self) {
AstalWpAudio *astal_wp_wp_get_audio() { return astal_wp_audio_get_default(); }
/**
+ * astal_wp_wp_get_video
+ *
+ * Returns: (nullable) (transfer none): gets the video object
+ */
+AstalWpVideo *astal_wp_wp_get_video() { return astal_wp_video_get_default(); }
+
+/**
* astal_wp_wp_get_default_speaker
*
* Returns: (nullable) (transfer none): gets the default speaker object
@@ -136,6 +145,9 @@ static void astal_wp_wp_get_property(GObject *object, guint property_id, GValue
case ASTAL_WP_WP_PROP_AUDIO:
g_value_set_object(value, astal_wp_wp_get_audio());
break;
+ case ASTAL_WP_WP_PROP_VIDEO:
+ g_value_set_object(value, astal_wp_wp_get_video());
+ break;
case ASTAL_WP_WP_PROP_ENDPOINTS:
g_value_set_pointer(value, g_hash_table_get_values(priv->endpoints));
break;
@@ -166,6 +178,7 @@ static void astal_wp_wp_object_added(AstalWpWp *self, gpointer object) {
// key = wp_properties_item_get_key (pi);
// value = wp_properties_item_get_value (pi);
// g_print("%s: %s\n", key, value);
+ // g_value_unset(&item);
// }
AstalWpWpPrivate *priv = astal_wp_wp_get_instance_private(self);
@@ -316,7 +329,7 @@ static void astal_wp_wp_init(AstalWpWp *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);
+ wp_init(7);
priv->core = wp_core_new(NULL, NULL, NULL);
if (!wp_core_connect(priv->core)) {
@@ -328,6 +341,7 @@ static void astal_wp_wp_init(AstalWpWp *self) {
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,
@@ -339,6 +353,18 @@ static void astal_wp_wp_init(AstalWpWp *self) {
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_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
+ "media.class", "=s", "Video/Sink", NULL);
+ wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
+ "media.class", "=s", "Video/Source", NULL);
+ wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
+ "media.class", "=s", "Stream/Output/Video", NULL);
+ wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY,
+ "media.class", "=s", "Stream/Input/Video", NULL);
+ wp_object_manager_add_interest(priv->obj_manager, WP_TYPE_DEVICE,
+ WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "media.class", "=s",
+ "Video/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,
@@ -363,6 +389,8 @@ static void astal_wp_wp_class_init(AstalWpWpClass *class) {
astal_wp_wp_properties[ASTAL_WP_WP_PROP_AUDIO] =
g_param_spec_object("audio", "audio", "audio", ASTAL_WP_TYPE_AUDIO, G_PARAM_READABLE);
+ astal_wp_wp_properties[ASTAL_WP_WP_PROP_VIDEO] =
+ g_param_spec_object("video", "video", "video", ASTAL_WP_TYPE_VIDEO, G_PARAM_READABLE);
/**
* AstalWpWp:endpoints: (type GList(AstalWpEndpoint)) (transfer container)