From a683ce415324319073c08b114834f4f43e436187 Mon Sep 17 00:00:00 2001
From: Colin Kinloch <[EMAIL REDACTED]>
Date: Sat, 16 Sep 2023 19:08:16 +0100
Subject: [PATCH] linux: Add portal drag and drop
---
src/core/linux/SDL_dbus.c | 65 +++++++++++++++++
src/core/linux/SDL_dbus.h | 2 +
src/video/wayland/SDL_waylanddatamanager.h | 1 +
src/video/wayland/SDL_waylandevents.c | 85 +++++++++++++++++-----
4 files changed, 134 insertions(+), 19 deletions(-)
diff --git a/src/core/linux/SDL_dbus.c b/src/core/linux/SDL_dbus.c
index f2358343f2d3..353e0777c8ff 100644
--- a/src/core/linux/SDL_dbus.c
+++ b/src/core/linux/SDL_dbus.c
@@ -534,6 +534,71 @@ char *SDL_DBus_GetLocalMachineId(void)
return NULL;
}
+
+/*
+ * Convert file drops with mime type "application/vnd.portal.filetransfer" to file paths
+ * Result must be freed with dbus->free_string_array().
+ * https://flatpak.github.io/xdg-desktop-portal/#gdbus-method-org-freedesktop-portal-FileTransfer.RetrieveFiles
+ */
+char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *path_count)
+{
+ DBusError err;
+ DBusMessageIter iter, iterDict;
+ char **paths = NULL;
+ DBusMessage *reply = NULL;
+ DBusMessage *msg = dbus.message_new_method_call("org.freedesktop.portal.Documents", /* Node */
+ "/org/freedesktop/portal/documents", /* Path */
+ "org.freedesktop.portal.FileTransfer", /* Interface */
+ "RetrieveFiles"); /* Method */
+
+ /* Make sure we have a connection to the dbus session bus */
+ if (!SDL_DBus_GetContext() || !dbus.session_conn) {
+ /* We either cannot connect to the session bus or were unable to
+ * load the D-Bus library at all. */
+ return NULL;
+ }
+
+ dbus.error_init(&err);
+
+ /* First argument is a "application/vnd.portal.filetransfer" key from a DnD or clipboard event */
+ if (!dbus.message_append_args(msg, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
+ SDL_OutOfMemory();
+ dbus.message_unref(msg);
+ goto failed;
+ }
+
+ /* Second argument is a variant dictionary for options.
+ * The spec doesn't define any entries yet so it's empty. */
+ 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_ARRAY, DBUS_TYPE_STRING, &paths, path_count, DBUS_TYPE_INVALID);
+ dbus.message_unref(reply);
+ }
+
+ if (paths) {
+ return paths;
+ }
+
+failed:
+ if (dbus.error_is_set(&err)) {
+ SDL_SetError("%s: %s", err.name, err.message);
+ dbus.error_free(&err);
+ } else {
+ SDL_SetError("Error retrieving paths for documents portal \"%s\"", key);
+ }
+
+ return NULL;
+}
#endif
/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/core/linux/SDL_dbus.h b/src/core/linux/SDL_dbus.h
index cda279eca49b..4d3898033864 100644
--- a/src/core/linux/SDL_dbus.h
+++ b/src/core/linux/SDL_dbus.h
@@ -98,6 +98,8 @@ extern SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit);
extern char *SDL_DBus_GetLocalMachineId(void);
+extern char **SDL_DBus_DocumentsPortalRetrieveFiles(const char *key, int *files_count);
+
#endif /* HAVE_DBUS_DBUS_H */
#endif /* SDL_dbus_h_ */
diff --git a/src/video/wayland/SDL_waylanddatamanager.h b/src/video/wayland/SDL_waylanddatamanager.h
index 7f0cb7e5adfe..b61fa89b0b02 100644
--- a/src/video/wayland/SDL_waylanddatamanager.h
+++ b/src/video/wayland/SDL_waylanddatamanager.h
@@ -29,6 +29,7 @@
#define TEXT_MIME "text/plain;charset=utf-8"
#define FILE_MIME "text/uri-list"
+#define FILE_PORTAL_MIME "application/vnd.portal.filetransfer"
typedef struct
{
diff --git a/src/video/wayland/SDL_waylandevents.c b/src/video/wayland/SDL_waylandevents.c
index 5450b7679476..53e19fb62693 100644
--- a/src/video/wayland/SDL_waylandevents.c
+++ b/src/video/wayland/SDL_waylandevents.c
@@ -1555,16 +1555,23 @@ static void data_device_handle_enter(void *data, struct wl_data_device *wl_data_
data_device->drag_offer = wl_data_offer_get_user_data(id);
/* TODO: SDL Support more mime types */
- has_mime = Wayland_data_offer_has_mime(
- data_device->drag_offer, FILE_MIME);
-
- /* If drag_mime is NULL this will decline the offer */
- wl_data_offer_accept(id, serial,
- (has_mime == SDL_TRUE) ? FILE_MIME : NULL);
+#ifdef SDL_USE_LIBDBUS
+ if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_PORTAL_MIME)) {
+ has_mime = SDL_TRUE;
+ wl_data_offer_accept(id, serial, FILE_PORTAL_MIME);
+ }
+#endif
+ if (Wayland_data_offer_has_mime(data_device->drag_offer, FILE_MIME)) {
+ has_mime = SDL_TRUE;
+ wl_data_offer_accept(id, serial, FILE_MIME);
+ }
/* SDL only supports "copy" style drag and drop */
- if (has_mime == SDL_TRUE) {
+ if (has_mime) {
dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ } else {
+ /* drag_mime is NULL this will decline the offer */
+ wl_data_offer_accept(id, serial, NULL);
}
if (wl_data_offer_get_version(data_device->drag_offer->offer) >= 3) {
wl_data_offer_set_actions(data_device->drag_offer->offer,
@@ -1725,21 +1732,61 @@ static void data_device_handle_drop(void *data, struct wl_data_device *wl_data_d
if (data_device->drag_offer != NULL) {
/* TODO: SDL Support more mime types */
size_t length;
- void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
- &length, FILE_MIME, SDL_TRUE);
- if (buffer) {
- char *saveptr = NULL;
- char *token = SDL_strtokr((char *)buffer, "\r\n", &saveptr);
- while (token != NULL) {
- char *fn = Wayland_URIToLocal(token);
- if (fn) {
- SDL_SendDropFile(data_device->dnd_window, fn);
+ SDL_bool drop_handled = SDL_FALSE;
+#ifdef SDL_USE_LIBDBUS
+ if (Wayland_data_offer_has_mime(
+ data_device->drag_offer, FILE_PORTAL_MIME)) {
+ void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
+ &length, FILE_PORTAL_MIME, SDL_TRUE);
+ if (buffer) {
+ SDL_DBusContext *dbus = SDL_DBus_GetContext();
+ if (dbus) {
+ int path_count = 0;
+ char **paths = SDL_DBus_DocumentsPortalRetrieveFiles(buffer, &path_count);
+ /* If dropped files contain a directory the list is empty */
+ if (paths && path_count > 0) {
+ for (int i = 0; i < path_count; i++) {
+ SDL_SendDropFile(data_device->dnd_window, paths[i]);
+ }
+ dbus->free_string_array(paths);
+ SDL_SendDropComplete(data_device->dnd_window);
+ drop_handled = SDL_TRUE;
+ }
+ }
+ SDL_free(buffer);
+ }
+ }
+#endif
+ /* If XDG document portal fails fallback.
+ * When running a flatpak sandbox this will most likely be a list of
+ * non paths that are not visible to the application
+ */
+ if (!drop_handled && Wayland_data_offer_has_mime(
+ data_device->drag_offer, FILE_MIME)) {
+ void *buffer = Wayland_data_offer_receive(data_device->drag_offer,
+ &length, FILE_MIME, SDL_TRUE);
+ if (buffer) {
+ char *saveptr = NULL;
+ char *token = SDL_strtokr((char *)buffer, "\r\n", &saveptr);
+ while (token != NULL) {
+ char *fn = Wayland_URIToLocal(token);
+ if (fn) {
+ SDL_SendDropFile(data_device->dnd_window, fn);
+ }
+ token = SDL_strtokr(NULL, "\r\n", &saveptr);
}
- token = SDL_strtokr(NULL, "\r\n", &saveptr);
+ SDL_SendDropComplete(data_device->dnd_window);
+ SDL_free(buffer);
+ drop_handled = SDL_TRUE;
}
- SDL_SendDropComplete(data_device->dnd_window);
- SDL_free(buffer);
}
+
+ if (drop_handled && wl_data_offer_get_version(data_device->drag_offer->offer) >=
+ WL_DATA_OFFER_FINISH_SINCE_VERSION) {
+ wl_data_offer_finish(data_device->drag_offer->offer);
+ }
+ Wayland_data_offer_destroy(data_device->drag_offer);
+ data_device->drag_offer = NULL;
}
}