diff options
-rw-r--r-- | include/astal/wireplumber/device.h | 5 | ||||
-rw-r--r-- | include/astal/wireplumber/endpoint.h | 5 | ||||
-rw-r--r-- | include/astal/wireplumber/meson.build | 1 | ||||
-rw-r--r-- | include/astal/wireplumber/video.h | 32 | ||||
-rw-r--r-- | src/audio.c | 39 | ||||
-rw-r--r-- | src/device.c | 41 | ||||
-rw-r--r-- | src/endpoint.c | 23 | ||||
-rw-r--r-- | src/meson.build | 1 | ||||
-rw-r--r-- | src/video.c | 447 | ||||
-rw-r--r-- | src/wireplumber.c | 30 |
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) |