Skip to content

Commit

Permalink
Linux/Wayland: Support parent windows
Browse files Browse the repository at this point in the history
  • Loading branch information
btzy committed Dec 8, 2024
1 parent 388549a commit 28e9745
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 12 deletions.
13 changes: 12 additions & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,47 @@ jobs:
os: [ {label: ubuntu-latest, name: latest}, {label: ubuntu-20.04, name: 20.04} ]
portal: [ {flag: OFF, dep: libgtk-3-dev, name: GTK}, {flag: ON, dep: libdbus-1-dev, name: Portal} ] # The NFD_PORTAL setting defaults to OFF (i.e. uses GTK)
autoappend: [ {flag: OFF, name: NoAppendExtn} ] # By default the NFD_PORTAL mode does not append extensions, because it breaks some features of the portal
wayland: [ {flag: OFF, name: NoWayland} ]
compiler: [ {c: gcc, cpp: g++, name: GCC}, {c: clang, cpp: clang++, name: Clang} ] # The default compiler is gcc/g++
cppstd: [20, 11]
shared_lib: [ {flag: OFF, name: Static} ]
include:
- os: {label: ubuntu-latest, name: latest}
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
autoappend: {flag: ON, name: AutoAppendExtn}
wayland: {flag: OFF, name: NoWayland}
compiler: {c: gcc, cpp: g++, name: GCC}
cppstd: 11
shared_lib: {flag: OFF, name: Static}
- os: {label: ubuntu-latest, name: latest}
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
autoappend: {flag: ON, name: AutoAppendExtn}
wayland: {flag: OFF, name: NoWayland}
compiler: {c: clang, cpp: clang++, name: Clang}
cppstd: 11
shared_lib: {flag: OFF, name: Static}
- os: {label: ubuntu-latest, name: latest}
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
autoappend: {flag: OFF, name: NoAppendExtn}
wayland: {flag: OFF, name: NoWayland}
compiler: {c: gcc, cpp: g++, name: GCC}
cppstd: 11
shared_lib: {flag: ON, name: Shared}
- os: {label: ubuntu-latest, name: latest}
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
autoappend: {flag: OFF, name: NoAppendExtn}
wayland: {flag: ON, name: Wayland}
compiler: {c: gcc, cpp: g++, name: GCC}
cppstd: 11
shared_lib: {flag: ON, name: Static}

steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Dependencies
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }}
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} -DCMAKE_C_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_APPEND_EXTENSION=${{ matrix.autoappend.flag }} -DBUILD_SHARED_LIBS=${{ matrix.shared_lib.flag }} -DNFD_BUILD_TESTS=ON ..
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=${{ matrix.compiler.c }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cpp }} -DCMAKE_CXX_STANDARD=${{ matrix.cppstd }} -DCMAKE_C_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DNFD_WAYLAND=OFF -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_APPEND_EXTENSION=${{ matrix.autoappend.flag }} -DBUILD_SHARED_LIBS=${{ matrix.shared_lib.flag }} -DNFD_BUILD_TESTS=ON ..
- name: Build
run: cmake --build build --target install
- name: Upload test binaries
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "3ps/wayland-protocols"]
path = 3ps/wayland-protocols
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git
1 change: 1 addition & 0 deletions 3ps/wayland-protocols
Submodule wayland-protocols added at 122a47
36 changes: 33 additions & 3 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,33 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
option(NFD_PORTAL "Use xdg-desktop-portal instead of GTK" OFF)
if(NOT NFD_PORTAL)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
message("Using GTK version: ${GTK3_VERSION}")
message(STATUS "Using GTK version: ${GTK3_VERSION}")
list(APPEND SOURCE_FILES nfd_gtk.cpp)
else()
pkg_check_modules(DBUS REQUIRED dbus-1)
message("Using DBUS version: ${DBUS_VERSION}")
message(STATUS "Using D-Bus version: ${DBUS_VERSION}")
list(APPEND SOURCE_FILES nfd_portal.cpp)
endif()

# for Linux, we support X11, Wayland, or both
option(NFD_X11 "Support X11 on Linux" ON)
option(NFD_WAYLAND "Support Wayland on Linux" ON)
if(NFD_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED wayland-client)
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
endif()
endif()

