From e3225bc62835c6accc18787c6038fc1dce2484fc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 10 May 2025 17:10:59 +0200 Subject: Move dbus code to a separate process to allow gpu-screen-recorder to use cap_sys_nice for better recording performance on amd --- dbus/client/dbus_client.c | 265 +++++++++++++ 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 +++++++++ extra/meson_post_install.sh | 11 +- include/dbus.h | 49 --- kms/client/kms_client.c | 1 + kms/server/kms_server.c | 3 + meson.build | 7 +- meson_options.txt | 2 +- src/capture/portal.c | 27 +- src/dbus.c | 893 ------------------------------------------- src/egl.c | 2 +- src/main.cpp | 15 +- 17 files changed, 1559 insertions(+), 980 deletions(-) create mode 100644 dbus/client/dbus_client.c create mode 100644 dbus/client/dbus_client.h create mode 100644 dbus/dbus_impl.c create mode 100644 dbus/dbus_impl.h create mode 100644 dbus/portal.h create mode 100644 dbus/protocol.h create mode 100644 dbus/server/dbus_server.c delete mode 100644 include/dbus.h delete mode 100644 src/dbus.c diff --git a/dbus/client/dbus_client.c b/dbus/client/dbus_client.c new file mode 100644 index 0000000..2fc9511 --- /dev/null +++ b/dbus/client/dbus_client.c @@ -0,0 +1,265 @@ +#include "dbus_client.h" +#include "../protocol.h" + +#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 side 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]); + + 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: execvp failed, 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 new file mode 100644 index 0000000..c9e8182 --- /dev/null +++ b/dbus/client/dbus_client.h @@ -0,0 +1,36 @@ +#ifndef GSR_DBUS_CLIENT_H +#define GSR_DBUS_CLIENT_H + +/* + Using a client-server architecture is needed for dbus because cap_sys_nice doesn't work with 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 new file mode 100644 index 0000000..600fcc5 --- /dev/null +++ b/dbus/dbus_impl.c @@ -0,0 +1,913 @@ +#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 new file mode 100644 index 0000000..c3f0751 --- /dev/null +++ b/dbus/dbus_impl.h @@ -0,0 +1,37 @@ +#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 new file mode 100644 index 0000000..6b93aa6 --- /dev/null +++ b/dbus/portal.h @@ -0,0 +1,17 @@ +#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 new file mode 100644 index 0000000..212358d --- /dev/null +++ b/dbus/protocol.h @@ -0,0 +1,86 @@ +#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 new file mode 100644 index 0000000..bde6acb --- /dev/null +++ b/dbus/server/dbus_server.c @@ -0,0 +1,175 @@ +#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/extra/meson_post_install.sh b/extra/meson_post_install.sh index 7bf0d96..8a01b18 100755 --- a/extra/meson_post_install.sh +++ b/extra/meson_post_install.sh @@ -4,14 +4,7 @@ /usr/sbin/setcap cap_sys_admin+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gsr-kms-server \ || echo "\n!!! Please re-run install as root\n" -# Cant do this because it breaks desktop portal (create session)!!!. -# For some reason the desktop portal tries to access /proc/gpu-screen-recorder-pid/root from the portal process -# which doesn't work because for some reason CAP_SYS_NICE on a program makes /proc/self/root not readable by other processes. -# The reason portal reads that file might be because portal seems to have a security feature where its able to identify the -# process and if the session token is stolen by another application then it will ignore the session token as it wasn't that -# application that created the session token. -# --- # This is needed (for EGL_CONTEXT_PRIORITY_HIGH_IMG) to allow gpu screen recorder to run faster than the heaviest application on AMD. # For example when trying to record a game at 60 fps and the game drops to 45 fps in some place that would also make gpu screen recorder -# drop to 45 fps unless this setcap is used. -#/usr/sbin/setcap cap_sys_nice+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gpu-screen-recorder +# drop to 45 fps unless this setcap is used. Recording would also drop to below 60 fps in some games even though they run above 60 fps. +/usr/sbin/setcap cap_sys_nice+ep ${MESON_INSTALL_DESTDIR_PREFIX}/bin/gpu-screen-recorder diff --git a/include/dbus.h b/include/dbus.h deleted file mode 100644 index 58edf3c..0000000 --- a/include/dbus.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef GSR_DBUS_H -#define GSR_DBUS_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; - -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; - -/* 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/kms/client/kms_client.c b/kms/client/kms_client.c index 018b25e..8335688 100644 --- a/kms/client/kms_client.c +++ b/kms/client/kms_client.c @@ -387,6 +387,7 @@ void cleanup_socket(gsr_kms_client *self, bool kill_server) { if(kill_server && self->kms_server_pid > 0) { kill(self->kms_server_pid, SIGKILL); + // TODO: //int status; //waitpid(self->kms_server_pid, &status, 0); self->kms_server_pid = -1; diff --git a/kms/server/kms_server.c b/kms/server/kms_server.c index c585f24..070875b 100644 --- a/kms/server/kms_server.c +++ b/kms/server/kms_server.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -434,6 +435,8 @@ static double clock_get_monotonic_seconds(void) { } int main(int argc, char **argv) { + setlocale(LC_ALL, "C"); // Sigh... stupid C + int res = 0; int socket_fd = 0; gsr_drm drm; diff --git a/meson.build b/meson.build index 35c4984..e2945fa 100644 --- a/meson.build +++ b/meson.build @@ -76,10 +76,9 @@ uses_pipewire = false if get_option('portal') == true src += [ 'src/capture/portal.c', - 'src/dbus.c', + 'dbus/client/dbus_client.c', 'src/pipewire_video.c', ] - dep += dependency('dbus-1') add_project_arguments('-DGSR_PORTAL', language : ['c', 'cpp']) uses_pipewire = true endif @@ -104,6 +103,10 @@ 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/meson_options.txt b/meson_options.txt index b1023c2..4adf715 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,5 +1,5 @@ option('systemd', type : 'boolean', value : true, description : 'Install systemd service file') -option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capability on gsr-kms-server binary to remove password prompt when recording monitor (without desktop portal option) on amd/intel or nvidia wayland') +option('capabilities', type : 'boolean', value : true, description : 'Set binary admin capability on gsr-kms-server binary to remove password prompt when recording monitor (without desktop portal option) on amd/intel or nvidia wayland. Also sets nice capability on gpu-screen-recorder to allow it to run as a high priority graphics process for better performance') option('nvidia_suspend_fix', type : 'boolean', value : true, description : 'Install nvidia modprobe config file to tell nvidia driver to preserve video memory on suspend. This is a workaround for an nvidia driver bug that breaks cuda (and gpu screen recorder) on suspend') option('portal', type : 'boolean', value : true, description : 'Build with support for xdg desktop portal ScreenCast capture (wayland only) (-w portal option). Requires pipewire') option('app_audio', type : 'boolean', value : true, description : 'Build with support for recording a single audio source (-a app: option). Requires pipewire') diff --git a/src/capture/portal.c b/src/capture/portal.c index 27f514f..a5e62af 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 "../../include/dbus.h" +#include "../../dbus/client/dbus_client.h" #include "../../include/pipewire_video.h" #include @@ -16,8 +16,8 @@ typedef struct { gsr_texture_map texture_map; - gsr_dbus dbus; - char *session_handle; + gsr_dbus_client dbus_client; + char session_handle[128]; gsr_pipewire_video pipewire; vec2i capture_size; @@ -52,15 +52,8 @@ static void gsr_capture_portal_stop(gsr_capture_portal *self) { } gsr_capture_portal_cleanup_plane_fds(self); - gsr_pipewire_video_deinit(&self->pipewire); - - if(self->session_handle) { - free(self->session_handle); - self->session_handle = NULL; - } - - gsr_dbus_deinit(&self->dbus); + gsr_dbus_client_deinit(&self->dbus_client); } static void gsr_capture_portal_create_input_textures(gsr_capture_portal *self) { @@ -195,36 +188,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_init(&self->dbus, restore_token)) + if(!gsr_dbus_client_init(&self->dbus_client, restore_token)) return -1; fprintf(stderr, "gsr info: gsr_capture_portal_setup_dbus: CreateSession\n"); - response_status = gsr_dbus_screencast_create_session(&self->dbus, &self->session_handle); + response_status = gsr_dbus_client_screencast_create_session(&self->dbus_client, self->session_handle, sizeof(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_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); + 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); 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_screencast_start(&self->dbus, self->session_handle, pipewire_node); + response_status = gsr_dbus_client_screencast_start(&self->dbus_client, 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_screencast_get_restore_token(&self->dbus); + const char *screencast_restore_token = gsr_dbus_client_screencast_get_restore_token(&self->dbus_client); 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_screencast_open_pipewire_remote(&self->dbus, self->session_handle, pipewire_fd)) { + if(!gsr_dbus_client_screencast_open_pipewire_remote(&self->dbus_client, self->session_handle, pipewire_fd)) { fprintf(stderr, "gsr error: gsr_capture_portal_setup_dbus: OpenPipeWireRemote failed\n"); return -1; } diff --git a/src/dbus.c b/src/dbus.c deleted file mode 100644 index 2ccdfb0..0000000 --- a/src/dbus.c +++ /dev/null @@ -1,893 +0,0 @@ -#include "../include/dbus.h" -#include "../include/utils.h" - -#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 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/egl.c b/src/egl.c index 76ab9ec..8e38589 100644 --- a/src/egl.c +++ b/src/egl.c @@ -60,7 +60,7 @@ static bool gsr_egl_create_window(gsr_egl *self) { const int32_t ctxattr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, - //EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, /* requires cap_sys_nice, ignored otherwise */ + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, /* requires cap_sys_nice, ignored otherwise */ EGL_NONE, EGL_NONE }; diff --git a/src/main.cpp b/src/main.cpp index 4a4fc0c..d716fcd 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 "../include/dbus.h" +#include "../dbus/client/dbus_client.h" #endif #ifdef GSR_APP_AUDIO #include "../include/pipewire_audio.h" @@ -1843,16 +1843,15 @@ static void list_supported_capture_options(const gsr_window *window, const char if(!wayland) return; - gsr_dbus dbus; - if(!gsr_dbus_init(&dbus, NULL)) + gsr_dbus_client dbus_client; + if(!gsr_dbus_client_init(&dbus_client, NULL)) return; - char *session_handle = NULL; - if(gsr_dbus_screencast_create_session(&dbus, &session_handle) == 0) { - free(session_handle); + char session_handle[128]; + if(gsr_dbus_client_screencast_create_session(&dbus_client, session_handle, sizeof(session_handle)) == 0) puts("portal"); - } - gsr_dbus_deinit(&dbus); + + gsr_dbus_client_deinit(&dbus_client); #endif } -- cgit v1.2.3-70-g09d2