messenger-gtk

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

commit ba7c65188a8adcaa8d809c81517108ad62f1b245
parent 73095c3fcfd59636303d0de859f0fad003a5cd80
Author: Jacki <jacki@thejackimonster.de>
Date:   Tue, 13 Aug 2024 17:47:59 +0200

Adjust code for handling screencast via async request (WIP)

Signed-off-by: Jacki <jacki@thejackimonster.de>

Diffstat:
Msrc/application.c | 47+++++++++++++++++++++++++++++++++++++++++++++++
Msrc/application.h | 27+++++++++++++++++++++++++++
Msrc/discourse.c | 2+-
Msrc/media.c | 81+++++++++++++++++++------------------------------------------------------------
Msrc/media.h | 11++---------
Msrc/request.c | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/request.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/discourse.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ui/new_contact.c | 10++++++++--
9 files changed, 373 insertions(+), 74 deletions(-)

diff --git a/src/application.c b/src/application.c @@ -218,6 +218,7 @@ application_init(MESSENGER_Application *app, #ifndef MESSENGER_APPLICATION_NO_PORTAL GError *error = NULL; app->portal = xdp_portal_initable_new(&error); + app->session = NULL; if (error) { @@ -412,6 +413,50 @@ application_pw_main_loop_run(MESSENGER_Application *app) pw_main_loop_run(app->pw.main_loop); } +#ifndef MESSENGER_APPLICATION_NO_PORTAL +void +application_set_active_session(MESSENGER_Application *app, + XdpSession *session) +{ + g_assert(app); + + if (app->session == session) + return; + + if (app->session) + { + const XdpSessionState state = xdp_session_get_session_state( + app->session + ); + + if (XDP_SESSION_ACTIVE == state) + xdp_session_close(app->session); + + g_object_unref(app->session); + } + + app->session = session; +} + +int +application_get_active_session_remote(MESSENGER_Application *app) +{ + g_assert(app); + + if (!(app->session)) + return -1; + + const XdpSessionState state = xdp_session_get_session_state( + app->session + ); + + if (XDP_SESSION_ACTIVE != state) + return -1; + + return xdp_session_open_pipewire_remote(app->session); +} +#endif + static void _request_background_callback(MESSENGER_Application *app, gboolean success, @@ -651,6 +696,8 @@ application_exit(MESSENGER_Application *app, } #ifndef MESSENGER_APPLICATION_NO_PORTAL + application_set_active_session(app, NULL); + if (app->portal) g_object_unref(app->portal); diff --git a/src/application.h b/src/application.h @@ -94,6 +94,7 @@ typedef struct MESSENGER_Application #ifndef MESSENGER_APPLICATION_NO_PORTAL XdpPortal *portal; XdpParent *parent; + XdpSession *session; #endif struct { @@ -235,6 +236,32 @@ application_pw_core_cleanup(MESSENGER_Application *app); void application_pw_main_loop_run(MESSENGER_Application *app); +#ifndef MESSENGER_APPLICATION_NO_PORTAL +/** + * Sets the active session for the messenger + * application and frees the previous one + * if any is still active. + * + * @param app Messenger application + * @param session Screencast session + */ +void +application_set_active_session(MESSENGER_Application *app, + XdpSession *session); + +/** + * Returns the file descriptor to the pipewire + * remote where the screencast streams of the + * active session from the messenger application + * are available. + * + * @param app Messenger application + * @return File descriptor + */ +int +application_get_active_session_remote(MESSENGER_Application *app); +#endif + /** * Shows the messenger application main window. * diff --git a/src/discourse.c b/src/discourse.c @@ -861,7 +861,7 @@ discourse_has_controls(struct GNUNET_CHAT_Discourse *discourse, case MESSENGER_DISCOURSE_CTRL_WEBCAM: return (info->video_record_pipeline? TRUE : FALSE); case MESSENGER_DISCOURSE_CTRL_SCREEN_CAPTURE: - return FALSE; + return (info->video_record_pipeline? TRUE : FALSE); default: return FALSE; } diff --git a/src/media.c b/src/media.c @@ -179,64 +179,18 @@ media_init_camera_capturing(MESSENGER_MediaInfo *media, 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; + int fd = -1; #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 + fd = application_get_active_session_remote(app); #endif - media_pw_init(media, app, -1); + + media_pw_init(media, app, fd); } static int @@ -269,14 +223,6 @@ media_pw_cleanup(MESSENGER_MediaInfo *media) 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; } @@ -318,7 +264,8 @@ iterate_global(void *obj, const char *name = NULL; const char *description = NULL; - const char *role = NULL; + const char *media_role = NULL; + const char *media_class = NULL; const struct spa_dict_item *item; spa_dict_for_each(item, props) @@ -329,14 +276,24 @@ iterate_global(void *obj, if (0 == g_strcmp0(item->key, "node.description")) description = item->value; + if (0 == g_strcmp0(item->key, "media.class")) + media_class = item->value; + if (0 == g_strcmp0(item->key, "media.role")) - role = item->value; + media_role = item->value; } - if ((!name) || (!description) || (!role)) + if ((!name) || (!media_class)) return 0; - closure->iterator(closure->cls, name, description, role); + closure->iterator( + closure->cls, + name, + description, + media_class, + media_role + ); + return 0; } diff --git a/src/media.h b/src/media.h @@ -25,10 +25,6 @@ #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; @@ -36,10 +32,6 @@ 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; @@ -55,7 +47,8 @@ typedef void (*MESSENGER_MediaNodeIterator) (void *cls, const char *name, const char *description, - const char *role); + const char *media_class, + const char *media_role); /** * Initialize a media info structure to list diff --git a/src/request.c b/src/request.c @@ -24,6 +24,10 @@ #include "request.h" +#ifndef MESSENGER_APPLICATION_NO_PORTAL +#include <libportal/portal.h> +#endif + #ifdef MESSENGER_APPLICATION_NO_PORTAL static gboolean @@ -224,6 +228,151 @@ request_new_camera(MESSENGER_Application *application, return request; } +#ifndef MESSENGER_APPLICATION_NO_PORTAL +static void +_request_session_start_callback(GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_assert((source_object) && (result) && (user_data)); + + XdpSession *session = (XdpSession*) source_object; + MESSENGER_Request *request = (MESSENGER_Request*) user_data; + + request_cleanup(request); + + MESSENGER_Application *app = request->application; + MESSENGER_RequestCallback callback = request->callback; + gpointer data = request->user_data; + + GError *error = NULL; + gboolean success = xdp_session_start_finish( + session, + result, + &error + ); + + application_set_active_session(app, success? session : NULL); + request_drop(request); + + gboolean error_value = false; + if (error) + { + g_printerr("ERROR: %s\n", error->message); + g_error_free(error); + + error_value = true; + } + + if (callback) + callback(app, success, error_value, data); +} + +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_Request *request = (MESSENGER_Request*) user_data; + + request_cleanup(request); + + MESSENGER_Application *app = request->application; + MESSENGER_RequestCallback callback = request->callback; + gpointer data = request->user_data; + + GError *error = NULL; + XdpSession *session = xdp_portal_create_screencast_session_finish( + portal, result, &error + ); + + if (session) + application_set_active_session(app, session); + request_drop(request); + + gboolean error_value = false; + if (error) + { + g_printerr("ERROR: %s\n", error->message); + g_error_free(error); + + error_value = true; + } + + if (!session) + goto skip_session_start; + + GCancellable* cancellable = g_cancellable_new(); + + if (!cancellable) + goto skip_session_start; + + request = request_new( + app, + callback, + cancellable, + user_data + ); + + xdp_session_start( + session, + app->parent, + cancellable, + _request_session_start_callback, + request + ); + + return; + +skip_session_start: + if (callback) + callback(app, false, error_value, data); +} +#endif + +MESSENGER_Request* +request_new_screencast(MESSENGER_Application *application, + XdpOutputType outputs, + XdpScreencastFlags flags, + XdpCursorMode cursor_mode, + XdpPersistMode persist_mode, + MESSENGER_RequestCallback callback, + gpointer user_data) +{ + g_assert((application) && (callback)); + + GCancellable* cancellable = g_cancellable_new(); + + if (!cancellable) + return NULL; + + MESSENGER_Request* request = request_new( + application, + callback, + cancellable, + user_data + ); + +#ifndef MESSENGER_APPLICATION_NO_PORTAL + xdp_portal_create_screencast_session( + application->portal, + outputs, + flags, + cursor_mode, + persist_mode, + NULL, + cancellable, + _request_screencast_callback, + request + ); +#endif + + return request; +} + void request_cancel(MESSENGER_Request *request) { diff --git a/src/request.h b/src/request.h @@ -39,6 +39,30 @@ typedef enum XdpBackgroundFlags { typedef enum XdpCameraFlags { XDP_CAMERA_FLAG_NONE = 0, } XdpCameraFlags; + +typedef enum XdpOutputType { + XDP_OUTPUT_NONE = 0, + XDP_OUTPUT_MONITOR = 1, + XDP_OUTPUT_WINDOW = 2, + XDP_OUTPUT_VIRTUAL = 4, +} XdpOutputType; + +typedef enum XdpScreencastFlags { + XDP_SCREENCAST_FLAG_NONE = 0, + XDP_SCREENCAST_FLAG_MULTIPLE = 1, +} XdpScreencastFlags; + +typedef enum XdpCursorMode { + XDP_CURSOR_MODE_HIDDEN = 1, + XDP_CURSOR_MODE_EMBEDDED = 2, + XDP_CURSOR_MODE_METADATA = 4, +} XdpCursorMode; + +typedef enum XdpPersistMode { + XDP_PERSIST_MODE_NONE = 0, + XDP_PERSIST_MODE_TRANSIENT = 1, + XDP_PERSIST_MODE_PERSISTENT = 2, +} XdpPersistMode; #endif #include "application.h" @@ -112,6 +136,28 @@ request_new_camera(MESSENGER_Application *application, gpointer user_data); /** + * Creates a new request for the messsenger + * application for a screencast permission. + * + * @param application Messenger application + * @param outputs Output types + * @param flags Screencast flags + * @param cursor_mode Cursor mode + * @param persist_mode Persist mode + * @param callback Callback + * @param user_data User data + * @return New camera request object + */ +MESSENGER_Request* +request_new_screencast(MESSENGER_Application *application, + XdpOutputType outputs, + XdpScreencastFlags flags, + XdpCursorMode cursor_mode, + XdpPersistMode persist_mode, + MESSENGER_RequestCallback callback, + gpointer user_data); + +/** * Cancel a request object if possible. * * @param request Request object diff --git a/src/ui/discourse.c b/src/ui/discourse.c @@ -34,6 +34,7 @@ #include "../application.h" #include "../discourse.h" +#include "../request.h" #include "../ui.h" #include "../util.h" @@ -123,6 +124,72 @@ handle_camera_button_click(UNUSED GtkButton *button, } static void +iterate_streams(void *cls, + const char *name, + const char *description, + const char *media_class, + const char *media_role) +{ + g_assert(cls); + + UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) cls; + + if ((!name) || (!description) || (!media_class) || (!media_role)) + return; + + if (0 != g_strcmp0(media_class, "Stream/Output/Video")) + return; + + // TODO +} + +static void +_request_screen_callback(MESSENGER_Application *app, + gboolean success, + gboolean error, + gpointer user_data) +{ + g_assert((app) && (user_data)); + + UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data; + +#ifndef MESSENGER_APPLICATION_NO_PORTAL + if ((app->portal) && (success)) +#else + if (success) +#endif + { + media_init_screen_sharing(&(app->media.screen), app); + media_pw_main_loop_run(&(app->media.screen)); + + media_pw_iterate_nodes(&(app->media.screen), iterate_streams, handle); + + // TODO + } +} + +static void +handle_screen_button_click(UNUSED GtkButton *button, + gpointer user_data) +{ + g_assert(user_data); + + UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data; + + // TODO + + request_new_screencast( + handle->app, + XDP_OUTPUT_MONITOR | XDP_OUTPUT_WINDOW, + XDP_SCREENCAST_FLAG_NONE, + XDP_CURSOR_MODE_EMBEDDED, + XDP_PERSIST_MODE_TRANSIENT, + _request_screen_callback, + handle + ); +} + +static void handle_speakers_button_value_changed(UNUSED GtkScaleButton *button, gdouble value, gpointer user_data) @@ -334,6 +401,13 @@ ui_discourse_window_init(MESSENGER_Application *app, ); g_signal_connect( + handle->screen_button, + "clicked", + G_CALLBACK(handle_screen_button_click), + handle + ); + + g_signal_connect( handle->speakers_button, "value-changed", G_CALLBACK(handle_speakers_button_value_changed), diff --git a/src/ui/new_contact.c b/src/ui/new_contact.c @@ -328,13 +328,19 @@ static void iterate_cameras(void *cls, const char *name, const char *description, - const char *role) + const char *media_class, + const char *media_role) { g_assert(cls); UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) cls; - if (0 != g_strcmp0(role, "Camera")) + if ((!name) || (!description) || (!media_class) || (!media_role)) + return; + + if (0 != g_strcmp0(media_class, "Video/Source")) + return; + if (0 != g_strcmp0(media_role, "Camera")) return; GtkTreeIter iter;