if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
Expand Down Expand Up @@ -94,6 +114,16 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
if(NFD_APPEND_EXTENSION)
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_APPEND_EXTENSION)
endif()

if(NFD_X11)
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_X11)
endif()
if(NFD_WAYLAND)
target_include_directories(${TARGET_NAME} PRIVATE ${WAYLAND_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME} PRIVATE ${WAYLAND_LINK_LIBRARIES})
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_WAYLAND)
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
endif()
endif()

if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
Expand All @@ -118,7 +148,7 @@ if(nfd_COMPILER STREQUAL COMPILER_CLANGCL)
endif()

if(nfd_COMPILER STREQUAL COMPILER_GNU)
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>)
endif()

set_target_properties(${TARGET_NAME} PROPERTIES
Expand Down
3 changes: 2 additions & 1 deletion src/include/nfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ enum {
NFD_WINDOW_HANDLE_TYPE_COCOA = 2,
// X11: handle is Window
NFD_WINDOW_HANDLE_TYPE_X11 = 3,
// Wayland support will be implemented separately in the future
// Wayland: handle is wl_surface*
NFD_WINDOW_HANDLE_TYPE_WAYLAND = 4,
};
// The native window handle. If using a platform abstraction framework (e.g. SDL2), this should be
// obtained using the corresponding NFD glue header (e.g. nfd_sdl2.h).
Expand Down
132 changes: 125 additions & 7 deletions src/nfd_portal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@
#define getrandom(buf, sz, flags) syscall(SYS_getrandom, buf, sz, flags)
#endif

#ifdef NFD_WAYLAND
#include <wayland-client.h>
#include "xdg-foreign-unstable-v1.h"
struct wl_display* wayland_display;
struct wl_registry* wayland_registry;
uint32_t wayland_xdg_exporter_v1_name;
struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
#endif

#include "nfd.h"

/*
Expand Down Expand Up @@ -68,6 +77,17 @@ struct FreeCheck_Guard {
}
};

void EmptyFn(void*) {}

struct DestroyFunc {
DestroyFunc() : fn(&EmptyFn), context(nullptr) {}
~DestroyFunc() {
(*fn)(context);
}
void(*fn)(void*);
void* context;
};

struct DBusMessage_Guard {
DBusMessage* data;
DBusMessage_Guard(DBusMessage* freeable) noexcept : data(freeable) {}
Expand Down Expand Up @@ -172,9 +192,43 @@ constexpr const char* DBUS_PATH = "/org/freedesktop/portal/desktop";
constexpr const char* DBUS_FILECHOOSER_IFACE = "org.freedesktop.portal.FileChooser";
constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";

void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandle_t& parentWindow) {
#ifdef NFD_WAYLAND
constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
constexpr const char* WAYLAND_PREFIX = "wayland:";

void DestroyXdgExported(void* context) {
zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
}

void zxdg_exported_v1_handle(void* context,
struct zxdg_exported_v1* zxdg_exported_v1,
const char* handle) {
if (!context) return;
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
const size_t handle_len = strlen(handle);
const size_t prefix_len = strlen(WAYLAND_PREFIX);
char* const buf = NFDi_Malloc<char>(prefix_len + handle_len + 1);
char* buf_end = copy(WAYLAND_PREFIX, WAYLAND_PREFIX + prefix_len, buf);
buf_end = copy(handle, handle+handle_len, buf_end);
*buf_end = '\0';
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &buf);
NFDi_Free(buf);
}

constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener {
&zxdg_exported_v1_handle
};
#endif

void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
(void) iter;
(void) parentWindow;
(void) destroyFn;
(void) destroyFnContext;
switch (parentWindow.type) {
#ifdef NFD_X11
case NFD_WINDOW_HANDLE_TYPE_X11: {
fprintf(stderr, "X11\n");
constexpr size_t maxX11WindowStrLen =
4 + sizeof(uintptr_t) * 2 + 1; // "x11:" + "<hex>" + "\0"
char serializedWindowBuf[maxX11WindowStrLen];
Expand All @@ -190,6 +244,26 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandl
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &serializedWindow);
return;
}
#endif
#ifdef NFD_WAYLAND
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
fprintf(stderr, "Wayland\n");
if (wayland_xdg_exporter_v1) {
struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
return;
}
zxdg_exported_v1_add_listener(exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
wl_display_roundtrip(wayland_display);
zxdg_exported_v1_set_user_data(exported, nullptr);
destroyFn = &DestroyXdgExported;
destroyFnContext = static_cast<void*>(exported);
}
return;
}
#endif
default: {
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
return;
Expand Down Expand Up @@ -619,11 +693,11 @@ void AppendOpenFileQueryParams(DBusMessage* query,
const nfdnfilteritem_t* filterList,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdwindowhandle_t& parentWindow) {
const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
DBusMessageIter iter;
dbus_message_iter_init_append(query, &iter);

AppendOpenFileQueryParentWindow(iter, parentWindow);
AppendOpenFileQueryParentWindow(iter, parentWindow, destroyFn, destroyFnContext);
AppendOpenFileQueryTitle<Multiple, Directory>(iter);

DBusMessageIter sub_iter;
Expand All @@ -643,11 +717,11 @@ void AppendSaveFileQueryParams(DBusMessage* query,
nfdfiltersize_t filterCount,
const nfdnchar_t* defaultPath,
const nfdnchar_t* defaultName,
const nfdwindowhandle_t& parentWindow) {
const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
DBusMessageIter iter;
dbus_message_iter_init_append(query, &iter);

AppendOpenFileQueryParentWindow(iter, parentWindow);
AppendOpenFileQueryParentWindow(iter, parentWindow, destroyFn, destroyFnContext);
AppendSaveFileQueryTitle(iter);

DBusMessageIter sub_iter;
Expand Down Expand Up @@ -1195,8 +1269,10 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
DBusMessage* query = dbus_message_new_method_call(
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "OpenFile");
DBusMessage_Guard query_guard(query);

DestroyFunc destroy;
AppendOpenFileQueryParams<Multiple, Directory>(
query, handle_token_ptr, filterList, filterCount, defaultPath, parentWindow);
query, handle_token_ptr, filterList, filterCount, defaultPath, parentWindow, destroy.fn, destroy.context);

DBusMessage* reply =
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
Expand Down Expand Up @@ -1278,8 +1354,10 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg,
DBusMessage* query = dbus_message_new_method_call(
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "SaveFile");
DBusMessage_Guard query_guard(query);

DestroyFunc destroy;
AppendSaveFileQueryParams(
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName, parentWindow);
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName, parentWindow, destroy.fn, destroy.context);

DBusMessage* reply =
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
Expand Down Expand Up @@ -1383,6 +1461,27 @@ nfdresult_t NFD_DBus_GetVersion(dbus_uint32_t& outVersion) {
return NFD_OKAY;
}

#ifdef NFD_WAYLAND
void registry_handle_global(void* context, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
wayland_xdg_exporter_v1_name = name;
wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
}
}

void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
wayland_xdg_exporter_v1 = nullptr;
}
}

constexpr struct wl_registry_listener wayland_registry_listener = {
&registry_handle_global,
&registry_handle_global_remove
};
#endif

} // namespace

/* public */
Expand All @@ -1408,11 +1507,30 @@ nfdresult_t NFD_Init(void) {
dbus_unique_name = dbus_bus_get_unique_name(dbus_conn);
if (!dbus_unique_name) {
NFDi_SetError("Unable to get the unique name of our D-Bus connection.");
dbus_connection_unref(dbus_conn);
return NFD_ERROR;
}
#ifdef NFD_WAYLAND
// This might fail, but it is fine because the system might not actually have Wayland installed
wayland_display = wl_display_connect(nullptr);
if (wayland_display) {
wayland_registry = wl_display_get_registry(wayland_display);
wayland_xdg_exporter_v1 = nullptr;
// seems like registry can't be null
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
wl_display_roundtrip(wayland_display);
}
#endif
return NFD_OKAY;
}
void NFD_Quit(void) {
#ifdef NFD_WAYLAND
if (wayland_display) {
if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
wl_registry_destroy(wayland_registry);
wl_display_disconnect(wayland_display);
}
#endif
dbus_connection_unref(dbus_conn);
// Note: We do not free dbus_error since NFD_Init might set it.
// To avoid leaking memory, the caller should explicitly call NFD_ClearError after reading the
Expand Down

0 comments on commit 28e9745

Please sign in to comment.