aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/capture/portal.c95
-rw-r--r--src/dbus.c913
-rw-r--r--src/main.cpp32
-rw-r--r--src/pipewire_video.c33
4 files changed, 1036 insertions, 37 deletions
diff --git a/src/capture/portal.c b/src/capture/portal.c
index 581a2ed..d2217d1 100644
--- a/src/capture/portal.c
+++ b/src/capture/portal.c
@@ -2,7 +2,7 @@
#include "../../include/color_conversion.h"
#include "../../include/egl.h"
#include "../../include/utils.h"
-#include "../../dbus/client/dbus_client.h"
+#include "../../include/dbus.h"
#include "../../include/pipewire_video.h"
#include <stdlib.h>
@@ -11,13 +11,22 @@
#include <limits.h>
#include <assert.h>
+#define PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE 60
+
+typedef enum {
+ PORTAL_CAPTURE_SETUP_IDLE,
+ PORTAL_CAPTURE_SETUP_IN_PROGRESS,
+ PORTAL_CAPTURE_SETUP_FINISHED,
+ PORTAL_CAPTURE_SETUP_FAILED
+} gsr_portal_capture_setup_state;
+
typedef struct {
gsr_capture_portal_params params;
gsr_texture_map texture_map;
- gsr_dbus_client dbus_client;
- char session_handle[128];
+ gsr_dbus dbus;
+ char *session_handle;
gsr_pipewire_video pipewire;
vec2i capture_size;
@@ -29,6 +38,9 @@ typedef struct {
uint32_t pipewire_fourcc;
uint64_t pipewire_modifiers;
bool using_external_image;
+
+ bool should_stop;
+ bool stop_is_error;
} gsr_capture_portal;
static void gsr_capture_portal_cleanup_plane_fds(gsr_capture_portal *self) {
@@ -59,7 +71,7 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) {
gsr_capture_portal_cleanup_plane_fds(self);
gsr_pipewire_video_deinit(&self->pipewire);
- gsr_dbus_client_deinit(&self->dbus_client);
+ gsr_dbus_deinit(&self->dbus);
}
static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) {
@@ -183,36 +195,36 @@ static int gsr_capture_portal_setup_dbus(gsr_capture_portal *self, int *pipewire
if(self->params.restore_portal_session)
gsr_capture_portal_get_restore_token_from_cache(restore_token, sizeof(restore_token), self->params.portal_session_token_filepath);
- if(!gsr_dbus_client_init(&self->dbus_client, restore_token))
+ if(!gsr_dbus_init(&self->dbus, restore_token))
return -1;
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: CreateSession\n");
- response_status = gsr_dbus_client_screencast_create_session(&self->dbus_client, self->session_handle, sizeof(self->session_handle));
+ response_status = gsr_dbus_screencast_create_session(&self->dbus, &self->session_handle);
if(response_status != 0) {
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: CreateSession failed\n");
return response_status;
}
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: SelectSources\n");
- response_status = gsr_dbus_client_screencast_select_sources(&self->dbus_client, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_ALL, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN);
+ response_status = gsr_dbus_screencast_select_sources(&self->dbus, self->session_handle, GSR_PORTAL_CAPTURE_TYPE_ALL, self->params.record_cursor ? GSR_PORTAL_CURSOR_MODE_EMBEDDED : GSR_PORTAL_CURSOR_MODE_HIDDEN);
if(response_status != 0) {
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: SelectSources failed\n");
return response_status;
}
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: Start\n");
- response_status = gsr_dbus_client_screencast_start(&self->dbus_client, self->session_handle, pipewire_node);
+ response_status = gsr_dbus_screencast_start(&self->dbus, self->session_handle, pipewire_node);
if(response_status != 0) {
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: Start failed\n");
return response_status;
}
- const char *screencast_restore_token = gsr_dbus_client_screencast_get_restore_token(&self->dbus_client);
+ const char *screencast_restore_token = gsr_dbus_screencast_get_restore_token(&self->dbus);
if(screencast_restore_token)
gsr_capture_portal_save_restore_token(screencast_restore_token, self->params.portal_session_token_filepath);
fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: OpenPipeWireRemote\n");
- if(!gsr_dbus_client_screencast_open_pipewire_remote(&self->dbus_client, self->session_handle, pipewire_fd)) {
+ if(!gsr_dbus_screencast_open_pipewire_remote(&self->dbus, self->session_handle, pipewire_fd)) {
fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: OpenPipeWireRemote failed\n");
return -1;
}
@@ -239,45 +251,51 @@ static bool gsr_capture_portal_get_frame_dimensions(gsr_capture_portal *self) {
return false;
}
-static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
- gsr_capture_portal *self = cap->priv;
-
+static int gsr_capture_portal_setup(gsr_capture_portal *self, int fps) {
gsr_capture_portal_create_input_textures(self);
int pipewire_fd = 0;
uint32_t pipewire_node = 0;
const int response_status = gsr_capture_portal_setup_dbus(self, &pipewire_fd, &pipewire_node);
if(response_status != 0) {
- gsr_capture_portal_stop(self);
// Response status values:
// 0: Success, the request is carried out
// 1: The user cancelled the interaction
// 2: The user interaction was ended in some other way
// Response status value 2 happens usually if there was some kind of error in the desktop portal on the system
if(response_status == 2) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. Either you Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system\n");
return 50;
} else if(response_status == 1) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
- return 60;
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: desktop portal capture failed. It seems like desktop portal capture was canceled by the user.\n");
+ return PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
} else {
return -1;
}
}
- fprintf(stderr, "gsr info: gsr_capture_portal_start: setting up pipewire\n");
+ fprintf(stderr, "gsr info: gsr_capture_portal_setup: setting up pipewire\n");
/* TODO: support hdr when pipewire supports it */
/* gsr_pipewire closes the pipewire fd, even on failure */
- if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, capture_metadata->fps, self->params.record_cursor, self->params.egl)) {
- fprintf(stderr, "gsr error: gsr_capture_portal_start: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
- gsr_capture_portal_stop(self);
+ if(!gsr_pipewire_video_init(&self->pipewire, pipewire_fd, pipewire_node, fps, self->params.record_cursor, self->params.egl)) {
+ fprintf(stderr, "gsr error: gsr_capture_portal_setup: failed to setup pipewire with fd: %d, node: %" PRIu32 "\n", pipewire_fd, pipewire_node);
return -1;
}
- fprintf(stderr, "gsr info: gsr_capture_portal_start: pipewire setup finished\n");
+ fprintf(stderr, "gsr info: gsr_capture_portal_setup: pipewire setup finished\n");
- if(!gsr_capture_portal_get_frame_dimensions(self)) {
- gsr_capture_portal_stop(self);
+ if(!gsr_capture_portal_get_frame_dimensions(self))
return -1;
+
+ return 0;
+}
+
+static int gsr_capture_portal_start(gsr_capture *cap, gsr_capture_metadata *capture_metadata) {
+ gsr_capture_portal *self = cap->priv;
+
+ const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
+ if(result != 0) {
+ gsr_capture_portal_stop(self);
+ return result;
}
if(self->params.output_resolution.x == 0 && self->params.output_resolution.y == 0) {
@@ -296,10 +314,29 @@ static int max_int(int a, int b) {
return a > b ? a : b;
}
+static bool gsr_capture_portal_capture_has_synchronous_task(gsr_capture *cap) {
+ gsr_capture_portal *self = cap->priv;
+ return gsr_pipewire_video_should_restart(&self->pipewire);
+}
+
static int gsr_capture_portal_capture(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion) {
(void)color_conversion;
gsr_capture_portal *self = cap->priv;
+ if(self->should_stop)
+ return -1;
+
+ if(gsr_pipewire_video_should_restart(&self->pipewire)) {
+ fprintf(stderr, "gsr info: gsr_capture_portal_capture: pipewire capture was paused, trying to start capture again\n");
+ gsr_capture_portal_stop(self);
+ const int result = gsr_capture_portal_setup(self, capture_metadata->fps);
+ if(result != 0) {
+ self->stop_is_error = result != PORTAL_CAPTURE_CANCELED_BY_USER_EXIT_CODE;
+ self->should_stop = true;
+ }
+ return -1;
+ }
+
/* TODO: Handle formats other than RGB(A) */
if(self->num_dmabuf_data == 0) {
if(gsr_pipewire_video_map_texture(&self->pipewire, self->texture_map, &self->region, &self->cursor_region, self->dmabuf_data, &self->num_dmabuf_data, &self->pipewire_fourcc, &self->pipewire_modifiers, &self->using_external_image)) {
@@ -362,6 +399,13 @@ static bool gsr_capture_portal_uses_external_image(gsr_capture *cap) {
return true;
}
+static bool gsr_capture_portal_should_stop(gsr_capture *cap, bool *err) {
+ gsr_capture_portal *self = cap->priv;
+ if(err)
+ *err = self->stop_is_error;
+ return self->should_stop;
+}
+
static bool gsr_capture_portal_is_damaged(gsr_capture *cap) {
gsr_capture_portal *self = cap->priv;
return gsr_pipewire_video_is_damaged(&self->pipewire);
@@ -403,7 +447,8 @@ gsr_capture* gsr_capture_portal_create(const gsr_capture_portal_params *params)
*cap = (gsr_capture) {
.start = gsr_capture_portal_start,
.tick = NULL,
- .should_stop = NULL,
+ .should_stop = gsr_capture_portal_should_stop,
+ .capture_has_synchronous_task = gsr_capture_portal_capture_has_synchronous_task,
.capture = gsr_capture_portal_capture,
.uses_external_image = gsr_capture_portal_uses_external_image,
.is_damaged = gsr_capture_portal_is_damaged,
diff --git a/src/dbus.c b/src/dbus.c
new file mode 100644
index 0000000..f12891f
--- /dev/null
+++ b/src/dbus.c
@@ -0,0 +1,913 @@
+#include "../include/dbus.h"
+
+#include <sys/random.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+/* TODO: Make non-blocking when GPU Screen Recorder is turned into a library */
+/* TODO: Make sure responses matches the requests */
+
+#define DESKTOP_PORTAL_SIGNAL_RULE "type='signal',interface='org.freedesktop.Portal.Request'"
+
+typedef enum {
+ DICT_TYPE_STRING,
+ DICT_TYPE_UINT32,
+ DICT_TYPE_BOOL,
+} dict_value_type;
+
+typedef struct {
+ const char *key;
+ dict_value_type value_type;
+ union {
+ char *str;
+ dbus_uint32_t u32;
+ dbus_bool_t boolean;
+ };
+} dict_entry;
+
+static bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) {
+ /* TODO: Use other functions on other platforms than linux */
+ if(getrandom(buffer, buffer_size, 0) < buffer_size) {
+ fprintf(stderr, "Failed to get random bytes, error: %s\n", strerror(errno));
+ return false;
+ }
+
+ for(int i = 0; i < buffer_size; ++i) {
+ unsigned char c = *(unsigned char*)&buffer[i];
+ buffer[i] = alphabet[c % alphabet_size];
+ }
+
+ return true;
+}
+
+static bool generate_random_characters_standard_alphabet(char *buffer, int buffer_size) {
+ return generate_random_characters(buffer, buffer_size, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62);
+}
+
+static const char* dict_value_type_to_string(dict_value_type type) {
+ switch(type) {
+ case DICT_TYPE_STRING: return "string";
+ case DICT_TYPE_UINT32: return "uint32";
+ case DICT_TYPE_BOOL: return "boolean";
+ }
+ return "(unknown)";
+}
+
+bool gsr_dbus_init(gsr_dbus *self, const char *screencast_restore_token) {
+ memset(self, 0, sizeof(*self));
+ dbus_error_init(&self->err);
+
+ self->random_str[DBUS_RANDOM_STR_SIZE] = '\0';
+ if(!generate_random_characters_standard_alphabet(self->random_str, DBUS_RANDOM_STR_SIZE)) {
+ fprintf(stderr, "gsr error: gsr_dbus_init: failed to generate random string\n");
+ return false;
+ }
+
+ self->con = dbus_bus_get(DBUS_BUS_SESSION, &self->err);
+ if(dbus_error_is_set(&self->err)) {
+ fprintf(stderr, "gsr error: gsr_dbus_init: dbus_bus_get failed with error: %s\n", self->err.message);
+ return false;
+ }
+
+ if(!self->con) {
+ fprintf(stderr, "gsr error: gsr_dbus_init: failed to get dbus session\n");
+ return false;
+ }
+
+ /* TODO: Check the name */
+ const int ret = dbus_bus_request_name(self->con, "com.dec05eba.gpu_screen_recorder", DBUS_NAME_FLAG_REPLACE_EXISTING, &self->err);
+ if(dbus_error_is_set(&self->err)) {
+ fprintf(stderr, "gsr error: gsr_dbus_init: dbus_bus_request_name failed with error: %s\n", self->err.message);
+ gsr_dbus_deinit(self);
+ return false;
+ }
+
+ if(screencast_restore_token) {
+ self->screencast_restore_token = strdup(screencast_restore_token);
+ if(!self->screencast_restore_token) {
+ fprintf(stderr, "gsr error: gsr_dbus_init: failed to clone restore token\n");
+ gsr_dbus_deinit(self);
+ return false;
+ }
+ }
+
+ (void)ret;
+ // if(ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
+ // fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: dbus_bus_request_name failed to get primary owner\n");
+ // return false;
+ // }
+
+ return true;
+}
+
+void gsr_dbus_deinit(gsr_dbus *self) {
+ if(self->screencast_restore_token) {
+ free(self->screencast_restore_token);
+ self->screencast_restore_token = NULL;
+ }
+
+ if(self->desktop_portal_rule_added) {
+ dbus_bus_remove_match(self->con, DESKTOP_PORTAL_SIGNAL_RULE, NULL);
+ // dbus_connection_flush(self->con);
+ self->desktop_portal_rule_added = false;
+ }
+
+ if(self->con) {
+ dbus_error_free(&self->err);
+
+ dbus_bus_release_name(self->con, "com.dec05eba.gpu_screen_recorder", NULL);
+
+ // Apparently shouldn't be used when a connection is setup by using dbus_bus_get
+ //dbus_connection_close(self->con);
+ dbus_connection_unref(self->con);
+ self->con = NULL;
+ }
+}
+
+static bool gsr_dbus_desktop_portal_get_property(gsr_dbus *self, const char *interface, const char *property_name, uint32_t *result) {
+ *result = 0;
+
+ DBusMessage *msg = dbus_message_new_method_call(
+ "org.freedesktop.portal.Desktop", // target for the method call
+ "/org/freedesktop/portal/desktop", // object to call on
+ "org.freedesktop.DBus.Properties", // interface to call on
+ "Get"); // method name
+ if(!msg) {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: dbus_message_new_method_call failed\n");
+ return false;
+ }
+
+ DBusMessageIter it;
+ dbus_message_iter_init_append(msg, &it);
+
+ if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &interface)) {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: failed to add interface\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &property_name)) {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: failed to add property_name\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ DBusPendingCall *pending = NULL;
+ if(!dbus_connection_send_with_reply(self->con, msg, &pending, -1) || !pending) { // -1 is default timeout
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: dbus_connection_send_with_reply failed\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+ dbus_connection_flush(self->con);
+
+ //fprintf(stderr, "Request Sent\n");
+
+ dbus_message_unref(msg);
+ msg = NULL;
+
+ dbus_pending_call_block(pending);
+
+ msg = dbus_pending_call_steal_reply(pending);
+ if(!msg) {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: dbus_pending_call_steal_reply failed\n");
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ dbus_pending_call_unref(pending);
+ pending = NULL;
+
+ DBusMessageIter resp_args;
+ if(!dbus_message_iter_init(msg, &resp_args)) {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: response message is missing arguments\n");
+ dbus_message_unref(msg);
+ return false;
+ } else if(DBUS_TYPE_UINT32 == dbus_message_iter_get_arg_type(&resp_args)) {
+ dbus_message_iter_get_basic(&resp_args, result);
+ } else if(DBUS_TYPE_VARIANT == dbus_message_iter_get_arg_type(&resp_args)) {
+ DBusMessageIter variant_iter;
+ dbus_message_iter_recurse(&resp_args, &variant_iter);
+
+ if(dbus_message_iter_get_arg_type(&variant_iter) == DBUS_TYPE_UINT32) {
+ dbus_message_iter_get_basic(&variant_iter, result);
+ } else {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: response message is not a variant with an uint32, %c\n", dbus_message_iter_get_arg_type(&variant_iter));
+ dbus_message_unref(msg);
+ return false;
+ }
+ } else {
+ fprintf(stderr, "gsr error: gsr_dbus_desktop_portal_get_property: response message is not an uint32, %c\n", dbus_message_iter_get_arg_type(&resp_args));
+ dbus_message_unref(msg);
+ return false;
+ // TODO: Check dbus_error_is_set?
+ }
+
+ dbus_message_unref(msg);
+ return true;
+}
+
+static uint32_t gsr_dbus_get_screencast_version_cached(gsr_dbus *self) {
+ if(self->screencast_version == 0)
+ gsr_dbus_desktop_portal_get_property(self, "org.freedesktop.portal.ScreenCast", "version", &self->screencast_version);
+ return self->screencast_version;
+}
+
+static bool gsr_dbus_ensure_desktop_portal_rule_added(gsr_dbus *self) {
+ if(self->desktop_portal_rule_added)
+ return true;
+
+ dbus_bus_add_match(self->con, DESKTOP_PORTAL_SIGNAL_RULE, &self->err);
+ dbus_connection_flush(self->con);
+ if(dbus_error_is_set(&self->err)) {
+ fprintf(stderr, "gsr error: gsr_dbus_ensure_desktop_portal_rule_added: failed to add dbus rule %s, error: %s\n", DESKTOP_PORTAL_SIGNAL_RULE, self->err.message);
+ return false;
+ }
+ self->desktop_portal_rule_added = true;
+ return true;
+}
+
+static void gsr_dbus_portal_get_unique_handle_token(gsr_dbus *self, char *buffer, int size) {
+ snprintf(buffer, size, "gpu_screen_recorder_handle_%s_%u", self->random_str, self->handle_counter++);
+}
+
+static void gsr_dbus_portal_get_unique_session_token(gsr_dbus *self, char *buffer, int size) {
+ snprintf(buffer, size, "gpu_screen_recorder_session_%s", self->random_str);
+}
+
+static bool dbus_add_dict(DBusMessageIter *it, const dict_entry *entries, int num_entries) {
+ DBusMessageIter array_it;
+ if(!dbus_message_iter_open_container(it, DBUS_TYPE_ARRAY, "{sv}", &array_it))
+ return false;
+
+ for (int i = 0; i < num_entries; ++i) {
+ DBusMessageIter entry_it = DBUS_MESSAGE_ITER_INIT_CLOSED;
+ DBusMessageIter variant_it = DBUS_MESSAGE_ITER_INIT_CLOSED;
+
+ if(!dbus_message_iter_open_container(&array_it, DBUS_TYPE_DICT_ENTRY, NULL, &entry_it))
+ goto entry_err;
+
+ if(!dbus_message_iter_append_basic(&entry_it, DBUS_TYPE_STRING, &entries[i].key))
+ goto entry_err;
+
+ switch (entries[i].value_type) {
+ case DICT_TYPE_STRING: {
+ if(!dbus_message_iter_open_container(&entry_it, DBUS_TYPE_VARIANT, DBUS_TYPE_STRING_AS_STRING, &variant_it))
+ goto entry_err;
+ if(!dbus_message_iter_append_basic(&variant_it, DBUS_TYPE_STRING, &entries[i].str))
+ goto entry_err;
+ break;
+ }
+ case DICT_TYPE_UINT32: {
+ if(!dbus_message_iter_open_container(&entry_it, DBUS_TYPE_VARIANT, DBUS_TYPE_UINT32_AS_STRING, &variant_it))
+ goto entry_err;
+ if(!dbus_message_iter_append_basic(&variant_it, DBUS_TYPE_UINT32, &entries[i].u32))
+ goto entry_err;
+ break;
+ }
+ case DICT_TYPE_BOOL: {
+ if(!dbus_message_iter_open_container(&entry_it, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant_it))
+ goto entry_err;
+ if(!dbus_message_iter_append_basic(&variant_it, DBUS_TYPE_BOOLEAN, &entries[i].boolean))
+ goto entry_err;
+ break;
+ }
+ }
+
+ dbus_message_iter_close_container(&entry_it, &variant_it);
+ dbus_message_iter_close_container(&array_it, &entry_it);
+ continue;
+
+ entry_err:
+ dbus_message_iter_abandon_container_if_open(&array_it, &variant_it);
+ dbus_message_iter_abandon_container_if_open(&array_it, &entry_it);
+ dbus_message_iter_abandon_container_if_open(it, &array_it);
+ return false;
+ }
+
+ return dbus_message_iter_close_container(it, &array_it);
+}
+
+/* If |response_msg| is NULL then we dont wait for a response signal */
+static bool gsr_dbus_call_screencast_method(gsr_dbus *self, const char *method_name, const char *session_handle, const char *parent_window, const dict_entry *entries, int num_entries, int *resp_fd, DBusMessage **response_msg) {
+ if(resp_fd)
+ *resp_fd = -1;
+
+ if(response_msg)
+ *response_msg = NULL;
+
+ if(!gsr_dbus_ensure_desktop_portal_rule_added(self))
+ return false;
+
+ DBusMessage *msg = dbus_message_new_method_call(
+ "org.freedesktop.portal.Desktop", // target for the method call
+ "/org/freedesktop/portal/desktop", // object to call on
+ "org.freedesktop.portal.ScreenCast", // interface to call on
+ method_name); // method name
+ if(!msg) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: dbus_message_new_method_call failed\n");
+ return false;
+ }
+
+ DBusMessageIter it;
+ dbus_message_iter_init_append(msg, &it);
+
+ if(session_handle) {
+ if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_OBJECT_PATH, &session_handle)) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: failed to add session_handle\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+ }
+
+ if(parent_window) {
+ if(!dbus_message_iter_append_basic(&it, DBUS_TYPE_STRING, &parent_window)) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: failed to add parent_window\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+ }
+
+ if(!dbus_add_dict(&it, entries, num_entries)) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: failed to add dict\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ DBusPendingCall *pending = NULL;
+ if(!dbus_connection_send_with_reply(self->con, msg, &pending, -1) || !pending) { // -1 is default timeout
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: dbus_connection_send_with_reply failed\n");
+ dbus_message_unref(msg);
+ return false;
+ }
+ dbus_connection_flush(self->con);
+
+ //fprintf(stderr, "Request Sent\n");
+
+ dbus_message_unref(msg);
+ msg = NULL;
+
+ dbus_pending_call_block(pending);
+
+ msg = dbus_pending_call_steal_reply(pending);
+ if(!msg) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: dbus_pending_call_steal_reply failed\n");
+ dbus_pending_call_unref(pending);
+ dbus_message_unref(msg);
+ return false;
+ }
+
+ dbus_pending_call_unref(pending);
+ pending = NULL;
+
+ DBusMessageIter resp_args;
+ if(!dbus_message_iter_init(msg, &resp_args)) {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: response message is missing arguments\n");
+ dbus_message_unref(msg);
+ return false;
+ } else if (DBUS_TYPE_OBJECT_PATH == dbus_message_iter_get_arg_type(&resp_args)) {
+ const char *res = NULL;
+ dbus_message_iter_get_basic(&resp_args, &res);
+ } else if(DBUS_TYPE_UNIX_FD == dbus_message_iter_get_arg_type(&resp_args)) {
+ int fd = -1;
+ dbus_message_iter_get_basic(&resp_args, &fd);
+
+ if(resp_fd)
+ *resp_fd = fd;
+ } else if(DBUS_TYPE_STRING == dbus_message_iter_get_arg_type(&resp_args)) {
+ char *err = NULL;
+ dbus_message_iter_get_basic(&resp_args, &err);
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: failed with error: %s\n", err);
+
+ dbus_message_unref(msg);
+ return false;
+ // TODO: Check dbus_error_is_set?
+ } else {
+ fprintf(stderr, "gsr error: gsr_dbus_call_screencast_method: response message is not an object path or unix fd\n");
+ dbus_message_unref(msg);
+ return false;
+ // TODO: Check dbus_error_is_set?
+ }
+
+ dbus_message_unref(msg);
+ if(!response_msg)
+ return true;
+
+ /* TODO: Add timeout, but take into consideration user interactive signals (such as selecting a monitor to capture for ScreenCast) */
+ for (;;) {
+ const int timeout_milliseconds = 10;
+ dbus_connection_read_write(self->con, timeout_milliseconds);
+ *response_msg = dbus_connection_pop_message(self->con);
+
+ if(!*response_msg)
+ continue;
+
+ if(!dbus_message_is_signal(*response_msg, "org.freedesktop.portal.Request", "Response")) {
+ dbus_message_unref(*response_msg);
+ *response_msg = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ return true;
+}
+
+static int gsr_dbus_get_response_status(DBusMessageIter *resp_args) {
+ if(dbus_message_iter_get_arg_type(resp_args) != DBUS_TYPE_UINT32) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_response_status: missing uint32 in response\n");
+ return -1;
+ }
+
+ dbus_uint32_t response_status = 0;
+ dbus_message_iter_get_basic(resp_args, &response_status);
+
+ dbus_message_iter_next(resp_args);
+ return (int)response_status;
+}
+
+static dict_entry* find_dict_entry_by_key(dict_entry *entries, int num_entries, const char *key) {
+ for(int i = 0; i < num_entries; ++i) {
+ if(strcmp(entries[i].key, key) == 0)
+ return &entries[i];
+ }
+ return NULL;
+}
+
+static bool gsr_dbus_get_variant_value(DBusMessageIter *iter, dict_entry *entry) {
+ if(dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_VARIANT) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: value is not a variant\n");
+ return false;
+ }
+
+ DBusMessageIter variant_iter;
+ dbus_message_iter_recurse(iter, &variant_iter);
+
+ switch(dbus_message_iter_get_arg_type(&variant_iter)) {
+ case DBUS_TYPE_STRING: {
+ if(entry->value_type != DICT_TYPE_STRING) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: expected entry value to be a(n) %s was a string\n", dict_value_type_to_string(entry->value_type));
+ return false;
+ }
+
+ const char *value = NULL;
+ dbus_message_iter_get_basic(&variant_iter, &value);
+
+ if(!value) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: failed to get entry value as value\n");
+ return false;
+ }
+
+ if(entry->str) {
+ free(entry->str);
+ entry->str = NULL;
+ }
+
+ entry->str = strdup(value);
+ if(!entry->str) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: failed to copy value\n");
+ return false;
+ }
+ return true;
+ }
+ case DBUS_TYPE_UINT32: {
+ if(entry->value_type != DICT_TYPE_UINT32) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: expected entry value to be a(n) %s was an uint32\n", dict_value_type_to_string(entry->value_type));
+ return false;
+ }
+
+ dbus_message_iter_get_basic(&variant_iter, &entry->u32);
+ return true;
+ }
+ case DBUS_TYPE_BOOLEAN: {
+ if(entry->value_type != DICT_TYPE_BOOL) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: expected entry value to be a(n) %s was a boolean\n", dict_value_type_to_string(entry->value_type));
+ return false;
+ }
+
+ dbus_message_iter_get_basic(&variant_iter, &entry->boolean);
+ return true;
+ }
+ }
+
+ fprintf(stderr, "gsr error: gsr_dbus_get_variant_value: got unexpected type, expected string, uint32 or boolean\n");
+ return false;
+}
+
+/*
+ Parses a{sv} into matching key entries in |entries|.
+ If the entry value is a string then it's allocated with malloc and is null-terminated
+ and has to be free by the caller.
+ The entry values should be 0 before this method is called.
+ The entries are free'd if this function fails.
+*/
+static bool gsr_dbus_get_map(DBusMessageIter *resp_args, dict_entry *entries, int num_entries) {
+ if(dbus_message_iter_get_arg_type(resp_args) != DBUS_TYPE_ARRAY) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_map: missing array in response\n");
+ return false;
+ }
+
+ DBusMessageIter subiter;
+ dbus_message_iter_recurse(resp_args, &subiter);
+
+ while(dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_INVALID) {
+ DBusMessageIter dictiter = DBUS_MESSAGE_ITER_INIT_CLOSED;
+ const char *key = NULL;
+ dict_entry *entry = NULL;
+
+ // fprintf(stderr, " array element type: %c, %s\n",
+ // dbus_message_iter_get_arg_type(&subiter),
+ // dbus_message_iter_get_signature(&subiter));
+ if(dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_DICT_ENTRY) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_map: array value is not an entry\n");
+ return false;
+ }
+
+ dbus_message_iter_recurse(&subiter, &dictiter);
+
+ if(dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_STRING) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_map: entry key is not a string\n");
+ goto error;
+ }
+
+ dbus_message_iter_get_basic(&dictiter, &key);
+ if(!key) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_map: failed to get entry key as value\n");
+ goto error;
+ }
+
+ entry = find_dict_entry_by_key(entries, num_entries, key);
+ if(!entry) {
+ dbus_message_iter_next(&subiter);
+ continue;
+ }
+
+ if(!dbus_message_iter_next(&dictiter)) {
+ fprintf(stderr, "gsr error: gsr_dbus_get_map: missing entry value\n");
+ goto error;
+ }
+
+ if(!gsr_dbus_get_variant_value(&dictiter, entry))
+ goto error;
+
+ dbus_message_iter_next(&subiter);
+ }
+
+ return true;
+
+ error:
+ for(int i = 0; i < num_entries; ++i) {
+ if(entries[i].value_type == DICT_TYPE_STRING) {
+ free(entries[i].str);
+ entries[i].str = NULL;
+ }
+ }
+ return false;
+}
+
+int gsr_dbus_screencast_create_session(gsr_dbus *self, char **session_handle) {
+ assert(session_handle);
+ *session_handle = NULL;
+
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ char session_handle_token[64];
+ gsr_dbus_portal_get_unique_session_token(self, session_handle_token, sizeof(session_handle_token));
+
+ dict_entry args[2];
+ args[0].key = "handle_token";
+ args[0].value_type = DICT_TYPE_STRING;
+ args[0].str = handle_token;
+
+ args[1].key = "session_handle_token";
+ args[1].value_type = DICT_TYPE_STRING;
+ args[1].str = session_handle_token;
+
+ DBusMessage *response_msg = NULL;
+ if(!gsr_dbus_call_screencast_method(self, "CreateSession", NULL, NULL, args, 2, NULL, &response_msg)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_create_session: failed to setup ScreenCast session. Make sure you have a desktop portal running with support for the ScreenCast interface and that the desktop portal matches the Wayland compositor you are running.\n");
+ return -1;
+ }
+
+ // TODO: Verify signal path matches |res|, maybe check the below
+ // DBUS_TYPE_ARRAY value?
+ //fprintf(stderr, "signature: %s, sender: %s\n", dbus_message_get_signature(msg), dbus_message_get_sender(msg));
+ DBusMessageIter resp_args;
+ if(!dbus_message_iter_init(response_msg, &resp_args)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_create_session: missing response\n");
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+ const int response_status = gsr_dbus_get_response_status(&resp_args);
+ if(response_status != 0) {
+ dbus_message_unref(response_msg);
+ return response_status;
+ }
+
+ dict_entry entries[1];
+ entries[0].key = "session_handle";
+ entries[0].str = NULL;
+ entries[0].value_type = DICT_TYPE_STRING;
+ if(!gsr_dbus_get_map(&resp_args, entries, 1)) {
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+ if(!entries[0].str) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_create_session: missing \"session_handle\" in response\n");
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+ *session_handle = entries[0].str;
+ //fprintf(stderr, "session handle: |%s|\n", entries[0].str);
+ //free(entries[0].str);
+
+ dbus_message_unref(response_msg);
+ return 0;
+}
+
+static uint32_t unset_unsupported_capture_types(uint32_t requested_capture_types, uint32_t available_capture_types) {
+ if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_MONITOR))
+ requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_MONITOR;
+ if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_WINDOW))
+ requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_WINDOW;
+ if(!(available_capture_types & GSR_PORTAL_CAPTURE_TYPE_VIRTUAL))
+ requested_capture_types &= ~GSR_PORTAL_CAPTURE_TYPE_VIRTUAL;
+ return requested_capture_types;
+}
+
+static uint32_t unset_unsupported_cursor_modes(uint32_t requested_cursor_modes, uint32_t available_cursor_modes) {
+ if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_HIDDEN))
+ requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_HIDDEN;
+ if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_EMBEDDED))
+ requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_EMBEDDED;
+ if(!(available_cursor_modes & GSR_PORTAL_CURSOR_MODE_METADATA))
+ requested_cursor_modes &= ~GSR_PORTAL_CURSOR_MODE_METADATA;
+ return requested_cursor_modes;
+}
+
+int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode) {
+ assert(session_handle);
+
+ uint32_t available_source_types = 0;
+ gsr_dbus_desktop_portal_get_property(self, "org.freedesktop.portal.ScreenCast", "AvailableSourceTypes", &available_source_types);
+ if(available_source_types == 0)
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_select_sources: no source types are available\n");
+ capture_type = unset_unsupported_capture_types(capture_type, available_source_types);
+
+ uint32_t available_cursor_modes = 0;
+ gsr_dbus_desktop_portal_get_property(self, "org.freedesktop.portal.ScreenCast", "AvailableCursorModes", &available_cursor_modes);
+ if(available_cursor_modes == 0)
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_select_sources: no cursors modes are available\n");
+ cursor_mode = unset_unsupported_cursor_modes(cursor_mode, available_cursor_modes);
+
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ int num_arg_dict = 4;
+ dict_entry args[6];
+ args[0].key = "types";
+ args[0].value_type = DICT_TYPE_UINT32;
+ args[0].u32 = capture_type;
+
+ args[1].key = "multiple";
+ args[1].value_type = DICT_TYPE_BOOL;
+ args[1].boolean = false; /* TODO: Wayland ignores this and still gives the option to select multiple sources. Support that case.. */
+
+ args[2].key = "handle_token";
+ args[2].value_type = DICT_TYPE_STRING;
+ args[2].str = handle_token;
+
+ args[3].key = "cursor_mode";
+ args[3].value_type = DICT_TYPE_UINT32;
+ args[3].u32 = cursor_mode;
+
+ const int screencast_server_version = gsr_dbus_get_screencast_version_cached(self);
+ if(screencast_server_version >= 4) {
+ num_arg_dict = 5;
+ args[4].key = "persist_mode";
+ args[4].value_type = DICT_TYPE_UINT32;
+ args[4].u32 = 2; /* persist until explicitly revoked */
+
+ if(self->screencast_restore_token && self->screencast_restore_token[0]) {
+ num_arg_dict = 6;
+
+ args[5].key = "restore_token";
+ args[5].value_type = DICT_TYPE_STRING;
+ args[5].str = self->screencast_restore_token;
+ }
+ } else if(self->screencast_restore_token && self->screencast_restore_token[0]) {
+ fprintf(stderr, "gsr warning: gsr_dbus_screencast_select_sources: tried to use restore token but this option is only available in screencast version >= 4, your wayland compositors screencast version is %d\n", screencast_server_version);
+ }
+
+ DBusMessage *response_msg = NULL;
+ if(!gsr_dbus_call_screencast_method(self, "SelectSources", session_handle, NULL, args, num_arg_dict, NULL, &response_msg)) {
+ if(num_arg_dict == 6) {
+ /* We dont know what the error exactly is but assume it may be because of invalid restore token. In that case try without restore token */
+ fprintf(stderr, "gsr warning: gsr_dbus_screencast_select_sources: SelectSources failed, retrying without restore_token\n");
+ num_arg_dict = 5;
+ if(!gsr_dbus_call_screencast_method(self, "SelectSources", session_handle, NULL, args, num_arg_dict, NULL, &response_msg))
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+
+ // TODO: Verify signal path matches |res|, maybe check the below
+ //fprintf(stderr, "signature: %s, sender: %s\n", dbus_message_get_signature(msg), dbus_message_get_sender(msg));
+ DBusMessageIter resp_args;
+ if(!dbus_message_iter_init(response_msg, &resp_args)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_create_session: missing response\n");
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+
+ const int response_status = gsr_dbus_get_response_status(&resp_args);
+ if(response_status != 0) {
+ dbus_message_unref(response_msg);
+ return response_status;
+ }
+
+ dbus_message_unref(response_msg);
+ return 0;
+}
+
+static dbus_uint32_t screencast_stream_get_pipewire_node(DBusMessageIter *iter) {
+ DBusMessageIter subiter;
+ dbus_message_iter_recurse(iter, &subiter);
+
+ if(dbus_message_iter_get_arg_type(&subiter) == DBUS_TYPE_STRUCT) {
+ DBusMessageIter structiter;
+ dbus_message_iter_recurse(&subiter, &structiter);
+
+ if(dbus_message_iter_get_arg_type(&structiter) == DBUS_TYPE_UINT32) {
+ dbus_uint32_t data = 0;
+ dbus_message_iter_get_basic(&structiter, &data);
+ return data;
+ }
+ }
+
+ return 0;
+}
+
+int gsr_dbus_screencast_start(gsr_dbus *self, const char *session_handle, uint32_t *pipewire_node) {
+ assert(session_handle);
+ *pipewire_node = 0;
+
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ dict_entry args[1];
+ args[0].key = "handle_token";
+ args[0].value_type = DICT_TYPE_STRING;
+ args[0].str = handle_token;
+
+ DBusMessage *response_msg = NULL;
+ if(!gsr_dbus_call_screencast_method(self, "Start", session_handle, "", args, 1, NULL, &response_msg))
+ return -1;
+
+ // TODO: Verify signal path matches |res|, maybe check the below
+ //fprintf(stderr, "signature: %s, sender: %s\n", dbus_message_get_signature(msg), dbus_message_get_sender(msg));
+ DBusMessageIter resp_args;
+ if(!dbus_message_iter_init(response_msg, &resp_args)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: missing response\n");
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+ const int response_status = gsr_dbus_get_response_status(&resp_args);
+ if(response_status != 0) {
+ dbus_message_unref(response_msg);
+ return response_status;
+ }
+
+ if(dbus_message_iter_get_arg_type(&resp_args) != DBUS_TYPE_ARRAY) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: missing array in response\n");
+ dbus_message_unref(response_msg);
+ return -1;
+ }
+
+ DBusMessageIter subiter;
+ dbus_message_iter_recurse(&resp_args, &subiter);
+
+ while(dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_INVALID) {
+ DBusMessageIter dictiter = DBUS_MESSAGE_ITER_INIT_CLOSED;
+ const char *key = NULL;
+
+ // fprintf(stderr, " array element type: %c, %s\n",
+ // dbus_message_iter_get_arg_type(&subiter),
+ // dbus_message_iter_get_signature(&subiter));
+ if(dbus_message_iter_get_arg_type(&subiter) != DBUS_TYPE_DICT_ENTRY) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: array value is not an entry\n");
+ goto error;
+ }
+
+ dbus_message_iter_recurse(&subiter, &dictiter);
+
+ if(dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_STRING) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: entry key is not a string\n");
+ goto error;
+ }
+
+ dbus_message_iter_get_basic(&dictiter, &key);
+ if(!key) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: failed to get entry key as value\n");
+ goto error;
+ }
+
+ if(strcmp(key, "restore_token") == 0) {
+ if(!dbus_message_iter_next(&dictiter)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: missing restore_token value\n");
+ goto error;
+ }
+
+ if(dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_VARIANT) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: restore_token is not a variant\n");
+ goto error;
+ }
+
+ DBusMessageIter variant_iter;
+ dbus_message_iter_recurse(&dictiter, &variant_iter);
+
+ if(dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_STRING) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: restore_token is not a string\n");
+ goto error;
+ }
+
+ char *restore_token_str = NULL;
+ dbus_message_iter_get_basic(&variant_iter, &restore_token_str);
+
+ if(restore_token_str) {
+ if(self->screencast_restore_token) {
+ free(self->screencast_restore_token);
+ self->screencast_restore_token = NULL;
+ }
+ self->screencast_restore_token = strdup(restore_token_str);
+ //fprintf(stderr, "got restore token: %s\n", self->screencast_restore_token);
+ }
+ } else if(strcmp(key, "streams") == 0) {
+ if(!dbus_message_iter_next(&dictiter)) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: missing streams value\n");
+ goto error;
+ }
+
+ if(dbus_message_iter_get_arg_type(&dictiter) != DBUS_TYPE_VARIANT) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: streams value is not a variant\n");
+ goto error;
+ }
+
+ DBusMessageIter variant_iter;
+ dbus_message_iter_recurse(&dictiter, &variant_iter);
+
+ if(dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_ARRAY) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: streams value is not an array\n");
+ goto error;
+ }
+
+ int num_streams = dbus_message_iter_get_element_count(&variant_iter);
+ //fprintf(stderr, "num streams: %d\n", num_streams);
+ /* Skip over all streams except the last one, since kde can return multiple streams even if only 1 is requested. The last one is the valid one */
+ for(int i = 0; i < num_streams - 1; ++i) {
+ screencast_stream_get_pipewire_node(&variant_iter);
+ }
+
+ if(num_streams > 0) {
+ *pipewire_node = screencast_stream_get_pipewire_node(&variant_iter);
+ //fprintf(stderr, "pipewire node: %u\n", *pipewire_node);
+ }
+ }
+
+ dbus_message_iter_next(&subiter);
+ }
+
+ if(*pipewire_node == 0) {
+ fprintf(stderr, "gsr error: gsr_dbus_screencast_start: no pipewire node returned\n");
+ goto error;
+ }
+
+ dbus_message_unref(response_msg);
+ return 0;
+
+ error:
+ dbus_message_unref(response_msg);
+ return -1;
+}
+
+bool gsr_dbus_screencast_open_pipewire_remote(gsr_dbus *self, const char *session_handle, int *pipewire_fd) {
+ assert(session_handle);
+ *pipewire_fd = -1;
+ return gsr_dbus_call_screencast_method(self, "OpenPipeWireRemote", session_handle, NULL, NULL, 0, pipewire_fd, NULL);
+}
+
+const char* gsr_dbus_screencast_get_restore_token(gsr_dbus *self) {
+ return self->screencast_restore_token;
+}
diff --git a/src/main.cpp b/src/main.cpp
index 67619f9..f382dd0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,7 +5,7 @@ extern "C" {
#include "../include/capture/kms.h"
#ifdef GSR_PORTAL
#include "../include/capture/portal.h"
-#include "../dbus/client/dbus_client.h"
+#include "../include/dbus.h"
#endif
#ifdef GSR_APP_AUDIO
#include "../include/pipewire_audio.h"
@@ -1847,15 +1847,15 @@ static void list_supported_capture_options(const gsr_window *window, const char
if(!wayland)
return;
- gsr_dbus_client dbus_client;
- if(!gsr_dbus_client_init(&dbus_client, NULL))
+ gsr_dbus dbus;
+ if(!gsr_dbus_init(&dbus, NULL))
return;
- char session_handle[128];
- if(gsr_dbus_client_screencast_create_session(&dbus_client, session_handle, sizeof(session_handle)) == 0)
+ char *session_handle = NULL;
+ if(gsr_dbus_screencast_create_session(&dbus, &session_handle) == 0)
puts("portal");
- gsr_dbus_client_deinit(&dbus_client);
+ gsr_dbus_deinit(&dbus);
#endif
}
@@ -3312,7 +3312,7 @@ int main(int argc, char **argv) {
int damage_fps_counter = 0;
bool paused = false;
- double paused_time_offset = 0.0;
+ std::atomic<double> paused_time_offset(0.0);
double paused_time_start = 0.0;
bool replay_recording = false;
RecordingStartResult replay_recording_start_result;
@@ -3628,7 +3628,23 @@ int main(int argc, char **argv) {
// TODO: Dont do this if no damage?
egl.glClear(0);
+
+ bool capture_has_synchronous_task = false;
+ if(capture->capture_has_synchronous_task) {
+ capture_has_synchronous_task = capture->capture_has_synchronous_task(capture);
+ if(capture_has_synchronous_task) {
+ paused_time_start = clock_get_monotonic_seconds();
+ paused = true;
+ }
+ }
+
gsr_capture_capture(capture, &capture_metadata, &color_conversion);
+
+ if(capture_has_synchronous_task) {
+ paused_time_offset = paused_time_offset + (clock_get_monotonic_seconds() - paused_time_start);
+ paused = false;
+ }
+
gsr_egl_swap_buffers(&egl);
gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, &color_conversion);
@@ -3678,7 +3694,7 @@ int main(int argc, char **argv) {
paused_time_start = clock_get_monotonic_seconds();
fprintf(stderr, "Paused\n");
} else {
- paused_time_offset += (clock_get_monotonic_seconds() - paused_time_start);
+ paused_time_offset = paused_time_offset + (clock_get_monotonic_seconds() - paused_time_start);
fprintf(stderr, "Unpaused\n");
}
diff --git a/src/pipewire_video.c b/src/pipewire_video.c
index 83b0bc3..277004c 100644
--- a/src/pipewire_video.c
+++ b/src/pipewire_video.c
@@ -280,13 +280,21 @@ static void on_param_changed_cb(void *user_data, uint32_t id, const struct spa_p
self->negotiated = true;
}
-static void on_state_changed_cb(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *error) {
- (void)old;
+static void on_state_changed_cb(void *user_data, enum pw_stream_state prev_state, enum pw_stream_state new_state, const char *error) {
gsr_pipewire_video *self = user_data;
- fprintf(stderr, "gsr info: pipewire: stream %p state: \"%s\" (error: %s)\n",
- (void*)self->stream, pw_stream_state_as_string(state),
+ fprintf(stderr, "gsr info: pipewire: stream %p previous state: \"%s\", new state: \"%s\" (error: %s)\n",
+ (void*)self->stream, pw_stream_state_as_string(prev_state), pw_stream_state_as_string(new_state),
error ? error : "none");
+
+ pthread_mutex_lock(&self->mutex);
+ if(new_state == PW_STREAM_STATE_PAUSED) {
+ self->paused_start_secs = clock_get_monotonic_seconds();
+ self->paused = true;
+ } else {
+ self->paused = false;
+ }
+ pthread_mutex_unlock(&self->mutex);
}
static const struct pw_stream_events stream_events = {
@@ -841,6 +849,9 @@ bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map te
}
bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return false;
+
bool damaged = false;
pthread_mutex_lock(&self->mutex);
damaged = self->damaged;
@@ -849,7 +860,21 @@ bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self) {
}
void gsr_pipewire_video_clear_damage(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return;
+
pthread_mutex_lock(&self->mutex);
self->damaged = false;
pthread_mutex_unlock(&self->mutex);
}
+
+bool gsr_pipewire_video_should_restart(gsr_pipewire_video *self) {
+ if(!self->mutex_initialized)
+ return false;
+
+ bool should_restart = false;
+ pthread_mutex_lock(&self->mutex);
+ should_restart = self->paused && clock_get_monotonic_seconds() - self->paused_start_secs >= 3.0;
+ pthread_mutex_unlock(&self->mutex);
+ return should_restart;
+}