Skip to content

Commit e703e7a

Browse files
committed
Linux/Wayland: Support parent windows
1 parent 388549a commit e703e7a

File tree

6 files changed

+181
-15
lines changed

6 files changed

+181
-15
lines changed

.github/workflows/cmake.yml

+17-4
Original file line numberDiff line numberDiff line change
@@ -38,50 +38,63 @@ jobs:
3838

3939
build-ubuntu:
4040

41-
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
41+
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.wayland.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
4242
runs-on: ${{ matrix.os.label }}
4343

4444
strategy:
4545
matrix:
4646
os: [ {label: ubuntu-latest, name: latest}, {label: ubuntu-20.04, name: 20.04} ]
4747
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)
4848
autoappend: [ {flag: OFF, name: NoAppendExtn} ] # By default the NFD_PORTAL mode does not append extensions, because it breaks some features of the portal
49+
wayland: [ {flag: OFF, dep: , name: NoWayland} ]
4950
compiler: [ {c: gcc, cpp: g++, name: GCC}, {c: clang, cpp: clang++, name: Clang} ] # The default compiler is gcc/g++
5051
cppstd: [20, 11]
5152
shared_lib: [ {flag: OFF, name: Static} ]
5253
include:
5354
- os: {label: ubuntu-latest, name: latest}
5455
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
5556
autoappend: {flag: ON, name: AutoAppendExtn}
57+
wayland: {flag: OFF, dep: , name: NoWayland}
5658
compiler: {c: gcc, cpp: g++, name: GCC}
5759
cppstd: 11
5860
shared_lib: {flag: OFF, name: Static}
5961
- os: {label: ubuntu-latest, name: latest}
6062
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
6163
autoappend: {flag: ON, name: AutoAppendExtn}
64+
wayland: {flag: OFF, dep: , name: NoWayland}
6265
compiler: {c: clang, cpp: clang++, name: Clang}
6366
cppstd: 11
6467
shared_lib: {flag: OFF, name: Static}
6568
- os: {label: ubuntu-latest, name: latest}
6669
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
6770
autoappend: {flag: OFF, name: NoAppendExtn}
71+
wayland: {flag: OFF, dep: , name: NoWayland}
6872
compiler: {c: gcc, cpp: g++, name: GCC}
6973
cppstd: 11
7074
shared_lib: {flag: ON, name: Shared}
75+
- os: {label: ubuntu-latest, name: latest}
76+
portal: {flag: ON, dep: libdbus-1-dev, name: Portal}
77+
autoappend: {flag: OFF, name: NoAppendExtn}
78+
wayland: {flag: ON, dep: libwayland-dev libwayland-bin, name: Wayland}
79+
compiler: {c: gcc, cpp: g++, name: GCC}
80+
cppstd: 11
81+
shared_lib: {flag: ON, name: Static}
7182

