Skip to content

Commit 74a6900

Browse files
committed
Add GLFW3 test
1 parent 99afd10 commit 74a6900

10 files changed

Lines changed: 444 additions & 55 deletions

File tree

.github/workflows/cmake.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,35 @@ jobs:
251251
build/src/*
252252
build/test/*
253253
254+
build-ubuntu-glfw3:
255+
256+
name: Ubuntu latest - GCC, ${{ matrix.portal.name }}, ${{ matrix.wayland.name }}, Static, GLFW3
257+
runs-on: ubuntu-latest
258+
259+
strategy:
260+
matrix:
261+
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)
262+
wayland: [ {flag: OFF, dep: , name: NoWayland}, {flag: ON, dep: libwayland-dev libwayland-bin, name: Wayland} ]
263+
264+
steps:
265+
- name: Checkout
266+
uses: actions/checkout@v4
267+
with:
268+
submodules: true
269+
- name: Install Dependencies
270+
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }} ${{ matrix.wayland.dep }} libglfw3-dev
271+
- name: Configure
272+
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_WAYLAND=${{ matrix.wayland.flag }} -DNFD_APPEND_EXTENSION=OFF -DNFD_BUILD_TESTS=OFF -DNFD_BUILD_GLFW3_TESTS=ON ..
273+
- name: Build
274+
run: cmake --build build --target install
275+
- name: Upload test binaries
276+
uses: actions/upload-artifact@v4
277+
with:
278+
name: Ubuntu latest - GCC, ${{ matrix.portal.name }}, ${{ matrix.wayland.name }}, Static, GLFW3
279+
path: |
280+
build/src/*
281+
build/test/*
282+
254283
build-macos-sdl2:
255284

256285
name: MacOS latest - Clang, Static, SDL2
@@ -273,6 +302,28 @@ jobs:
273302
build/src/*
274303
build/test/*
275304
305+
build-macos-glfw3:
306+
307+
name: MacOS latest - Clang, Static, GLFW3
308+
runs-on: macos-latest
309+
310+
steps:
311+
- name: Checkout
312+
uses: actions/checkout@v4
313+
- name: Install Dependencies
314+
run: brew install glfw
315+
- name: Configure
316+
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Wshadow -Werror -pedantic" -DNFD_BUILD_TESTS=OFF -DNFD_BUILD_GLFW3_TESTS=ON ..
317+
- name: Build
318+
run: cmake --build build --target install
319+
- name: Upload test binaries
320+
uses: actions/upload-artifact@v4
321+
with:
322+
name: MacOS latest - Clang, Static, GLFW3
323+
path: |
324+
build/src/*
325+
build/test/*
326+
276327
build-windows-sdl2:
277328

278329
name: Windows latest - MSVC, Static, SDL2
@@ -296,3 +347,27 @@ jobs:
296347
path: |
297348
build/src/Release/*
298349
build/test/Release/*
350+
351+
build-windows-glfw3:
352+
353+
name: Windows latest - MSVC, Static, GLFW3
354+
runs-on: windows-latest
355+
356+
steps:
357+
- name: Checkout
358+
uses: actions/checkout@v4
359+
- name: Install pkgconfiglite
360+
run: choco install pkgconfiglite
361+
- name: Install Dependencies
362+
run: vcpkg integrate install && vcpkg install glfw3 --triplet=x64-windows-release
363+
- name: Configure
364+
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_TOOLCHAIN_FILE="C:/vcpkg/scripts/buildsystems/vcpkg.cmake" -DVCPKG_TARGET_TRIPLET="x64-windows-release" -DNFD_BUILD_TESTS=OFF -DNFD_BUILD_GLFW3_TESTS=ON ..
365+
- name: Build
366+
run: cmake --build build --target install --config Release
367+
- name: Upload test binaries
368+
uses: actions/upload-artifact@v4
369+
with:
370+
name: Windows latest - MSVC, Static, GLFW3
371+
path: |
372+
build/src/Release/*
373+
build/test/Release/*

3ps/wayland-protocols

Submodule wayland-protocols updated from 122a47a to 8822301

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ endif ()
99
option(BUILD_SHARED_LIBS "Build a shared library instead of static" OFF)
1010
option(NFD_BUILD_TESTS "Build tests for nfd" ${nfd_ROOT_PROJECT})
1111
option(NFD_BUILD_SDL2_TESTS "Build SDL2 tests for nfd" OFF)
12+
option(NFD_BUILD_GLFW3_TESTS "Build GLFW3 tests for nfd" OFF)
1213
option(NFD_INSTALL "Generate install target for nfd" ${nfd_ROOT_PROJECT})
1314

1415
set(nfd_PLATFORM Undefined)
@@ -47,6 +48,6 @@ endif()
4748

4849
add_subdirectory(src)
4950

50-
if(${NFD_BUILD_TESTS} OR ${NFD_BUILD_SDL2_TESTS})
51+
if(${NFD_BUILD_TESTS} OR ${NFD_BUILD_SDL2_TESTS} OR ${NFD_BUILD_GLFW3_TESTS})
5152
add_subdirectory(test)
5253
endif()

README.md

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
# Native File Dialog Extended
33

4-
![GitHub Actions](https://github.com/btzy/nativefiledialog-extended/workflows/build/badge.svg)
4+
![GitHub Actions](https://github.com/btzy/nativefiledialog-extended/workflows/build/badge.svg?branch=master&event=push)
55

66
A small C library that portably invokes native file open, folder select and file save dialogs. Write dialog code once and have it pop up native dialogs on all supported platforms. Avoid linking large dependencies like wxWidgets and Qt.
77

@@ -298,6 +298,32 @@ Macros that might be defined by `nfd.h`:
298298
299299
NFDe is known to work with SDL2 and GLFW, and should also work with other platform abstraction framworks. This section explains how to use NFDe properly with such frameworks.
300300
301+
### Initialization order
302+
303+
You should initialize NFDe _after_ initializing the framework, and probably should deinitialize NFDe _before_ deinitializing the framework. This is because some frameworks expect to be initialized on a "clean slate", and they may configure the system in a different way from NFDe. `NFD_Init` is generally very careful not to disrupt the existing configuration unless necessary, and `NFD_Quit` restores the configuration back exactly to what it was before initialization.
304+
305+
An example with SDL2:
306+
307+
```
308+
// Initialize SDL2 first
309+
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
310+
// display some error here
311+
}
312+
313+
// Then initialize NFDe
314+
if (NFD_Init() != NFD_OKAY) {
315+
// display some error here
316+
}
317+
318+
/*
319+
Your main program goes here
320+
*/
321+
322+
NFD_Quit(); // deinitialize NFDe first
323+
324+
SDL_Quit(); // Then deinitialize SDL2
325+
```
326+
301327
### Parent window handle
302328

303329
The `parentWindow` argument allows the user to give the dialog a parent.
@@ -308,7 +334,7 @@ Win32 (Windows), Cocoa (macOS), X11 (Linux), and Wayland (Linux) windows are sup
308334

309335
If using SDL2, include `<nfd_sdl2.h>` and do the following:
310336

311-
Call the following function once, after you create your first SDL2 window (usually with `SDL_CreateWindow()`) but before opening any file dialogs, to tell NFDe the `wl_display` your application is using (this is a no-op if your application isn't using Wayland):
337+
Call the following function once, after you create your first SDL2 window (usually with `SDL_CreateWindow()`) but before opening any file dialogs, to tell NFDe the `wl_display` your application is using (this function does nothing if your application isn't using Wayland):
312338
```C
313339
NFD_SetDisplayPropertiesFromSDLWindow(sdlWindow /* SDL_Window* */);
314340
```
@@ -322,16 +348,27 @@ See `test_sdl.c` for an example.
322348

323349
#### GLFW3
324350

325-
If using GLFW3, define the appropriate `GLFW_EXPOSE_NATIVE_*` macros described on the [GLFW native access page](https://www.glfw.org/docs/latest/group__native.html), and then include `<nfd_glfw3.h>` and call the following function to set the parent window handle:
351+
If using GLFW3, define the appropriate `GLFW_EXPOSE_NATIVE_*` macros as described on the [GLFW native access page](https://www.glfw.org/docs/latest/group__native.html), and then include `<nfd_glfw3.h>` and do the following:
352+
353+
Call the following function once, after `glfwInit()` and `NFD_Init()` but before opening any file dialogs, to tell NFDe the `wl_display` your application is using (this function does nothing if your application isn't using Wayland):
354+
```C
355+
NFD_SetDisplayPropertiesFromGLFW();
356+
```
357+
358+
Each time you want to show a dialog, call the following function to retrieve the parent window handle and set the corresponding argument:
326359
```C
327360
NFD_GetNativeWindowFromGLFWWindow(glfwWindow /* GLFWwindow* */, &args.parentWindow);
328361
```
329362
363+
See `test_glfw.c` for an example.
364+
365+
*Note: GLFW version < 3.4 does not support dynamically selecting a display server at runtime, meaning that it will support either X11 or Wayland, but not both. Make sure the `GLFW_EXPOSE_NATIVE_*` macros you define are indeed available on your GLFW library.*
366+
330367
#### Others
331368
332-
If you are using another platform abstraction framework, or not using any such framework, you can do the following:
369+
If using another platform abstraction framework or not using any such framework, do the following:
333370
334-
If you are using Wayland, call the following function once, after connecting to the Wayland compositor (thereby obtaining a `wl_display*`) but before opening any file dialogs:
371+
Figure out if you are using Wayland in your application code, and if so, call the following function to tell NFDe the `wl_display` you are using:
335372
```C
336373
NFD_SetWaylandDisplay(display /* wl_display* */);
337374
```
@@ -344,41 +381,15 @@ To make a window (in this case the file dialog) stay above another window, we ne
344381

345382
#### Why is Wayland special?
346383

347-
Linux has two differences when compared with Windows and macOS: Linux applications open a connection with a display server (X11 or Wayland) to show their windows, and windows are owned by this connection. It is possible to open multiple connections at the same time (including multiple connections to the same display server), and windows are not shared between those connections. GTK needs to use a connection opened by itself, and so it opens a new connection if it hasn't previously opened a connection (which will be the case if your application doesn't create its own GTK windows). Using portals essentially causes the file dialog to be shown by a separate helper process, which also opens its own connection. In either case, the window handle needs to be passed to a different connection. On X11, window handles are global identifiers that can be used as-is by another connection, but on Wayland, the handles need to be _exported_ to a string and then _imported_ by the receiving connection, and performing the export operation requires the owner's Wayland display handle.
348-
349-
### Initialization order
350-
351-
You should initialize NFDe _after_ initializing the framework, and probably should deinitialize NFDe _before_ deinitializing the framework. This is because some frameworks expect to be initialized on a "clean slate", and they may configure the system in a different way from NFDe. `NFD_Init` is generally very careful not to disrupt the existing configuration unless necessary, and `NFD_Quit` restores the configuration back exactly to what it was before initialization.
352-
353-
An example with SDL2:
354-
355-
```
356-
// Initialize SDL2 first
357-
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
358-
// display some error here
359-
}
360-
361-
// Then initialize NFDe
362-
if (NFD_Init() != NFD_OKAY) {
363-
// display some error here
364-
}
365-
366-
/*
367-
Your main program goes here
368-
*/
369-
370-
NFD_Quit(); // deinitialize NFDe first
371-
372-
SDL_Quit(); // Then deinitialize SDL2
373-
```
384+
Linux has two differences when compared with Windows and macOS: (1) Linux applications open a connection with a display server (X11 or Wayland) to show their windows, and (2) windows are owned by this connection. It is possible to open multiple connections at the same time (including multiple connections to the same display server), and windows are not shared between those connections. GTK needs to use a connection opened by itself, and so it opens a new connection if it hasn't previously opened a connection (which will be the case if your application doesn't use GTK on its own). Portals work by opening the file dialog in a separate helper process, which also opens its own connection. In either case, the window handle needs to be passed to a different connection. On X11, window handles are global identifiers that can be used as-is by another connection, but on Wayland, the handles need to be _exported_ to a string and then _imported_ by the receiving connection, and performing the export operation requires the owner's Wayland display handle.
374385

375386
## Using xdg-desktop-portal on Linux
376387

377388
On Linux, you can use the portal implementation instead of GTK, which will open the "native" file chooser selected by the OS or customized by the user. The user must have `xdg-desktop-portal` and a suitable backend installed (this comes pre-installed with most common desktop distros), otherwise `NFD_ERROR` will be returned.
378389

379390
To use the portal implementation, add `-DNFD_PORTAL=ON` to the build command.
380391

381-
*Note: The folder picker is only supported on org.freedesktop.portal.FileChooser interface version >= 3, which corresponds to xdg-desktop-portal version >= 1.7.1. `NFD_PickFolder()` will query the interface version at runtime, and return `NFD_ERROR` if the version is too low.
392+
*Note: The folder picker is only supported on org.freedesktop.portal.FileChooser interface version >= 3, which corresponds to xdg-desktop-portal version >= 1.7.1. `NFD_PickFolder()` will query the interface version at runtime, and return `NFD_ERROR` if the version is too low.*
382393

383394
### What is a portal?
384395

src/include/nfd_glfw3.h

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,90 @@ extern "C" {
2424
#endif // __cplusplus
2525

2626
/**
27-
* Converts a GLFW window handle to a native window handle that can be passed to NFDe.
28-
* @param sdlWindow The GLFW window handle.
29-
* @param[out] nativeWindow The output native window handle, populated if and only if this function
30-
* returns true.
31-
* @return Either true to indicate success, or false to indicate failure. It is intended that
27+
* Sets the wayland display if the process is running under Wayland, otherwise does nothing.
28+
* @return Either true to indicate success, or false to indicate failure. If false is returned,
29+
* you can call SDL_GetError() for more information.
30+
*/
31+
NFD_INLINE bool NFD_SetDisplayPropertiesFromGLFW(void) {
32+
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
33+
#if defined(GLFW_VERSION_MAJOR) && defined(GLFW_VERSION_MINOR) && \
34+
(GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4))
35+
// GLFW 3.4+ has glfwGetPlatform()
36+
const int platform = glfwGetPlatform();
37+
if (!platform) {
38+
return false;
39+
}
40+
if (platform != GLFW_PLATFORM_WAYLAND) {
41+
return true;
42+
}
43+
struct wl_display* wayland_display = glfwGetWaylandDisplay();
44+
if (!wayland_display) {
45+
return false;
46+
}
47+
return NFD_SetWaylandDisplay(wayland_display) == NFD_OKAY;
48+
#else
49+
const GLFWerrorfun oldCallback = glfwSetErrorCallback(NULL);
50+
bool success = true;
51+
struct wl_display* wayland_display = glfwGetWaylandDisplay();
52+
if (wayland_display) {
53+
success = NFD_SetWaylandDisplay(wayland_display) == NFD_OKAY;
54+
}
55+
glfwSetErrorCallback(oldCallback);
56+
return success;
57+
#endif
58+
#else
59+
return true;
60+
#endif
61+
}
62+
63+
/**
64+
* Converts a GLFW window handle to a native window handle that can be passed to NFDe.
65+
* @param sdlWindow The GLFW window handle.
66+
* @param[out] nativeWindow The output native window handle, populated if and only if this function
67+
* returns true.
68+
* @return Either true to indicate success, or false to indicate failure. It is intended that
3269
* users ignore the error and simply pass a value-initialized nfdwindowhandle_t to NFDe if this
3370
* function fails. */
3471
NFD_INLINE bool NFD_GetNativeWindowFromGLFWWindow(GLFWwindow* glfwWindow,
3572
nfdwindowhandle_t* nativeWindow) {
36-
GLFWerrorfun oldCallback = glfwSetErrorCallback(NULL);
73+
#if defined(GLFW_VERSION_MAJOR) && defined(GLFW_VERSION_MINOR) && \
74+
(GLFW_VERSION_MAJOR > 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR >= 4))
75+
// GLFW 3.4+ has glfwGetPlatform()
76+
const int platform = glfwGetPlatform();
77+
switch (platform) {
78+
case 0:
79+
return false;
80+
#if defined(GLFW_EXPOSE_NATIVE_WIN32)
81+
case GLFW_PLATFORM_WIN32:
82+
nativeWindow->type = NFD_WINDOW_HANDLE_TYPE_WINDOWS;
83+
nativeWindow->handle = (void*)glfwGetWin32Window(glfwWindow);
84+
return true;
85+
#endif
86+
#if defined(GLFW_EXPOSE_NATIVE_COCOA)
87+
case GLFW_PLATFORM_COCOA:
88+
nativeWindow->type = NFD_WINDOW_HANDLE_TYPE_COCOA;
89+
nativeWindow->handle = (void*)glfwGetCocoaWindow(glfwWindow);
90+
return true;
91+
#endif
92+
#if defined(GLFW_EXPOSE_NATIVE_X11)
93+
case GLFW_PLATFORM_X11:
94+
nativeWindow->type = NFD_WINDOW_HANDLE_TYPE_X11;
95+
nativeWindow->handle = (void*)glfwGetX11Window(glfwWindow);
96+
return true;
97+
#endif
98+
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
99+
case GLFW_PLATFORM_WAYLAND:
100+
nativeWindow->type = NFD_WINDOW_HANDLE_TYPE_WAYLAND;
101+
nativeWindow->handle = (void*)glfwGetWaylandWindow(glfwWindow);
102+
return true;
103+
#endif
104+
default:
105+
(void)glfwWindow;
106+
(void)nativeWindow;
107+
return true;
108+
}
109+
#else
110+
const GLFWerrorfun oldCallback = glfwSetErrorCallback(NULL);
37111
bool success = false;
38112
#if defined(GLFW_EXPOSE_NATIVE_WIN32)
39113
if (!success) {
@@ -66,15 +140,18 @@ NFD_INLINE bool NFD_GetNativeWindowFromGLFWWindow(GLFWwindow* glfwWindow,
66140
}
67141
#endif
68142
#if defined(GLFW_EXPOSE_NATIVE_WAYLAND)
69-
// For now we don't support Wayland, but we intend to support it eventually.
70-
// Silence the warnings.
71-
{
72-
(void)glfwWindow;
73-
(void)nativeWindow;
143+
if (!success) {
144+
const struct wl_surface* wayland_window = glfwGetWaylandWindow(glfwWindow);
145+
if (wayland_window) {
146+
nativeWindow->type = NFD_WINDOW_HANDLE_TYPE_WAYLAND;
147+
nativeWindow->handle = (void*)wayland_window;
148+
success = true;
149+
}
74150
}
75151
#endif
76152
glfwSetErrorCallback(oldCallback);
77153
return success;
154+
#endif
78155
}
79156

80157
#undef NFD_INLINE

src/include/nfd_sdl2.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,22 @@ extern "C" {
2626
#define NFD_INLINE static inline
2727
#endif // __cplusplus
2828

29+
/**
30+
* Sets the wayland display if the process is running under Wayland, otherwise does nothing.
31+
* @param sdlWindow The SDL window handle.
32+
* @return Either true to indicate success, or false to indicate failure. If false is returned,
33+
* you can call SDL_GetError() for more information.
34+
*/
2935
NFD_INLINE bool NFD_SetDisplayPropertiesFromSDLWindow(SDL_Window* sdlWindow) {
3036
#if defined(SDL_VIDEO_DRIVER_WAYLAND)
3137
SDL_SysWMinfo info;
3238
SDL_VERSION(&info.version);
33-
if (!SDL_GetWindowWMInfo(sdlWindow, &info) || info.subsystem != SDL_SYSWM_WAYLAND) {
39+
if (!SDL_GetWindowWMInfo(sdlWindow, &info)) {
3440
return false;
3541
}
42+
if (info.subsystem != SDL_SYSWM_WAYLAND) {
43+
return true;
44+
}
3645
return NFD_SetWaylandDisplay(info.info.wl.display) == NFD_OKAY;
3746
#else
3847
(void)sdlWindow;
@@ -41,11 +50,11 @@ NFD_INLINE bool NFD_SetDisplayPropertiesFromSDLWindow(SDL_Window* sdlWindow) {
4150
}
4251

4352
/**
44-
* Converts an SDL window handle to a native window handle that can be passed to NFDe.
45-
* @param sdlWindow The SDL window handle.
46-
* @param[out] nativeWindow The output native window handle, populated if and only if this function
47-
* returns true.
48-
* @return Either true to indicate success, or false to indicate failure. If false is returned,
53+
* Converts an SDL window handle to a native window handle that can be passed to NFDe.
54+
* @param sdlWindow The SDL window handle.
55+
* @param[out] nativeWindow The output native window handle, populated if and only if this function
56+
* returns true.
57+
* @return Either true to indicate success, or false to indicate failure. If false is returned,
4958
* you can call SDL_GetError() for more information. However, it is intended that users ignore the
5059
* error and simply pass a value-initialized nfdwindowhandle_t to NFDe if this function fails. */
5160
NFD_INLINE bool NFD_GetNativeWindowFromSDLWindow(SDL_Window* sdlWindow,

0 commit comments

Comments
 (0)