diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ef9ed793..d7e3c5303 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev libwayland-dev libxkbcommon-dev + sudo apt install libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev libwayland-dev libxkbcommon-dev libdbus-1-dev - name: Configure Null shared library run: cmake -B build-null-shared -D GLFW_BUILD_WAYLAND=OFF -D GLFW_BUILD_X11=OFF -D BUILD_SHARED_LIBS=ON diff --git a/README.md b/README.md index b27ea51dd..b51c0a350 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ information on what to include when reporting a bug. potential segmentation fault (#2744) - [Wayland] Bugfix: Confining or disabling the cursor could segfault on compositors without `pointer-constraints-unstable-v1` + - [Wayland]: Add support for dropping files when sandboxed - [X11] Bugfix: Running without a WM could trigger an assert (#2593,#2601,#2631) - [X11] Bugfix: Occasional crash when an idle display awakes (#2766) - [X11] Bugfix: Prevent BadWindow when creating small windows with a content scale @@ -174,4 +175,3 @@ request, please file it in the Finally, if you're interested in helping out with the development of GLFW or porting it to your favorite platform, join us on the forum or GitHub. - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2cbe8a733..63d51304c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -161,6 +161,10 @@ if (GLFW_BUILD_WAYLAND) wayland-egl>=0.2.7 xkbcommon>=0.5.0) + pkg_check_modules(DBUS REQUIRED dbus-1) + target_include_directories(glfw PRIVATE ${DBUS_INCLUDE_DIRS}) + target_link_libraries(glfw PRIVATE ${DBUS_LIBRARIES}) + target_include_directories(glfw PRIVATE ${Wayland_INCLUDE_DIRS}) if (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") diff --git a/src/wl_platform.h b/src/wl_platform.h index bdadb657e..682b2ce79 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -342,6 +342,7 @@ typedef struct _GLFWofferWayland struct wl_data_offer* offer; GLFWbool text_plain_utf8; GLFWbool text_uri_list; + GLFWbool portal_file_transfer; } _GLFWofferWayland; typedef struct _GLFWscaleWayland @@ -454,6 +455,7 @@ typedef struct _GLFWlibraryWayland struct wl_data_offer* dragOffer; _GLFWwindow* dragFocus; uint32_t dragSerial; + GLFWbool dragUsePortal; const char* tag; diff --git a/src/wl_window.c b/src/wl_window.c index ad39b2e0e..8d9d3865c 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -41,6 +41,7 @@ #include #include #include +#include #include "wayland-client-protocol.h" #include "xdg-shell-client-protocol.h" @@ -54,6 +55,7 @@ #define GLFW_BORDER_SIZE 4 #define GLFW_CAPTION_HEIGHT 24 +#define FILE_TRANSFER_PORTAL_MIME_TYPE "application/vnd.portal.filetransfer" static int createTmpfileCloexec(char* tmpname) { @@ -1972,6 +1974,8 @@ static void dataOfferHandleOffer(void* userData, _glfw.wl.offers[i].text_plain_utf8 = GLFW_TRUE; else if (strcmp(mimeType, "text/uri-list") == 0) _glfw.wl.offers[i].text_uri_list = GLFW_TRUE; + else if (strcmp(mimeType, FILE_TRANSFER_PORTAL_MIME_TYPE) == 0) + _glfw.wl.offers[i].portal_file_transfer = GLFW_TRUE; break; } @@ -2034,13 +2038,19 @@ static void dataDeviceHandleEnter(void* userData, _GLFWwindow* window = wl_surface_get_user_data(surface); if (window->wl.surface == surface) { - if (_glfw.wl.offers[i].text_uri_list) + GLFWbool portal = _glfw.wl.offers[i].portal_file_transfer; + if (_glfw.wl.offers[i].text_uri_list || portal) { _glfw.wl.dragOffer = offer; _glfw.wl.dragFocus = window; _glfw.wl.dragSerial = serial; + _glfw.wl.dragUsePortal = portal; - wl_data_offer_accept(offer, serial, "text/uri-list"); + if (portal) { + wl_data_offer_accept(offer, serial, FILE_TRANSFER_PORTAL_MIME_TYPE); + } else { + wl_data_offer_accept(offer, serial, "text/uri-list"); + } } } } @@ -2074,12 +2084,126 @@ static void dataDeviceHandleMotion(void* userData, { } +// Receives a dropped file that was sent using the +// [File Transfer](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileTransfer.html) portal. +// This enables us to receive files when running as a Flatpak or Snap. +static void dataDeviceHandleFileTransferPortalDrop(void* userData, + struct wl_data_device* device) +{ + char* key = readDataOfferAsString(_glfw.wl.dragOffer, FILE_TRANSFER_PORTAL_MIME_TYPE); + if (!key) + return; + + DBusError error; + dbus_error_init(&error); + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (dbus_error_is_set(&error)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus: %s", error.message); + dbus_error_free(&error); + _glfw_free(key); + return; + } + dbus_connection_set_exit_on_disconnect(connection, FALSE); + + DBusMessage* message = dbus_message_new_method_call( + "org.freedesktop.portal.Documents", + "/org/freedesktop/portal/documents", + "org.freedesktop.portal.FileTransfer", + "RetrieveFiles" + ); + if (!message) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + _glfw_free(key); + return; + } + DBusMessageIter args, options; + dbus_message_iter_init_append(message, &args); + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &key)) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + dbus_message_unref(message); + _glfw_free(key); + return; + } + if (!dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY, "{sv}", &options)) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + dbus_message_unref(message); + _glfw_free(key); + return; + } + if (!dbus_message_iter_close_container(&args, &options)) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + dbus_message_unref(message); + _glfw_free(key); + return; + } + + DBusMessage* reply = dbus_connection_send_with_reply_and_block(connection, message, DBUS_TIMEOUT_INFINITE, &error); + if (dbus_error_is_set(&error)) + { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus: %s", error.message); + dbus_error_free(&error); + dbus_message_unref(message); + _glfw_free(key); + return; + } + + DBusMessageIter out, array; + if (!dbus_message_iter_init(reply, &out)) + { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + dbus_message_unref(reply); + dbus_message_unref(message); + _glfw_free(key); + return; + } + if (dbus_message_iter_get_arg_type(&out) != DBUS_TYPE_ARRAY + || dbus_message_iter_get_element_type(&out) != DBUS_TYPE_STRING) { + _glfwInputError(GLFW_PLATFORM_ERROR, "DBus: Reply is not an array of strings"); + dbus_message_unref(reply); + dbus_message_unref(message); + _glfw_free(key); + return; + } + dbus_message_iter_recurse(&out, &array); + int elements = dbus_message_iter_get_element_count(&out); + char** paths = _glfw_calloc(elements, sizeof(char*)); + if (!paths) { + _glfwInputError(GLFW_OUT_OF_MEMORY, NULL); + dbus_message_unref(reply); + dbus_message_unref(message); + _glfw_free(key); + return; + } + int i = 0; + do { + dbus_message_iter_get_basic(&array, &paths[i++]); + } while (dbus_message_iter_next(&array)); + + _glfwInputDrop(_glfw.wl.dragFocus, elements, (const char**) paths); + + _glfw_free(paths); + dbus_message_unref(reply); + dbus_message_unref(message); + _glfw_free(key); +} + static void dataDeviceHandleDrop(void* userData, struct wl_data_device* device) { if (!_glfw.wl.dragOffer) return; + if (_glfw.wl.dragUsePortal) + { + dataDeviceHandleFileTransferPortalDrop(userData, device); + return; + } + char* string = readDataOfferAsString(_glfw.wl.dragOffer, "text/uri-list"); if (string) {