commit 001188aa407c6f6eecef0298d14a5fac3c7395e0
parent d84938b3aba2404d58669b2518d3d134409c0f69
Author: Jacki <jacki@thejackimonster.de>
Date: Mon, 12 Aug 2024 21:12:20 +0200
Abstract pipewire node listing to also handle screencast sessions
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
7 files changed, 469 insertions(+), 220 deletions(-)
diff --git a/src/application.c b/src/application.c
@@ -185,92 +185,6 @@ _application_open(GApplication* application,
g_application_release(application);
}
-static void
-on_core_done(void *data,
- UNUSED uint32_t id,
- int seq)
-{
- g_assert(data);
-
- MESSENGER_Application *app = (MESSENGER_Application*) data;
-
- if ((seq == app->pw.pending) && (app->pw.main_loop))
- pw_main_loop_quit(app->pw.main_loop);
-}
-
-static void
-on_core_error(void *data,
- UNUSED uint32_t id,
- UNUSED int seq,
- int res,
- const char *message)
-{
- g_assert((data) && (message));
-
- MESSENGER_Application *app = (MESSENGER_Application*) data;
-
- g_printerr("ERROR: %s\n", message);
-
- if ((id == PW_ID_CORE) && (res == -EPIPE) && (app->pw.main_loop))
- pw_main_loop_quit(app->pw.main_loop);
-}
-
-static const struct pw_core_events remote_core_events = {
- PW_VERSION_CORE_EVENTS,
- .done = on_core_done,
- .error = on_core_error,
-};
-
-static void
-registry_event_global(void *data,
- uint32_t id,
- uint32_t permissions,
- const char *type,
- uint32_t version,
- const struct spa_dict *props)
-{
- g_assert(data);
-
- MESSENGER_Application *app = (MESSENGER_Application*) data;
-
- if (!props)
- return;
-
- struct pw_properties *properties = pw_properties_new_dict(props);
- if (!properties)
- return;
-
- size_t size = pw_map_get_size(&(app->pw.globals));
- while (id > size)
- pw_map_insert_at(&(app->pw.globals), size++, NULL);
-
- pw_map_insert_at(&(app->pw.globals), id, properties);
-
- app->pw.pending = app->pw.core? pw_core_sync(app->pw.core, 0, 0) : 0;
-}
-
-static void
-registry_event_global_remove(void *data,
- uint32_t id)
-{
- g_assert(data);
-
- MESSENGER_Application *app = (MESSENGER_Application*) data;
-
- struct pw_properties *properties = pw_map_lookup(&(app->pw.globals), id);
- if (!properties)
- return;
-
- pw_map_insert_at(&(app->pw.globals), id, NULL);
- pw_properties_free(properties);
-}
-
-static const struct pw_registry_events registry_events = {
- PW_VERSION_REGISTRY_EVENTS,
- .global = registry_event_global,
- .global_remove = registry_event_global_remove,
-};
-
void
application_init(MESSENGER_Application *app,
int argc,
@@ -488,95 +402,14 @@ application_run(MESSENGER_Application *app)
}
void
-application_pw_core_init(MESSENGER_Application *app)
-{
- g_assert(app);
-
- application_pw_core_cleanup(app);
-
- if (app->pw.context)
- {
-#ifndef MESSENGER_APPLICATION_NO_PORTAL
- if (app->portal)
- app->pw.core = pw_context_connect_fd(
- app->pw.context,
- xdp_portal_open_pipewire_remote_for_camera(app->portal),
- NULL,
- 0
- );
- else
-#endif
- app->pw.core = pw_context_connect(app->pw.context, NULL, 0);
- }
- else
- app->pw.core = NULL;
-
- app->pw.registry = app->pw.core?
- pw_core_get_registry(app->pw.core, PW_VERSION_REGISTRY, 0) : NULL;
-
- pw_map_init(&(app->pw.globals), 64, 16);
-
- if (app->pw.core)
- pw_core_add_listener(
- app->pw.core,
- &(app->pw.core_listener),
- &remote_core_events,
- app
- );
-
- if (app->pw.registry)
- pw_registry_add_listener(
- app->pw.registry,
- &(app->pw.registry_listener),
- ®istry_events,
- app
- );
-}
-
-static int
-destroy_global(void *obj,
- UNUSED void *data)
-{
- struct pw_properties *properties = (struct pw_properties*) obj;
-
- if (!properties)
- return 0;
-
- pw_properties_free(properties);
- return 0;
-}
-
-void
-application_pw_core_cleanup(MESSENGER_Application *app)
-{
- g_assert(app);
-
- if (app->pw.registry)
- pw_proxy_destroy((struct pw_proxy*) app->pw.registry);
-
- app->pw.registry = NULL;
-
- pw_map_for_each(&(app->pw.globals), destroy_global, NULL);
- pw_map_clear(&(app->pw.globals));
-
- if (app->pw.core)
- pw_core_disconnect(app->pw.core);
-
- app->pw.core = NULL;
-}
-
-void
application_pw_main_loop_run(MESSENGER_Application *app)
{
g_assert(app);
- if ((! app->pw.core) || (! app->pw.main_loop))
+ if (!(app->pw.main_loop))
return;
- app->pw.pending = app->pw.core? pw_core_sync(app->pw.core, 0, 0) : 0;
-
- if ((app->pw.main_loop) && (app->pw.core))
- pw_main_loop_run(app->pw.main_loop);
+ pw_main_loop_run(app->pw.main_loop);
}
static void
@@ -805,7 +638,8 @@ application_exit(MESSENGER_Application *app,
util_scheduler_cleanup();
- application_pw_core_cleanup(app);
+ media_pw_cleanup(&(app->media.camera));
+ media_pw_cleanup(&(app->media.screen));
if (app->pw.context)
pw_context_destroy(app->pw.context);
diff --git a/src/application.h b/src/application.h
@@ -55,6 +55,7 @@
#include "ui/send_file.h"
#include "ui/settings.h"
+#include "media.h"
#include "schedule.h"
#include "util.h"
@@ -105,15 +106,14 @@ typedef struct MESSENGER_Application
struct pw_main_loop *main_loop;
struct pw_loop *loop;
struct pw_context *context;
- struct pw_core *core;
- struct pw_registry *registry;
- struct spa_hook core_listener;
- struct spa_hook registry_listener;
- struct pw_map globals;
- int pending;
} pw;
struct {
+ MESSENGER_MediaInfo camera;
+ MESSENGER_MediaInfo screen;
+ } media;
+
+ struct {
int status;
pthread_t tid;
char *identity;
diff --git a/src/media.c b/src/media.c
@@ -0,0 +1,358 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file media.c
+ */
+
+#include "media.h"
+
+#include <glib-2.0/glib.h>
+#include <pipewire/impl.h>
+
+#include "application.h"
+
+static void
+on_core_done(void *data,
+ UNUSED uint32_t id,
+ int seq)
+{
+ g_assert(data);
+
+ MESSENGER_MediaInfo *media = (MESSENGER_MediaInfo*) data;
+ MESSENGER_Application *app = media->app;
+
+ if ((seq == media->pw.pending) && (app->pw.main_loop))
+ pw_main_loop_quit(app->pw.main_loop);
+}
+
+static void
+on_core_error(void *data,
+ UNUSED uint32_t id,
+ UNUSED int seq,
+ int res,
+ const char *message)
+{
+ g_assert((data) && (message));
+
+ MESSENGER_MediaInfo *media = (MESSENGER_MediaInfo*) data;
+ MESSENGER_Application *app = media->app;
+
+ g_printerr("ERROR: %s\n", message);
+
+ if ((id == PW_ID_CORE) && (res == -EPIPE) && (app->pw.main_loop))
+ pw_main_loop_quit(app->pw.main_loop);
+}
+
+static const struct pw_core_events remote_core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void
+registry_event_global(void *data,
+ uint32_t id,
+ uint32_t permissions,
+ const char *type,
+ uint32_t version,
+ const struct spa_dict *props)
+{
+ g_assert(data);
+
+ MESSENGER_MediaInfo *media = (MESSENGER_MediaInfo*) data;
+
+ if (!props)
+ return;
+
+ struct pw_properties *properties = pw_properties_new_dict(props);
+ if (!properties)
+ return;
+
+ size_t size = pw_map_get_size(&(media->pw.globals));
+ while (id > size)
+ pw_map_insert_at(&(media->pw.globals), size++, NULL);
+
+ pw_map_insert_at(&(media->pw.globals), id, properties);
+
+ media->pw.pending = media->pw.core? pw_core_sync(media->pw.core, 0, 0) : 0;
+}
+
+static void
+registry_event_global_remove(void *data,
+ uint32_t id)
+{
+ g_assert(data);
+
+ MESSENGER_MediaInfo *media = (MESSENGER_MediaInfo*) data;
+
+ struct pw_properties *properties = pw_map_lookup(&(media->pw.globals), id);
+ if (!properties)
+ return;
+
+ pw_map_insert_at(&(media->pw.globals), id, NULL);
+ pw_properties_free(properties);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+void
+media_pw_init(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app,
+ int fd)
+{
+ g_assert((media) && (app));
+
+ media_pw_cleanup(media);
+ media->app = app;
+
+ if (app->pw.context)
+ {
+ if (-1 != fd)
+ media->pw.core = pw_context_connect_fd(
+ app->pw.context,
+ fd,
+ NULL,
+ 0
+ );
+ else
+ media->pw.core = pw_context_connect(app->pw.context, NULL, 0);
+ }
+ else
+ media->pw.core = NULL;
+
+ media->pw.registry = media->pw.core?
+ pw_core_get_registry(media->pw.core, PW_VERSION_REGISTRY, 0) : NULL;
+
+ pw_map_init(&(media->pw.globals), 64, 16);
+
+ if (media->pw.core)
+ pw_core_add_listener(
+ media->pw.core,
+ &(media->pw.core_listener),
+ &remote_core_events,
+ media
+ );
+
+ if (media->pw.registry)
+ pw_registry_add_listener(
+ media->pw.registry,
+ &(media->pw.registry_listener),
+ ®istry_events,
+ media
+ );
+}
+
+void
+media_init_camera_capturing(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app)
+{
+ g_assert((media) && (app));
+ int fd = -1;
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+ if (app->portal)
+ fd = xdp_portal_open_pipewire_remote_for_camera(app->portal);
+#endif
+
+ media_pw_init(media, app, fd);
+}
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+static void
+_request_screencast_callback(GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_assert((source_object) && (result) && (user_data));
+
+ XdpPortal *portal = (XdpPortal*) source_object;
+ MESSENGER_MediaInfo *media = (MESSENGER_MediaInfo*) user_data;
+
+ GError *error = NULL;
+ XdpSession *session = xdp_portal_create_screencast_session_finish(
+ portal,
+ result,
+ &error
+ );
+
+ media->session = session;
+
+ if (error)
+ {
+ g_printerr("ERROR: %s\n", error->message);
+ g_error_free(error);
+ }
+ else
+ {
+ const int fd = xdp_session_open_pipewire_remote(media->session);
+
+ media_pw_init(media, media->app, fd);
+ }
+}
+#endif
+
+void
+media_init_screen_sharing(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app)
+{
+ g_assert((media) && (app));
+
+ media->app = app;
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+ if (app->portal)
+ xdp_portal_create_screencast_session(
+ app->portal,
+ XDP_OUTPUT_MONITOR | XDP_OUTPUT_WINDOW,
+ XDP_SCREENCAST_FLAG_NONE,
+ XDP_CURSOR_MODE_EMBEDDED,
+ XDP_PERSIST_MODE_NONE,
+ NULL,
+ NULL,
+ _request_screencast_callback,
+ media
+ );
+ else
+#endif
+ media_pw_init(media, app, -1);
+}
+
+static int
+destroy_global(void *obj,
+ UNUSED void *data)
+{
+ struct pw_properties *properties = (struct pw_properties*) obj;
+
+ if (!properties)
+ return 0;
+
+ pw_properties_free(properties);
+ return 0;
+}
+
+void
+media_pw_cleanup(MESSENGER_MediaInfo *media)
+{
+ g_assert(media);
+
+ if (media->pw.registry)
+ pw_proxy_destroy((struct pw_proxy*) media->pw.registry);
+
+ media->pw.registry = NULL;
+
+ pw_map_for_each(&(media->pw.globals), destroy_global, NULL);
+ pw_map_clear(&(media->pw.globals));
+
+ if (media->pw.core)
+ pw_core_disconnect(media->pw.core);
+
+ media->pw.core = NULL;
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+ if (media->session)
+ xdp_session_close(media->session);
+
+ media->session = NULL;
+#endif
+
+ media->app = NULL;
+}
+
+void
+media_pw_main_loop_run(MESSENGER_MediaInfo *media)
+{
+ g_assert(media);
+
+ if (!(media->pw.core))
+ return;
+
+ media->pw.pending = media->pw.core? pw_core_sync(media->pw.core, 0, 0) : 0;
+
+ application_pw_main_loop_run(media->app);
+}
+
+typedef struct IterateGlobalClosure
+{
+ MESSENGER_MediaNodeIterator iterator;
+ void *cls;
+} IterateGlobalClosure;
+
+static int
+iterate_global(void *obj,
+ void *data)
+{
+ g_assert(data);
+
+ IterateGlobalClosure *closure = (IterateGlobalClosure*) data;
+ struct pw_properties *properties = (struct pw_properties*) obj;
+
+ if (!properties)
+ return 0;
+
+ struct spa_dict *props = &(properties->dict);
+
+ if ((!props) || (!props->n_items))
+ return 0;
+
+ const char *name = NULL;
+ const char *description = NULL;
+ const char *role = NULL;
+
+ const struct spa_dict_item *item;
+ spa_dict_for_each(item, props)
+ {
+ if (0 == g_strcmp0(item->key, "node.name"))
+ name = item->value;
+
+ if (0 == g_strcmp0(item->key, "node.description"))
+ description = item->value;
+
+ if (0 == g_strcmp0(item->key, "media.role"))
+ role = item->value;
+ }
+
+ if ((!name) || (!description) || (!role))
+ return 0;
+
+ closure->iterator(closure->cls, name, description, role);
+ return 0;
+}
+
+void
+media_pw_iterate_nodes(MESSENGER_MediaInfo *media,
+ MESSENGER_MediaNodeIterator it,
+ void *cls)
+{
+ g_assert(media);
+
+ if (!it)
+ return;
+
+ IterateGlobalClosure closure;
+ closure.iterator = it;
+ closure.cls = cls;
+
+ pw_map_for_each(&(media->pw.globals), iterate_global, &closure);
+}
diff --git a/src/media.h b/src/media.h
@@ -0,0 +1,84 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2024 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file media.h
+ */
+
+#ifndef MEDIA_H_
+#define MEDIA_H_
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+#include <libportal/portal.h>
+#endif
+
+#include <pipewire/pipewire.h>
+
+typedef struct MESSENGER_Application MESSENGER_Application;
+
+typedef struct MESSENGER_MediaInfo
+{
+ MESSENGER_Application *app;
+
+#ifndef MESSENGER_APPLICATION_NO_PORTAL
+ XdpSession *session;
+#endif
+
+ struct {
+ struct pw_core *core;
+ struct pw_registry *registry;
+ struct spa_hook core_listener;
+ struct spa_hook registry_listener;
+ struct pw_map globals;
+ int pending;
+ } pw;
+} MESSENGER_MediaInfo;
+
+typedef void
+(*MESSENGER_MediaNodeIterator) (void *cls,
+ const char *name,
+ const char *description,
+ const char *role);
+
+void
+media_pw_init(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app,
+ int fd);
+
+void
+media_init_camera_capturing(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app);
+
+void
+media_init_screen_sharing(MESSENGER_MediaInfo *media,
+ MESSENGER_Application *app);
+
+void
+media_pw_cleanup(MESSENGER_MediaInfo *media);
+
+void
+media_pw_main_loop_run(MESSENGER_MediaInfo *media);
+
+void
+media_pw_iterate_nodes(MESSENGER_MediaInfo *media,
+ MESSENGER_MediaNodeIterator it,
+ void *cls);
+
+#endif /* MEDIA_H_ */
diff --git a/src/meson.build b/src/meson.build
@@ -28,6 +28,7 @@ messenger_gtk_sources = files([
'discourse.c', 'discourse.h',
'event.c', 'event.h',
'file.c', 'file.h',
+ 'media.c', 'media.h',
'request.c', 'request.h',
'resources.c', 'resources.h',
'schedule.c', 'schedule.h',
diff --git a/src/request.c b/src/request.c
@@ -103,7 +103,8 @@ _request_background_callback(GObject *source_object,
request_drop(request);
gboolean error_value = false;
- if (error) {
+ if (error)
+ {
g_printerr("ERROR: %s\n", error->message);
g_error_free(error);
@@ -176,7 +177,8 @@ _request_camera_callback(GObject *source_object,
request_drop(request);
gboolean error_value = false;
- if (error) {
+ if (error)
+ {
g_printerr("ERROR: %s\n", error->message);
g_error_free(error);
diff --git a/src/ui/new_contact.c b/src/ui/new_contact.c
@@ -324,47 +324,18 @@ _ui_new_contact_video_thread(void *args)
return NULL;
}
-static int
-iterate_global(void *obj,
- void *data)
+static void
+iterate_cameras(void *cls,
+ const char *name,
+ const char *description,
+ const char *role)
{
- g_assert(data);
-
- UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data;
- struct pw_properties *properties = (struct pw_properties*) obj;
-
- if (!properties)
- return 0;
-
- struct spa_dict *props = &(properties->dict);
-
- if ((!props) || (!props->n_items))
- return 0;
+ g_assert(cls);
- gboolean is_camera = FALSE;
- const char *name = NULL;
- const char *description = NULL;
+ UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) cls;
- const struct spa_dict_item *item;
- spa_dict_for_each(item, props)
- {
- if (0 == g_strcmp0(item->key, "node.name"))
- name = item->value;
-
- if (0 == g_strcmp0(item->key, "node.description"))
- description = item->value;
-
- if (0 != g_strcmp0(item->key, "media.role"))
- continue;
-
- if (0 != g_strcmp0(item->value, "Camera"))
- continue;
-
- is_camera = TRUE;
- }
-
- if ((!is_camera) || (!name) || (!description))
- return 0;
+ if (0 != g_strcmp0(role, "Camera"))
+ return;
GtkTreeIter iter;
gtk_list_store_append(handle->camera_list_store, &iter);
@@ -377,7 +348,6 @@ iterate_global(void *obj,
);
handle->camera_count++;
- return 0;
}
static void
@@ -395,10 +365,10 @@ _init_camera_pipeline(MESSENGER_Application *app,
if (access)
#endif
{
- application_pw_core_init(app);
- application_pw_main_loop_run(app);
+ media_init_camera_capturing(&(app->media.camera), app);
+ media_pw_main_loop_run(&(app->media.camera));
- pw_map_for_each(&(app->pw.globals), iterate_global, handle);
+ media_pw_iterate_nodes(&(app->media.camera), iterate_cameras, handle);
if (handle->camera_count)
gtk_combo_box_set_active(handle->camera_combo_box, 0);