messenger-gtk

Gtk+3 graphical user interfaces for GNUnet Messenger
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/application.c | 174++-----------------------------------------------------------------------------
Msrc/application.h | 12++++++------
Asrc/media.c | 358+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/media.h | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/meson.build | 1+
Msrc/request.c | 6++++--
Msrc/ui/new_contact.c | 54++++++++++++------------------------------------------
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), - &registry_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), + &registry_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);