From 16f12c0d55ca798a821350f5aef66c7be8ce95c6 Mon Sep 17 00:00:00 2001
From: kemal <[EMAIL REDACTED]>
Date: Thu, 29 Aug 2024 10:18:28 +0300
Subject: [PATCH] Implement the XDP Camera portal
This helps the Pipewire camera driver to access cameras
in a sandboxed environment without host Pipewire socket access.
Unlike other platforms, no event is sent when the user rejects
camera access. This is because there is no mechanism to query
cameras through the portal, and we only obtain access to the
Pipewire fd if the user accepts the request. The Pipewire driver
will attempt to open the host socket instead.
---
src/camera/pipewire/SDL_camera_pipewire.c | 26 ++-
src/core/linux/SDL_dbus.c | 183 ++++++++++++++++++++++
src/core/linux/SDL_dbus.h | 5 +
3 files changed, 213 insertions(+), 1 deletion(-)
diff --git a/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c
index 4de2f2346cadc..5059a9b1e50b8 100644
--- a/src/camera/pipewire/SDL_camera_pipewire.c
+++ b/src/camera/pipewire/SDL_camera_pipewire.c
@@ -25,6 +25,10 @@
#include "../SDL_syscamera.h"
+#ifdef HAVE_DBUS_DBUS_H
+#include "../../core/linux/SDL_dbus.h"
+#endif
+
#include <spa/utils/type.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
@@ -78,6 +82,9 @@ static int (*PIPEWIRE_pw_thread_loop_start)(struct pw_thread_loop *);
static struct pw_context *(*PIPEWIRE_pw_context_new)(struct pw_loop *, struct pw_properties *, size_t);
static void (*PIPEWIRE_pw_context_destroy)(struct pw_context *);
static struct pw_core *(*PIPEWIRE_pw_context_connect)(struct pw_context *, struct pw_properties *, size_t);
+#ifdef SDL_USE_LIBDBUS
+static struct pw_core *(*PIPEWIRE_pw_context_connect_fd)(struct pw_context *, int, struct pw_properties *, size_t);
+#endif
static void (*PIPEWIRE_pw_proxy_add_object_listener)(struct pw_proxy *, struct spa_hook *, const void *, void *);
static void (*PIPEWIRE_pw_proxy_add_listener)(struct pw_proxy *, struct spa_hook *, const struct pw_proxy_events *, void *);
static void *(*PIPEWIRE_pw_proxy_get_user_data)(struct pw_proxy *);
@@ -171,6 +178,9 @@ static bool load_pipewire_syms(void)
SDL_PIPEWIRE_SYM(pw_context_new);
SDL_PIPEWIRE_SYM(pw_context_destroy);
SDL_PIPEWIRE_SYM(pw_context_connect);
+#ifdef SDL_USE_LIBDBUS
+ SDL_PIPEWIRE_SYM(pw_context_connect_fd);
+#endif
SDL_PIPEWIRE_SYM(pw_proxy_add_listener);
SDL_PIPEWIRE_SYM(pw_proxy_add_object_listener);
SDL_PIPEWIRE_SYM(pw_proxy_get_user_data);
@@ -1021,6 +1031,13 @@ static bool pipewire_server_version_at_least(int major, int minor, int patch)
static bool hotplug_loop_init(void)
{
int res;
+#ifdef SDL_USE_LIBDBUS
+ int fd;
+
+ fd = SDL_DBus_CameraPortalRequestAccess();
+ if (fd == -1)
+ return false;
+#endif
spa_list_init(&hotplug.global_list);
@@ -1035,8 +1052,15 @@ static bool hotplug_loop_init(void)
if (!hotplug.context) {
return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
}
-
+#ifdef SDL_USE_LIBDBUS
+ if (fd >= 0) {
+ hotplug.core = PIPEWIRE_pw_context_connect_fd(hotplug.context, fd, NULL, 0);
+ } else {
+ hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0);
+ }
+#else
hotplug.core = PIPEWIRE_pw_context_connect(hotplug.context, NULL, 0);
+#endif
if (!hotplug.core) {
return SDL_SetError("Pipewire: Failed to connect hotplug detection context (%i)", errno);
}
diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c
index 9a2fc1ea53194..b4ddfda1bc21f 100644
--- a/src/core/linux/SDL_dbus.c
+++ b/src/core/linux/SDL_dbus.c
@@ -48,6 +48,7 @@ static bool LoadDBUSSyms(void)
SDL_DBUS_SYM(DBusConnection *(*)(DBusBusType, DBusError *), bus_get_private);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, DBusError *), bus_register);
SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_add_match);
+ SDL_DBUS_SYM(void (*)(DBusConnection *, const char *, DBusError *), bus_remove_match);
SDL_DBUS_SYM(DBusConnection *(*)(const char *, DBusError *), connection_open_private);
SDL_DBUS_SYM(void (*)(DBusConnection *, dbus_bool_t), connection_set_exit_on_disconnect);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *), connection_get_is_connected);
@@ -61,6 +62,7 @@ static bool LoadDBUSSyms(void)
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_unref);
SDL_DBUS_SYM(void (*)(DBusConnection *), connection_flush);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write);
+ SDL_DBUS_SYM(dbus_bool_t (*)(DBusConnection *, int), connection_read_write_dispatch);
SDL_DBUS_SYM(DBusDispatchStatus (*)(DBusConnection *), connection_dispatch);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *, const char *), message_is_signal);
SDL_DBUS_SYM(dbus_bool_t (*)(DBusMessage *, const char *), message_has_path);
@@ -82,6 +84,7 @@ static bool LoadDBUSSyms(void)
SDL_DBUS_SYM(dbus_bool_t (*)(void), threads_init_default);
SDL_DBUS_SYM(void (*)(DBusError *), error_init);
SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *), error_is_set);
+ SDL_DBUS_SYM(dbus_bool_t (*)(const DBusError *, const char *), error_has_name);
SDL_DBUS_SYM(void (*)(DBusError *), error_free);
SDL_DBUS_SYM(char *(*)(void), get_local_machine_id);
SDL_DBUS_SYM_OPTIONAL(char *(*)(DBusError *), try_get_local_machine_id);
@@ -638,4 +641,184 @@ char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
return NULL;
}
+typedef struct SDL_DBus_CameraPortalMessageHandlerData
+{
+ uint32_t response;
+ char *path;
+ DBusError *err;
+ bool done;
+} SDL_DBus_CameraPortalMessageHandlerData;
+
+static DBusHandlerResult SDL_DBus_CameraPortalMessageHandler(DBusConnection *conn, DBusMessage *msg, void *v)
+{
+ SDL_DBus_CameraPortalMessageHandlerData *data = v;
+ const char *name, *old, *new;
+
+ if (dbus.message_is_signal(msg, "org.freedesktop.DBus", "NameOwnerChanged")) {
+ if (!dbus.message_get_args(msg, data->err,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old,
+ DBUS_TYPE_STRING, &new,
+ DBUS_TYPE_INVALID)) {
+ data->done = true;
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ if (SDL_strcmp(name, "org.freedesktop.portal.Desktop") != 0 ||
+ SDL_strcmp(new, "") != 0) {
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ data->done = true;
+ data->response = -1;
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ if (!dbus.message_has_path(msg, data->path) || !dbus.message_is_signal(msg, "org.freedesktop.portal.Request", "Response")) {
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+ dbus.message_get_args(msg, data->err, DBUS_TYPE_UINT32, &data->response, DBUS_TYPE_INVALID);
+ data->done = true;
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+#define SIGNAL_NAMEOWNERCHANGED "type='signal',\
+ sender='org.freedesktop.DBus',\
+ interface='org.freedesktop.DBus',\
+ member='NameOwnerChanged',\
+ arg0='org.freedesktop.portal.Desktop',\
+ arg2=''"
+
+/*
+ * Requests access for the camera. Returns -1 on error, -2 on denied access or
+ * missing portal, otherwise returns a file descriptor to be used by the Pipewire driver.
+ * https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Camera.html
+ */
+int SDL_DBus_CameraPortalRequestAccess(void)
+{
+ SDL_DBus_CameraPortalMessageHandlerData data;
+ DBusError err;
+ DBusMessageIter iter, iterDict;
+ DBusMessage *reply, *msg;
+ int fd;
+
+ if (SDL_DetectSandbox() == SDL_SANDBOX_NONE) {
+ return -2;
+ }
+
+ if (!SDL_DBus_GetContext()) {
+ return -2;
+ }
+
+ dbus.error_init(&err);
+
+ msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Camera",
+ "AccessCamera");
+
+ dbus.message_iter_init_append(msg, &iter);
+ if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
+ !dbus.message_iter_close_container(&iter, &iterDict)) {
+ SDL_OutOfMemory();
+ dbus.message_unref(msg);
+ goto failed;
+ }
+
+ reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
+ dbus.message_unref(msg);
+
+ if (reply) {
+ dbus.message_get_args(reply, &err, DBUS_TYPE_OBJECT_PATH, &data.path, DBUS_TYPE_INVALID);
+ if (dbus.error_is_set(&err)) {
+ dbus.message_unref(reply);
+ goto failed;
+ }
+ if ((data.path = SDL_strdup(data.path)) == NULL) {
+ dbus.message_unref(reply);
+ SDL_OutOfMemory();
+ goto failed;
+ }
+ dbus.message_unref(reply);
+ } else {
+ if (dbus.error_has_name(&err, DBUS_ERROR_NAME_HAS_NO_OWNER)) {
+ return -2;
+ }
+ goto failed;
+ }
+
+ dbus.bus_add_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
+ if (dbus.error_is_set(&err)) {
+ SDL_free(data.path);
+ goto failed;
+ }
+ data.err = &err;
+ data.done = false;
+ if (!dbus.connection_add_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data, NULL)) {
+ SDL_free(data.path);
+ SDL_OutOfMemory();
+ goto failed;
+ }
+ while (!data.done && dbus.connection_read_write_dispatch(dbus.session_conn, -1)) {
+ ;
+ }
+
+ dbus.bus_remove_match(dbus.session_conn, SIGNAL_NAMEOWNERCHANGED, &err);
+ if (dbus.error_is_set(&err)) {
+ SDL_free(data.path);
+ goto failed;
+ }
+ dbus.connection_remove_filter(dbus.session_conn, SDL_DBus_CameraPortalMessageHandler, &data);
+ SDL_free(data.path);
+ if (!data.done) {
+ goto failed;
+ }
+ if (dbus.error_is_set(&err)) { // from the message handler
+ goto failed;
+ }
+ if (data.response == 1 || data.response == 2) {
+ return -2;
+ } else if (data.response != 0) {
+ goto failed;
+ }
+
+ msg = dbus.message_new_method_call("org.freedesktop.portal.Desktop",
+ "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Camera",
+ "OpenPipeWireRemote");
+
+ dbus.message_iter_init_append(msg, &iter);
+ if (!dbus.message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &iterDict) ||
+ !dbus.message_iter_close_container(&iter, &iterDict)) {
+ SDL_OutOfMemory();
+ dbus.message_unref(msg);
+ goto failed;
+ }
+
+ reply = dbus.connection_send_with_reply_and_block(dbus.session_conn, msg, DBUS_TIMEOUT_USE_DEFAULT, &err);
+ dbus.message_unref(msg);
+
+ if (reply) {
+ dbus.message_get_args(reply, &err, DBUS_TYPE_UNIX_FD, &fd, DBUS_TYPE_INVALID);
+ dbus.message_unref(reply);
+ if (dbus.error_is_set(&err)) {
+ goto failed;
+ }
+ } else {
+ goto failed;
+ }
+
+ return fd;
+
+failed:
+ if (dbus.error_is_set(&err)) {
+ if (dbus.error_has_name(&err, DBUS_ERROR_NO_MEMORY)) {
+ SDL_OutOfMemory();
+ }
+ SDL_SetError("%s: %s", err.name, err.message);
+ dbus.error_free(&err);
+ } else {
+ SDL_SetError("Error requesting access for the camera");
+ }
+
+ return -1;
+}
+
#endif
diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h
index 2073d6cee8bab..3fab08c14cc31 100644
--- a/src/core/linux/SDL_dbus.h
+++ b/src/core/linux/SDL_dbus.h
@@ -43,6 +43,7 @@ typedef struct SDL_DBusContext
DBusConnection *(*bus_get_private)(DBusBusType, DBusError *);
dbus_bool_t (*bus_register)(DBusConnection *, DBusError *);
void (*bus_add_match)(DBusConnection *, const char *, DBusError *);
+ void (*bus_remove_match)(DBusConnection *, const char *, DBusError *);
DBusConnection *(*connection_open_private)(const char *, DBusError *);
void (*connection_set_exit_on_disconnect)(DBusConnection *, dbus_bool_t);
dbus_bool_t (*connection_get_is_connected)(DBusConnection *);
@@ -57,6 +58,7 @@ typedef struct SDL_DBusContext
void (*connection_unref)(DBusConnection *);
void (*connection_flush)(DBusConnection *);
dbus_bool_t (*connection_read_write)(DBusConnection *, int);
+ dbus_bool_t (*connection_read_write_dispatch)(DBusConnection *, int);
DBusDispatchStatus (*connection_dispatch)(DBusConnection *);
dbus_bool_t (*message_is_signal)(DBusMessage *, const char *, const char *);
dbus_bool_t (*message_has_path)(DBusMessage *, const char *);
@@ -78,6 +80,7 @@ typedef struct SDL_DBusContext
dbus_bool_t (*threads_init_default)(void);
void (*error_init)(DBusError *);
dbus_bool_t (*error_is_set)(const DBusError *);
+ dbus_bool_t (*error_has_name)(const DBusError *, const char *);
void (*error_free)(DBusError *);
char *(*get_local_machine_id)(void);
char *(*try_get_local_machine_id)(DBusError *);
@@ -109,6 +112,8 @@ extern char *SDL_DBus_GetLocalMachineId(void);
extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count);
+extern int SDL_DBus_CameraPortalRequestAccess(void);
+
#endif // HAVE_DBUS_DBUS_H
#endif // SDL_dbus_h_