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.
This commit is contained in:
kemal
2024-08-29 10:18:28 +03:00
committed by Sam Lantinga
parent c3b415641a
commit 16f12c0d55
3 changed files with 213 additions and 1 deletions

View File

@@ -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

View File

@@ -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_