diff --git a/src/camera/pipewire/SDL_camera_pipewire.c b/src/camera/pipewire/SDL_camera_pipewire.c index 4de2f2346c..5059a9b1e5 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 #include #include @@ -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 9a2fc1ea53..b4ddfda1bc 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 @@ failed: 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 2073d6cee8..3fab08c14c 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_