linux: Add portal drag and drop

This commit is contained in:
Colin Kinloch 2023-09-20 16:13:31 +01:00 committed by Frank Praznik
parent 952c5059b5
commit 99421b64d0
4 changed files with 137 additions and 20 deletions

View File

@ -565,4 +565,70 @@ 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

View File

@ -102,6 +102,8 @@ extern SDL_bool SDL_DBus_ScreensaverInhibit(SDL_bool inhibit);
extern void SDL_DBus_PumpEvents(void);
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_ */

View File

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

View File

@ -1848,18 +1848,26 @@ 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) {
if (wl_data_offer_get_version(data_device->drag_offer->offer) >=
WL_DATA_OFFER_SET_ACTIONS_SINCE_VERSION) {
wl_data_offer_set_actions(data_device->drag_offer->offer,
dnd_action, dnd_action);
}
@ -2041,21 +2049,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,
FILE_MIME, &length);
if (buffer) {
char *saveptr = NULL;
char *token = SDL_strtok_r((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,
FILE_PORTAL_MIME, &length);
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;
}
}
token = SDL_strtok_r(NULL, "\r\n", &saveptr);
SDL_free(buffer);
}
SDL_SendDropComplete(data_device->dnd_window);
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,
FILE_MIME, &length);
if (buffer) {
char *saveptr = NULL;
char *token = SDL_strtok_r((char *)buffer, "\r\n", &saveptr);
while (token != NULL) {
char *fn = Wayland_URIToLocal(token);
if (fn) {
SDL_SendDropFile(data_device->dnd_window, fn);
}
token = SDL_strtok_r(NULL, "\r\n", &saveptr);
}
SDL_SendDropComplete(data_device->dnd_window);
SDL_free(buffer);
drop_handled = SDL_TRUE;
}
}
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;
}
}