From b9537941c9eba041b7afc8b0f4256c73e35c2387 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 28 Jun 2025 04:25:20 +0200 Subject: Restart portal capture if it's paused for 3 seconds or more (can happen if returning from lock screen) Remove dbus server, no longer needed. Just run dbus code directly. --- dbus/client/dbus_client.c | 269 -------------- dbus/client/dbus_client.h | 36 -- dbus/dbus_impl.c | 913 ---------------------------------------------- dbus/dbus_impl.h | 37 -- dbus/portal.h | 17 - dbus/protocol.h | 86 ----- dbus/server/dbus_server.c | 175 --------- include/capture/capture.h | 5 +- include/dbus.h | 49 +++ include/pipewire_video.h | 4 + meson.build | 7 +- project.conf | 2 +- src/capture/portal.c | 95 +++-- src/dbus.c | 913 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.cpp | 32 +- src/pipewire_video.c | 33 +- 16 files changed, 1095 insertions(+), 1578 deletions(-) delete mode 100644 dbus/client/dbus_client.c delete mode 100644 dbus/client/dbus_client.h delete mode 100644 dbus/dbus_impl.c delete mode 100644 dbus/dbus_impl.h delete mode 100644 dbus/portal.h delete mode 100644 dbus/protocol.h delete mode 100644 dbus/server/dbus_server.c create mode 100644 include/dbus.h create mode 100644 src/dbus.c diff --git a/dbus/client/dbus_client.c b/dbus/client/dbus_client.c deleted file mode 100644 index de2df62..0000000 --- a/dbus/client/dbus_client.c +++ /dev/null @@ -1,269 +0,0 @@ -#include "dbus_client.h" -#include "../protocol.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -// TODO: Error checking for write/read - -static bool gsr_dbus_client_wait_for_startup(gsr_dbus_client *self) { - struct pollfd poll_fd = { - .fd = self->socket_pair[0], - .events = POLLIN, - .revents = 0 - }; - for(;;) { - int poll_res = poll(&poll_fd, 1, 100); - if(poll_res > 0 && (poll_fd.revents & POLLIN)) { - char msg; - read(self->socket_pair[0], &msg, 1); - return true; - } else { - int status = 0; - int wait_result = waitpid(self->pid, &status, WNOHANG); - if(wait_result != 0) { - int exit_code = -1; - if(WIFEXITED(status)) - exit_code = WEXITSTATUS(status); - fprintf(stderr, "gsr error: gsr_dbus_client_init: server died or never started, exit code: %d\n", exit_code); - self->pid = 0; - return false; - } - } - } -} - -bool gsr_dbus_client_init(gsr_dbus_client *self, const char *screencast_restore_token) { - memset(self, 0, sizeof(*self)); - - if(socketpair(AF_UNIX, SOCK_STREAM, 0, self->socket_pair) == -1) { - fprintf(stderr, "gsr error: gsr_dbus_client_init: socketpair failed, error: %s\n", strerror(errno)); - 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_client_init: failed to clone restore token\n"); - gsr_dbus_client_deinit(self); - return false; - } - } - - self->pid = fork(); - if(self->pid == -1) { - fprintf(stderr, "gsr error: gsr_dbus_client_init: failed to fork process\n"); - gsr_dbus_client_deinit(self); - return false; - } else if(self->pid == 0) { /* child */ - char socket_pair_server_str[32]; - snprintf(socket_pair_server_str, sizeof(socket_pair_server_str), "%d", self->socket_pair[1]); - - /* Needed for NixOS for example, to make sure gsr-dbus-server doesn't inherit cap_sys_nice */ - prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0); - - const char *args[] = { "gsr-dbus-server", socket_pair_server_str, self->screencast_restore_token ? self->screencast_restore_token : "", NULL }; - execvp(args[0], (char *const*)args); - - fprintf(stderr, "gsr error: gsr_dbus_client_init: failed to launch \"gsr-dbus-server\", error: %s\n", strerror(errno)); - _exit(127); - } else { /* parent */ - if(!gsr_dbus_client_wait_for_startup(self)) { - gsr_dbus_client_deinit(self); - return false; - } - } - - return true; -} - -void gsr_dbus_client_deinit(gsr_dbus_client *self) { - for(int i = 0; i < 2; ++i) { - if(self->socket_pair[i] > 0) { - close(self->socket_pair[i]); - self->socket_pair[i] = -1; - } - } - - if(self->screencast_restore_token) { - free(self->screencast_restore_token); - self->screencast_restore_token = NULL; - } - - if(self->pid > 0) { - kill(self->pid, SIGKILL); - int status = 0; - waitpid(self->pid, &status, 0); - self->pid = 0; - } -} - -int gsr_dbus_client_screencast_create_session(gsr_dbus_client *self, char *session_handle, size_t session_handle_size) { - const gsr_dbus_request_message request = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_REQ_CREATE_SESSION, - .create_session = (gsr_dbus_message_req_create_session) {} - }; - write(self->socket_pair[0], &request, sizeof(request)); - - gsr_dbus_response_message response = {0}; - read(self->socket_pair[0], &response, sizeof(response)); - - if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION); - return -1; - } - - if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code); - return response.error.error_code; - } - - if(response.type != GSR_DBUS_MESSAGE_RESP_CREATE_SESSION) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_create_session: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_CREATE_SESSION, response.type); - return -1; - } - - snprintf(session_handle, session_handle_size, "%s", response.create_session.session_handle); - return 0; -} - -int gsr_dbus_client_screencast_select_sources(gsr_dbus_client *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode) { - gsr_dbus_request_message request = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES, - .select_sources = (gsr_dbus_message_req_select_sources) { - .capture_type = capture_type, - .cursor_mode = cursor_mode - } - }; - snprintf(request.select_sources.session_handle, sizeof(request.select_sources.session_handle), "%s", session_handle); - write(self->socket_pair[0], &request, sizeof(request)); - - gsr_dbus_response_message response = {0}; - read(self->socket_pair[0], &response, sizeof(response)); - - if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION); - return -1; - } - - if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code); - return response.error.error_code; - } - - if(response.type != GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_select_sources: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES, response.type); - return -1; - } - - return 0; -} - -int gsr_dbus_client_screencast_start(gsr_dbus_client *self, const char *session_handle, uint32_t *pipewire_node) { - *pipewire_node = 0; - - gsr_dbus_request_message request = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_REQ_START, - .start = (gsr_dbus_message_req_start) {} - }; - snprintf(request.start.session_handle, sizeof(request.start.session_handle), "%s", session_handle); - write(self->socket_pair[0], &request, sizeof(request)); - - gsr_dbus_response_message response = {0}; - read(self->socket_pair[0], &response, sizeof(response)); - - if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION); - return -1; - } - - if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code); - return response.error.error_code; - } - - if(response.type != GSR_DBUS_MESSAGE_RESP_START) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_start: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_START, response.type); - return -1; - } - - if(self->screencast_restore_token) { - free(self->screencast_restore_token); - if(response.start.restore_token[0] == '\0') - self->screencast_restore_token = NULL; - else - self->screencast_restore_token = strdup(response.start.restore_token); - } - - *pipewire_node = response.start.pipewire_node; - return 0; -} - -bool gsr_dbus_client_screencast_open_pipewire_remote(gsr_dbus_client *self, const char *session_handle, int *pipewire_fd) { - *pipewire_fd = 0; - - gsr_dbus_request_message request = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE, - .open_pipewire_remote = (gsr_dbus_message_req_open_pipewire_remote) {} - }; - snprintf(request.open_pipewire_remote.session_handle, sizeof(request.open_pipewire_remote.session_handle), "%s", session_handle); - write(self->socket_pair[0], &request, sizeof(request)); - - gsr_dbus_response_message response = {0}; - struct iovec iov = { - .iov_base = &response, - .iov_len = sizeof(response) - }; - - char msg_control[CMSG_SPACE(sizeof(int))]; - - struct msghdr message = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = msg_control, - .msg_controllen = sizeof(msg_control) - }; - - const int bla = recvmsg(self->socket_pair[0], &message, MSG_WAITALL); - (void)bla; - - if(response.protocol_version != GSR_DBUS_PROTOCOL_VERSION) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: server uses protocol version %d while the client is using protocol version %d", response.protocol_version, GSR_DBUS_PROTOCOL_VERSION); - return false; - } - - if(response.type == GSR_DBUS_MESSAGE_RESP_ERROR) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: server return error: %s (%d)\n", response.error.message, (int)response.error.error_code); - return false; - } - - if(response.type != GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: received incorrect response type. Expected %d got %d\n", GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE, response.type); - return false; - } - - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); - if(!cmsg || cmsg->cmsg_type != SCM_RIGHTS) { - fprintf(stderr, "gsr error: gsr_dbus_client_screencast_open_pipewire_remote: returned message data is missing file descriptor\n"); - return false; - } - - memcpy(pipewire_fd, CMSG_DATA(cmsg), sizeof(*pipewire_fd)); - return true; -} - -const char* gsr_dbus_client_screencast_get_restore_token(gsr_dbus_client *self) { - return self->screencast_restore_token; -} diff --git a/dbus/client/dbus_client.h b/dbus/client/dbus_client.h deleted file mode 100644 index 98a1ecf..0000000 --- a/dbus/client/dbus_client.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef GSR_DBUS_CLIENT_H -#define GSR_DBUS_CLIENT_H - -/* - Using a client-server architecture is needed for dbus because cap_sys_nice breaks desktop portal. - The main binary has cap_sys_nice and we launch a new child-process without it which uses uses desktop portal. -*/ - -#include "../portal.h" -#include -#include -#include - -typedef struct { - int socket_pair[2]; - char *screencast_restore_token; - pid_t pid; -} gsr_dbus_client; - -/* Blocking. TODO: Make non-blocking */ -bool gsr_dbus_client_init(gsr_dbus_client *self, const char *screencast_restore_token); -void gsr_dbus_client_deinit(gsr_dbus_client *self); - -/* The follow functions should be called in order to setup ScreenCast properly */ -/* These functions that return an int return the response status code */ -int gsr_dbus_client_screencast_create_session(gsr_dbus_client *self, char *session_handle, size_t session_handle_size); -/* - |capture_type| is a bitmask of gsr_portal_capture_type values. gsr_portal_capture_type values that are not supported by the desktop portal will be ignored. - |gsr_portal_cursor_mode| is a bitmask of gsr_portal_cursor_mode values. gsr_portal_cursor_mode values that are not supported will be ignored. -*/ -int gsr_dbus_client_screencast_select_sources(gsr_dbus_client *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode); -int gsr_dbus_client_screencast_start(gsr_dbus_client *self, const char *session_handle, uint32_t *pipewire_node); -bool gsr_dbus_client_screencast_open_pipewire_remote(gsr_dbus_client *self, const char *session_handle, int *pipewire_fd); -const char* gsr_dbus_client_screencast_get_restore_token(gsr_dbus_client *self); - -#endif /* GSR_DBUS_CLIENT_H */ diff --git a/dbus/dbus_impl.c b/dbus/dbus_impl.c deleted file mode 100644 index 600fcc5..0000000 --- a/dbus/dbus_impl.c +++ /dev/null @@ -1,913 +0,0 @@ -#include "dbus_impl.h" - -#include - -#include -#include -#include -#include -#include - -/* 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/dbus/dbus_impl.h b/dbus/dbus_impl.h deleted file mode 100644 index c3f0751..0000000 --- a/dbus/dbus_impl.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef GSR_DBUS_H -#define GSR_DBUS_H - -#include "portal.h" -#include -#include -#include - -#define DBUS_RANDOM_STR_SIZE 16 - -typedef struct { - DBusConnection *con; - DBusError err; - char random_str[DBUS_RANDOM_STR_SIZE + 1]; - unsigned int handle_counter; - bool desktop_portal_rule_added; - uint32_t screencast_version; - char *screencast_restore_token; -} gsr_dbus; - -/* Blocking. TODO: Make non-blocking */ -bool gsr_dbus_init(gsr_dbus *self, const char *screencast_restore_token); -void gsr_dbus_deinit(gsr_dbus *self); - -/* The follow functions should be called in order to setup ScreenCast properly */ -/* These functions that return an int return the response status code */ -int gsr_dbus_screencast_create_session(gsr_dbus *self, char **session_handle); -/* - |capture_type| is a bitmask of gsr_portal_capture_type values. gsr_portal_capture_type values that are not supported by the desktop portal will be ignored. - |gsr_portal_cursor_mode| is a bitmask of gsr_portal_cursor_mode values. gsr_portal_cursor_mode values that are not supported will be ignored. -*/ -int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode); -int gsr_dbus_screencast_start(gsr_dbus *self, const char *session_handle, uint32_t *pipewire_node); -bool gsr_dbus_screencast_open_pipewire_remote(gsr_dbus *self, const char *session_handle, int *pipewire_fd); -const char* gsr_dbus_screencast_get_restore_token(gsr_dbus *self); - -#endif /* GSR_DBUS_H */ diff --git a/dbus/portal.h b/dbus/portal.h deleted file mode 100644 index 6b93aa6..0000000 --- a/dbus/portal.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef GSR_PORTAL_H -#define GSR_PORTAL_H - -typedef enum { - GSR_PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0, - GSR_PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1, - GSR_PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2, - GSR_PORTAL_CAPTURE_TYPE_ALL = GSR_PORTAL_CAPTURE_TYPE_MONITOR | GSR_PORTAL_CAPTURE_TYPE_WINDOW | GSR_PORTAL_CAPTURE_TYPE_VIRTUAL -} gsr_portal_capture_type; - -typedef enum { - GSR_PORTAL_CURSOR_MODE_HIDDEN = 1 << 0, - GSR_PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1, - GSR_PORTAL_CURSOR_MODE_METADATA = 1 << 2 -} gsr_portal_cursor_mode; - -#endif /* GSR_PORTAL_H */ diff --git a/dbus/protocol.h b/dbus/protocol.h deleted file mode 100644 index 212358d..0000000 --- a/dbus/protocol.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef GSR_DBUS_PROTOCOL_H -#define GSR_DBUS_PROTOCOL_H - -#include - -#define GSR_DBUS_PROTOCOL_VERSION 1 - -typedef enum { - GSR_DBUS_MESSAGE_REQ_CREATE_SESSION, - GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES, - GSR_DBUS_MESSAGE_REQ_START, - GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE -} gsr_dbus_message_req_type; - -typedef struct { - -} gsr_dbus_message_req_create_session; - -typedef struct { - char session_handle[128]; - uint32_t capture_type; - uint32_t cursor_mode; -} gsr_dbus_message_req_select_sources; - -typedef struct { - char session_handle[128]; -} gsr_dbus_message_req_start; - -typedef struct { - char session_handle[128]; -} gsr_dbus_message_req_open_pipewire_remote; - -typedef struct { - uint8_t protocol_version; - gsr_dbus_message_req_type type; - union { - gsr_dbus_message_req_create_session create_session; - gsr_dbus_message_req_select_sources select_sources; - gsr_dbus_message_req_start start; - gsr_dbus_message_req_open_pipewire_remote open_pipewire_remote; - }; -} gsr_dbus_request_message; - -typedef enum { - GSR_DBUS_MESSAGE_RESP_ERROR, - GSR_DBUS_MESSAGE_RESP_CREATE_SESSION, - GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES, - GSR_DBUS_MESSAGE_RESP_START, - GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE -} gsr_dbus_message_resp_type; - -typedef struct { - uint32_t error_code; - char message[128]; -} gsr_dbus_message_resp_error; - -typedef struct { - char session_handle[128]; -} gsr_dbus_message_resp_create_session; - -typedef struct { - -} gsr_dbus_message_resp_select_sources; - -typedef struct { - char restore_token[128]; - uint32_t pipewire_node; -} gsr_dbus_message_resp_start; - -typedef struct { - -} gsr_dbus_message_resp_open_pipewire_remote; - -typedef struct { - uint8_t protocol_version; - gsr_dbus_message_resp_type type; - union { - gsr_dbus_message_resp_error error; - gsr_dbus_message_resp_create_session create_session; - gsr_dbus_message_resp_select_sources select_sources; - gsr_dbus_message_resp_start start; - gsr_dbus_message_resp_open_pipewire_remote open_pipewire_remote; - }; -} gsr_dbus_response_message; - -#endif /* GSR_DBUS_PROTOCOL_H */ diff --git a/dbus/server/dbus_server.c b/dbus/server/dbus_server.c deleted file mode 100644 index bde6acb..0000000 --- a/dbus/server/dbus_server.c +++ /dev/null @@ -1,175 +0,0 @@ -#include "../dbus_impl.h" -#include "../protocol.h" - -#include -#include - -#include -#include - -/* TODO: Error check write/read */ - -static int handle_create_session(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_create_session *create_session) { - (void)create_session; - char *session_handle = NULL; - const int status = gsr_dbus_screencast_create_session(dbus, &session_handle); - if(status == 0) { - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_CREATE_SESSION, - .create_session = (gsr_dbus_message_resp_create_session) {} - }; - snprintf(response.create_session.session_handle, sizeof(response.create_session.session_handle), "%s", session_handle); - free(session_handle); - write(rpc_fd, &response, sizeof(response)); - } - return status; -} - -static int handle_select_sources(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_select_sources *select_sources) { - const int status = gsr_dbus_screencast_select_sources(dbus, select_sources->session_handle, select_sources->capture_type, select_sources->cursor_mode); - if(status == 0) { - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_SELECT_SOURCES, - .select_sources = (gsr_dbus_message_resp_select_sources) {} - }; - write(rpc_fd, &response, sizeof(response)); - } - return status; -} - -static int handle_start(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_start *start) { - uint32_t pipewire_node = 0; - const int status = gsr_dbus_screencast_start(dbus, start->session_handle, &pipewire_node); - if(status == 0) { - const char *screencast_restore_token = gsr_dbus_screencast_get_restore_token(dbus); - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_START, - .start = (gsr_dbus_message_resp_start) { - .pipewire_node = pipewire_node - } - }; - snprintf(response.start.restore_token, sizeof(response.start.restore_token), "%s", screencast_restore_token ? screencast_restore_token : ""); - write(rpc_fd, &response, sizeof(response)); - } - return status; -} - -static bool handle_open_pipewire_remote(gsr_dbus *dbus, int rpc_fd, const gsr_dbus_message_req_open_pipewire_remote *open_pipewire_remote) { - int pipewire_fd = 0; - const bool success = gsr_dbus_screencast_open_pipewire_remote(dbus, open_pipewire_remote->session_handle, &pipewire_fd); - if(success) { - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_OPEN_PIPEWIRE_REMOTE, - .open_pipewire_remote = (gsr_dbus_message_resp_open_pipewire_remote) {} - }; - - struct iovec iov = { - .iov_base = &response, - .iov_len = sizeof(response) - }; - - char msg_control[CMSG_SPACE(sizeof(int))]; - - struct msghdr message = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = msg_control, - .msg_controllen = sizeof(msg_control) - }; - - struct cmsghdr *cmsg = CMSG_FIRSTHDR(&message); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - int *fds = (int*)CMSG_DATA(cmsg); - fds[0] = pipewire_fd; - message.msg_controllen = cmsg->cmsg_len; - sendmsg(rpc_fd, &message, 0); - } - return success; -} - -int main(int argc, char **argv) { - if(argc != 3) { - fprintf(stderr, "usage: gsr-dbus-server \n"); - return 1; - } - - const char *rpc_fd_str = argv[1]; - const char *screencast_restore_token = argv[2]; - - int rpc_fd = -1; - if(sscanf(rpc_fd_str, "%d", &rpc_fd) != 1) { - fprintf(stderr, "gsr-dbus-server error: rpc-fd is not a number: %s\n", rpc_fd_str); - return 1; - } - - if(screencast_restore_token[0] == '\0') - screencast_restore_token = NULL; - - gsr_dbus dbus; - if(!gsr_dbus_init(&dbus, screencast_restore_token)) - return 1; - - /* Tell client we have started up */ - write(rpc_fd, "S", 1); - - gsr_dbus_request_message request; - for(;;) { - read(rpc_fd, &request, sizeof(request)); - - if(request.protocol_version != GSR_DBUS_PROTOCOL_VERSION) { - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_ERROR, - .error = (gsr_dbus_message_resp_error) { - .error_code = 1 - } - }; - snprintf(response.error.message, sizeof(response.error.message), "Client uses protocol version %d while the server is using protocol version %d", request.protocol_version, GSR_DBUS_PROTOCOL_VERSION); - fprintf(stderr, "gsr-dbus-server error: %s\n", response.error.message); - write(rpc_fd, &response, sizeof(response)); - continue; - } - - int status = 0; - switch(request.type) { - case GSR_DBUS_MESSAGE_REQ_CREATE_SESSION: { - status = handle_create_session(&dbus, rpc_fd, &request.create_session); - break; - } - case GSR_DBUS_MESSAGE_REQ_SELECT_SOURCES: { - status = handle_select_sources(&dbus, rpc_fd, &request.select_sources); - break; - } - case GSR_DBUS_MESSAGE_REQ_START: { - status = handle_start(&dbus, rpc_fd, &request.start); - break; - } - case GSR_DBUS_MESSAGE_REQ_OPEN_PIPEWIRE_REMOTE: { - if(!handle_open_pipewire_remote(&dbus, rpc_fd, &request.open_pipewire_remote)) - status = -1; - break; - } - } - - if(status != 0) { - gsr_dbus_response_message response = { - .protocol_version = GSR_DBUS_PROTOCOL_VERSION, - .type = GSR_DBUS_MESSAGE_RESP_ERROR, - .error = (gsr_dbus_message_resp_error) { - .error_code = status - } - }; - snprintf(response.error.message, sizeof(response.error.message), "%s", "Failed to handle request"); - write(rpc_fd, &response, sizeof(response)); - } - } - - gsr_dbus_deinit(&dbus); - return 0; -} \ No newline at end of file diff --git a/include/capture/capture.h b/include/capture/capture.h index 634eee0..c2128c5 100644 --- a/include/capture/capture.h +++ b/include/capture/capture.h @@ -22,12 +22,13 @@ typedef struct { } gsr_capture_metadata; struct gsr_capture { - /* These methods should not be called manually. Call gsr_capture_* instead */ + /* These methods should not be called manually. Call gsr_capture_* instead. |capture_metdata->width| and |capture_metadata->height| should be set by this function */ int (*start)(gsr_capture *cap, gsr_capture_metadata *capture_metadata); void (*on_event)(gsr_capture *cap, gsr_egl *egl); /* can be NULL */ void (*tick)(gsr_capture *cap); /* can be NULL. If there is an event then |on_event| is called before this */ bool (*should_stop)(gsr_capture *cap, bool *err); /* can be NULL. If NULL, return false */ - int (*capture)(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion); + bool (*capture_has_synchronous_task)(gsr_capture *cap); /* can be NULL. If this returns true then the time spent in |capture| is ignored for video/audio (capture is paused while the synchronous task happens) */ + int (*capture)(gsr_capture *cap, gsr_capture_metadata *capture_metadata, gsr_color_conversion *color_conversion); /* Return 0 if the frame was captured */ bool (*uses_external_image)(gsr_capture *cap); /* can be NULL. If NULL, return false */ bool (*set_hdr_metadata)(gsr_capture *cap, AVMasteringDisplayMetadata *mastering_display_metadata, AVContentLightMetadata *light_metadata); /* can be NULL. If NULL, return false */ uint64_t (*get_window_id)(gsr_capture *cap); /* can be NULL. Returns 0 if unknown */ diff --git a/include/dbus.h b/include/dbus.h new file mode 100644 index 0000000..229f7ea --- /dev/null +++ b/include/dbus.h @@ -0,0 +1,49 @@ +#ifndef GSR_DBUS_H +#define GSR_DBUS_H + +#include +#include +#include + +#define DBUS_RANDOM_STR_SIZE 16 + +typedef enum { + GSR_PORTAL_CAPTURE_TYPE_MONITOR = 1 << 0, + GSR_PORTAL_CAPTURE_TYPE_WINDOW = 1 << 1, + GSR_PORTAL_CAPTURE_TYPE_VIRTUAL = 1 << 2, + GSR_PORTAL_CAPTURE_TYPE_ALL = GSR_PORTAL_CAPTURE_TYPE_MONITOR | GSR_PORTAL_CAPTURE_TYPE_WINDOW | GSR_PORTAL_CAPTURE_TYPE_VIRTUAL +} gsr_portal_capture_type; + +typedef enum { + GSR_PORTAL_CURSOR_MODE_HIDDEN = 1 << 0, + GSR_PORTAL_CURSOR_MODE_EMBEDDED = 1 << 1, + GSR_PORTAL_CURSOR_MODE_METADATA = 1 << 2 +} gsr_portal_cursor_mode; + +typedef struct { + DBusConnection *con; + DBusError err; + char random_str[DBUS_RANDOM_STR_SIZE + 1]; + unsigned int handle_counter; + bool desktop_portal_rule_added; + uint32_t screencast_version; + char *screencast_restore_token; +} gsr_dbus; + +/* Blocking. TODO: Make non-blocking */ +bool gsr_dbus_init(gsr_dbus *self, const char *screencast_restore_token); +void gsr_dbus_deinit(gsr_dbus *self); + +/* The follow functions should be called in order to setup ScreenCast properly */ +/* These functions that return an int return the response status code */ +int gsr_dbus_screencast_create_session(gsr_dbus *self, char **session_handle); +/* + |capture_type| is a bitmask of gsr_portal_capture_type values. gsr_portal_capture_type values that are not supported by the desktop portal will be ignored. + |gsr_portal_cursor_mode| is a bitmask of gsr_portal_cursor_mode values. gsr_portal_cursor_mode values that are not supported will be ignored. +*/ +int gsr_dbus_screencast_select_sources(gsr_dbus *self, const char *session_handle, uint32_t capture_type, uint32_t cursor_mode); +int gsr_dbus_screencast_start(gsr_dbus *self, const char *session_handle, uint32_t *pipewire_node); +bool gsr_dbus_screencast_open_pipewire_remote(gsr_dbus *self, const char *session_handle, int *pipewire_fd); +const char* gsr_dbus_screencast_get_restore_token(gsr_dbus *self); + +#endif /* GSR_DBUS_H */ diff --git a/include/pipewire_video.h b/include/pipewire_video.h index 785f56f..d98e43d 100644 --- a/include/pipewire_video.h +++ b/include/pipewire_video.h @@ -95,6 +95,9 @@ typedef struct { uint64_t modifiers[GSR_PIPEWIRE_VIDEO_MAX_MODIFIERS]; size_t num_modifiers; + + bool paused; + double paused_start_secs; } gsr_pipewire_video; /* @@ -109,5 +112,6 @@ void gsr_pipewire_video_deinit(gsr_pipewire_video *self); bool gsr_pipewire_video_map_texture(gsr_pipewire_video *self, gsr_texture_map texture_map, gsr_pipewire_video_region *region, gsr_pipewire_video_region *cursor_region, gsr_pipewire_video_dmabuf_data *dmabuf_data, int *num_dmabuf_data, uint32_t *fourcc, uint64_t *modifiers, bool *using_external_image); bool gsr_pipewire_video_is_damaged(gsr_pipewire_video *self); void gsr_pipewire_video_clear_damage(gsr_pipewire_video *self); +bool gsr_pipewire_video_should_restart(gsr_pipewire_video *self); #endif /* GSR_PIPEWIRE_VIDEO_H */ diff --git a/meson.build b/meson.build index 43c429d..70f624b 100644 --- a/meson.build +++ b/meson.build @@ -76,7 +76,7 @@ uses_pipewire = false if get_option('portal') == true src += [ 'src/capture/portal.c', - 'dbus/client/dbus_client.c', + 'src/dbus.c', 'src/pipewire_video.c', ] add_project_arguments('-DGSR_PORTAL', language : ['c', 'cpp']) @@ -95,6 +95,7 @@ if uses_pipewire == true dep += [ dependency('libpipewire-0.3'), dependency('libspa-0.2'), + dependency('dbus-1'), ] endif @@ -103,10 +104,6 @@ add_project_arguments('-DGSR_VERSION="' + meson.project_version() + '"', languag executable('gsr-kms-server', 'kms/server/kms_server.c', dependencies : dependency('libdrm'), c_args : '-fstack-protector-all', install : true) executable('gpu-screen-recorder', src, dependencies : dep, install : true) -if get_option('portal') == true - executable('gsr-dbus-server', ['dbus/server/dbus_server.c', 'dbus/dbus_impl.c'], dependencies : dependency('dbus-1'), install : true) -endif - if get_option('systemd') == true install_data(files('extra/gpu-screen-recorder.service'), install_dir : 'lib/systemd/user') endif diff --git a/project.conf b/project.conf index 7cf013b..0c235d4 100644 --- a/project.conf +++ b/project.conf @@ -5,7 +5,7 @@ version = "5.5.10" platforms = ["posix"] [config] -ignore_dirs = ["kms/server", "build", "debug-build", "dbus/server"] +ignore_dirs = ["kms/server", "build", "debug-build"] #error_on_warning = "true" [define] 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 @@ -11,13 +11,22 @@ #include #include +#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 + +#include +#include +#include +#include +#include + +/* 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 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; +} -- cgit v1.2.3-70-g09d2