7283
steps:
7384
- name: Checkout
7485
uses: actions/checkout@v4
86+
with:
87+
submodules: true
7588
- name: Install Dependencies
76-
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }}
89+
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }} ${{ matrix.wayland.dep }}
7790
- name: Configure
78-
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 ..
91+
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 ..
7992
- name: Build
8093
run: cmake --build build --target install
8194
- name: Upload test binaries
8295
uses: actions/upload-artifact@v4
8396
with:
84-
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
97+
name: Ubuntu ${{ matrix.os.name }} - ${{ matrix.compiler.name }}, ${{ matrix.portal.name }}, ${{ matrix.wayland.name }}, ${{ matrix.autoappend.name }}, ${{ matrix.shared_lib.name }}, C++${{ matrix.cppstd }}
8598
path: |
8699
build/src/*
87100
build/test/*

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "3ps/wayland-protocols"]
2+
path = 3ps/wayland-protocols
3+
url = https://gitlab.freedesktop.org/wayland/wayland-protocols.git

3ps/wayland-protocols

Submodule wayland-protocols added at 122a47a

src/CMakeLists.txt

+33-3
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,33 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
1818
option(NFD_PORTAL "Use xdg-desktop-portal instead of GTK" OFF)
1919
if(NOT NFD_PORTAL)
2020
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
21-
message("Using GTK version: ${GTK3_VERSION}")
21+
message(STATUS "Using GTK version: ${GTK3_VERSION}")
2222
list(APPEND SOURCE_FILES nfd_gtk.cpp)
2323
else()
2424
pkg_check_modules(DBUS REQUIRED dbus-1)
25-
message("Using DBUS version: ${DBUS_VERSION}")
25+
message(STATUS "Using D-Bus version: ${DBUS_VERSION}")
2626
list(APPEND SOURCE_FILES nfd_portal.cpp)
2727
endif()
28+
29+
# for Linux, we support X11, Wayland, or both
30+
option(NFD_X11 "Support X11 on Linux" ON)
31+
option(NFD_WAYLAND "Support Wayland on Linux" ON)
32+
if(NFD_WAYLAND)
33+
pkg_check_modules(WAYLAND REQUIRED wayland-client)
34+
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
35+
set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
36+
add_custom_command(
37+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
38+
COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
39+
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
40+
)
41+
add_custom_command(
42+
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
43+
COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
44+
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
45+
)
46+
list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
47+
endif()
2848
endif()
2949

3050
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
@@ -94,6 +114,16 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
94114
if(NFD_APPEND_EXTENSION)
95115
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_APPEND_EXTENSION)
96116
endif()
117+
118+
if(NFD_X11)
119+
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_X11)
120+
endif()
121+
if(NFD_WAYLAND)
122+
target_include_directories(${TARGET_NAME} PRIVATE ${WAYLAND_INCLUDE_DIRS})
123+
target_link_libraries(${TARGET_NAME} PRIVATE ${WAYLAND_LINK_LIBRARIES})
124+
target_compile_definitions(${TARGET_NAME} PRIVATE NFD_WAYLAND)
125+
target_include_directories(${TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
126+
endif()
97127
endif()
98128

99129
if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
@@ -118,7 +148,7 @@ if(nfd_COMPILER STREQUAL COMPILER_CLANGCL)
118148
endif()
119149

120150
if(nfd_COMPILER STREQUAL COMPILER_GNU)
121-
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions -fno-rtti)
151+
target_compile_options(${TARGET_NAME} PRIVATE -nostdlib -fno-exceptions $<$<COMPILE_LANGUAGE:CXX>:-fno-rtti>)
122152
endif()
123153

124154
set_target_properties(${TARGET_NAME} PROPERTIES

src/include/nfd.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,8 @@ enum {
105105
NFD_WINDOW_HANDLE_TYPE_COCOA = 2,
106106
// X11: handle is Window
107107
NFD_WINDOW_HANDLE_TYPE_X11 = 3,
108-
// Wayland support will be implemented separately in the future
108+
// Wayland: handle is wl_surface*
109+
NFD_WINDOW_HANDLE_TYPE_WAYLAND = 4,
109110
};
110111
// The native window handle. If using a platform abstraction framework (e.g. SDL2), this should be
111112
// obtained using the corresponding NFD glue header (e.g. nfd_sdl2.h).

src/nfd_portal.cpp

+125-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@
2626
#define getrandom(buf, sz, flags) syscall(SYS_getrandom, buf, sz, flags)
2727
#endif
2828

29+
#ifdef NFD_WAYLAND
30+
#include <wayland-client.h>
31+
#include "xdg-foreign-unstable-v1.h"
32+
struct wl_display* wayland_display;
33+
struct wl_registry* wayland_registry;
34+
uint32_t wayland_xdg_exporter_v1_name;
35+
struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
36+
#endif
37+
2938
#include "nfd.h"
3039

3140
/*
@@ -68,6 +77,17 @@ struct FreeCheck_Guard {
6877
}
6978
};
7079

80+
void EmptyFn(void*) {}
81+
82+
struct DestroyFunc {
83+
DestroyFunc() : fn(&EmptyFn), context(nullptr) {}
84+
~DestroyFunc() {
85+
(*fn)(context);
86+
}
87+
void(*fn)(void*);
88+
void* context;
89+
};
90+
7191
struct DBusMessage_Guard {
7292
DBusMessage* data;
7393
DBusMessage_Guard(DBusMessage* freeable) noexcept : data(freeable) {}
@@ -172,9 +192,43 @@ constexpr const char* DBUS_PATH = "/org/freedesktop/portal/desktop";
172192
constexpr const char* DBUS_FILECHOOSER_IFACE = "org.freedesktop.portal.FileChooser";
173193
constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
174194

175-
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandle_t& parentWindow) {
195+
#ifdef NFD_WAYLAND
196+
constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
197+
constexpr const char* WAYLAND_PREFIX = "wayland:";
198+
199+
void DestroyXdgExported(void* context) {
200+
zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
201+
}
202+
203+
void zxdg_exported_v1_handle(void* context,
204+
struct zxdg_exported_v1* zxdg_exported_v1,
205+
const char* handle) {
206+
if (!context) return;
207+
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
208+
const size_t handle_len = strlen(handle);
209+
const size_t prefix_len = strlen(WAYLAND_PREFIX);
210+
char* const buf = NFDi_Malloc<char>(prefix_len + handle_len + 1);
211+
char* buf_end = copy(WAYLAND_PREFIX, WAYLAND_PREFIX + prefix_len, buf);
212+
buf_end = copy(handle, handle+handle_len, buf_end);
213+
*buf_end = '\0';
214+
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &buf);
215+
NFDi_Free(buf);
216+
}
217+
218+
constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener {
219+
&zxdg_exported_v1_handle
220+
};
221+
#endif
222+
223+
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
224+
(void) iter;
225+
(void) parentWindow;
226+
(void) destroyFn;
227+
(void) destroyFnContext;
176228
switch (parentWindow.type) {
229+
#ifdef NFD_X11
177230
case NFD_WINDOW_HANDLE_TYPE_X11: {
231+
fprintf(stderr, "X11\n");
178232
constexpr size_t maxX11WindowStrLen =
179233
4 + sizeof(uintptr_t) * 2 + 1; // "x11:" + "<hex>" + "\0"
180234
char serializedWindowBuf[maxX11WindowStrLen];
@@ -190,6 +244,26 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter, const nfdwindowhandl
190244
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &serializedWindow);
191245
return;
192246
}
247+
#endif
248+
#ifdef NFD_WAYLAND
249+
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
250+
fprintf(stderr, "Wayland\n");
251+
if (wayland_xdg_exporter_v1) {
252+
struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
253+
if (!exported) {
254+
// if we fail to export the wl_surface, act as if the window has no parent
255+
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
256+
return;
257+
}
258+
zxdg_exported_v1_add_listener(exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
259+
wl_display_roundtrip(wayland_display);
260+
zxdg_exported_v1_set_user_data(exported, nullptr);
261+
destroyFn = &DestroyXdgExported;
262+
destroyFnContext = static_cast<void*>(exported);
263+
}
264+
return;
265+
}
266+
#endif
193267
default: {
194268
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
195269
return;
@@ -619,11 +693,11 @@ void AppendOpenFileQueryParams(DBusMessage* query,
619693
const nfdnfilteritem_t* filterList,
620694
nfdfiltersize_t filterCount,
621695
const nfdnchar_t* defaultPath,
622-
const nfdwindowhandle_t& parentWindow) {
696+
const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
623697
DBusMessageIter iter;
624698
dbus_message_iter_init_append(query, &iter);
625699

626-
AppendOpenFileQueryParentWindow(iter, parentWindow);
700+
AppendOpenFileQueryParentWindow(iter, parentWindow, destroyFn, destroyFnContext);
627701
AppendOpenFileQueryTitle<Multiple, Directory>(iter);
628702

629703
DBusMessageIter sub_iter;
@@ -643,11 +717,11 @@ void AppendSaveFileQueryParams(DBusMessage* query,
643717
nfdfiltersize_t filterCount,
644718
const nfdnchar_t* defaultPath,
645719
const nfdnchar_t* defaultName,
646-
const nfdwindowhandle_t& parentWindow) {
720+
const nfdwindowhandle_t& parentWindow, void(*&destroyFn)(void*), void*& destroyFnContext) {
647721
DBusMessageIter iter;
648722
dbus_message_iter_init_append(query, &iter);
649723

650-
AppendOpenFileQueryParentWindow(iter, parentWindow);
724+
AppendOpenFileQueryParentWindow(iter, parentWindow, destroyFn, destroyFnContext);
651725
AppendSaveFileQueryTitle(iter);
652726

653727
DBusMessageIter sub_iter;
@@ -1195,8 +1269,10 @@ nfdresult_t NFD_DBus_OpenFile(DBusMessage*& outMsg,
11951269
DBusMessage* query = dbus_message_new_method_call(
11961270
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "OpenFile");
11971271
DBusMessage_Guard query_guard(query);
1272+
1273+
DestroyFunc destroy;
11981274
AppendOpenFileQueryParams<Multiple, Directory>(
1199-
query, handle_token_ptr, filterList, filterCount, defaultPath, parentWindow);
1275+
query, handle_token_ptr, filterList, filterCount, defaultPath, parentWindow, destroy.fn, destroy.context);
12001276

12011277
DBusMessage* reply =
12021278
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
@@ -1278,8 +1354,10 @@ nfdresult_t NFD_DBus_SaveFile(DBusMessage*& outMsg,
12781354
DBusMessage* query = dbus_message_new_method_call(
12791355
DBUS_DESTINATION, DBUS_PATH, DBUS_FILECHOOSER_IFACE, "SaveFile");
12801356
DBusMessage_Guard query_guard(query);
1357+
1358+
DestroyFunc destroy;
12811359
AppendSaveFileQueryParams(
1282-
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName, parentWindow);
1360+
query, handle_token_ptr, filterList, filterCount, defaultPath, defaultName, parentWindow, destroy.fn, destroy.context);
12831361

12841362
DBusMessage* reply =
12851363
dbus_connection_send_with_reply_and_block(dbus_conn, query, DBUS_TIMEOUT_INFINITE, &err);
@@ -1383,6 +1461,27 @@ nfdresult_t NFD_DBus_GetVersion(dbus_uint32_t& outVersion) {
13831461
return NFD_OKAY;
13841462
}
13851463

1464+
#ifdef NFD_WAYLAND
1465+
void registry_handle_global(void* context, struct wl_registry* registry, uint32_t name, const char* interface, uint32_t version) {
1466+
if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
1467+
wayland_xdg_exporter_v1_name = name;
1468+
wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
1469+
}
1470+
}
1471+
1472+
void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
1473+
if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
1474+
zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
1475+
wayland_xdg_exporter_v1 = nullptr;
1476+
}
1477+
}
1478+
1479+
constexpr struct wl_registry_listener wayland_registry_listener = {
1480+
&registry_handle_global,
1481+
&registry_handle_global_remove
1482+
};
1483+
#endif
1484+
13861485
} // namespace
13871486

13881487
/* public */
@@ -1408,11 +1507,30 @@ nfdresult_t NFD_Init(void) {
14081507
dbus_unique_name = dbus_bus_get_unique_name(dbus_conn);
14091508
if (!dbus_unique_name) {
14101509
NFDi_SetError("Unable to get the unique name of our D-Bus connection.");
1510+
dbus_connection_unref(dbus_conn);
14111511
return NFD_ERROR;
14121512
}
1513+
#ifdef NFD_WAYLAND
1514+
// This might fail, but it is fine because the system might not actually have Wayland installed
1515+
wayland_display = wl_display_connect(nullptr);
1516+
if (wayland_display) {
1517+
wayland_registry = wl_display_get_registry(wayland_display);
1518+
wayland_xdg_exporter_v1 = nullptr;
1519+
// seems like registry can't be null
1520+
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
1521+
wl_display_roundtrip(wayland_display);
1522+
}
1523+
#endif
14131524
return NFD_OKAY;
14141525
}
14151526
void NFD_Quit(void) {
1527+
#ifdef NFD_WAYLAND
1528+
if (wayland_display) {
1529+
if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
1530+
wl_registry_destroy(wayland_registry);
1531+
wl_display_disconnect(wayland_display);
1532+
}
1533+
#endif
14161534
dbus_connection_unref(dbus_conn);
14171535
// Note: We do not free dbus_error since NFD_Init might set it.
14181536
// To avoid leaking memory, the caller should explicitly call NFD_ClearError after reading the

0 commit comments

Comments
 (0)