Skip to content

Commit 8292193

Browse files
committed
Add GLFW3 test
1 parent 99afd10 commit 8292193

9 files changed

Lines changed: 378 additions & 23 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/*

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: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,11 +322,20 @@ See `test_sdl.c` for an example.
322322
323323
#### GLFW3
324324
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:
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 do the following:
326+
327+
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 is a no-op if your application isn't using Wayland):
328+
```C
329+
NFD_SetDisplayPropertiesFromGLFW();
330+
```
331+
332+
Each time you want to show a dialog, call the following function to retrieve the parent window handle and set the corresponding argument:
326333
```C
327334
NFD_GetNativeWindowFromGLFWWindow(glfwWindow /* GLFWwindow* */, &args.parentWindow);
328335
```
329336
337+
See `test_glfw.c` for an example.
338+
330339
#### Others
331340
332341
If you are using another platform abstraction framework, or not using any such framework, you can do the following:
@@ -344,7 +353,7 @@ To make a window (in this case the file dialog) stay above another window, we ne
344353

345354
#### Why is Wayland special?
346355

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.
356+
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). 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.
348357

349358
### Initialization order
350359

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,

test/CMakeLists.txt

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ endif()
3434
if(${NFD_BUILD_SDL2_TESTS})
3535
find_package(PkgConfig REQUIRED)
3636
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_ttf)
37-
if(WIN32)
38-
add_executable(test_sdl2 WIN32 test_sdl.c test_sdl.manifest)
37+
if(nfd_PLATFORM STREQUAL PLATFORM_WIN32)
38+
add_executable(test_sdl2 WIN32 test_sdl.c test.manifest)
3939
else()
4040
add_executable(test_sdl2 test_sdl.c)
4141
endif()
@@ -44,3 +44,38 @@ if(${NFD_BUILD_SDL2_TESTS})
4444
target_link_libraries(test_sdl2 PRIVATE ${SDL2_LINK_LIBRARIES})
4545
target_compile_options(test_sdl2 PUBLIC ${SDL2_CFLAGS_OTHER})
4646
endif()
47+
48+
if(${NFD_BUILD_GLFW3_TESTS})
49+
find_package(PkgConfig REQUIRED)
50+
pkg_check_modules(GLFW3 REQUIRED glfw3)
51+
if(nfd_PLATFORM STREQUAL PLATFORM_WIN32)
52+
add_executable(test_glfw3 WIN32 test_glfw.c test.manifest)
53+
else()
54+
add_executable(test_glfw3 test_glfw.c)
55+
endif()
56+
target_link_libraries(test_glfw3 PRIVATE nfd)
57+
target_include_directories(test_glfw3 PRIVATE ${GLFW3_INCLUDE_DIRS})
58+
target_link_libraries(test_glfw3 PRIVATE ${GLFW3_LINK_LIBRARIES})
59+
target_compile_options(test_glfw3 PUBLIC ${GLFW3_CFLAGS_OTHER})
60+
if(nfd_PLATFORM STREQUAL PLATFORM_WIN32)
61+
target_link_libraries(test_glfw3 PRIVATE opengl32)
62+
target_compile_definitions(test_glfw3 PRIVATE GLFW_EXPOSE_NATIVE_WIN32)
63+
elseif(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
64+
find_package(OpenGL REQUIRED)
65+
target_include_directories(test_glfw3 PRIVATE ${OPENGL_INCLUDE_DIRS})
66+
target_link_libraries(test_glfw3 PRIVATE ${OPENGL_LIBRARIES})
67+
target_compile_definitions(test_glfw3 PRIVATE GLFW_EXPOSE_NATIVE_COCOA)
68+
target_compile_definitions(test_glfw3 PRIVATE GL_SILENCE_DEPRECATION) # OpenGL is deprecated on macOS
69+
elseif(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
70+
pkg_check_modules(OPENGL REQUIRED gl)
71+
target_include_directories(test_glfw3 PRIVATE ${OPENGL_INCLUDE_DIRS})
72+
target_link_libraries(test_glfw3 PRIVATE ${OPENGL_LINK_LIBRARIES})
73+
target_compile_options(test_glfw3 PUBLIC ${OPENGL_CFLAGS_OTHER})
74+
if(${NFD_X11})
75+
target_compile_definitions(test_glfw3 PRIVATE GLFW_EXPOSE_NATIVE_X11)
76+
endif()
77+
if(${NFD_WAYLAND})
78+
target_compile_definitions(test_glfw3 PRIVATE GLFW_EXPOSE_NATIVE_WAYLAND)
79+
endif()
80+
endif()
81+
endif()

0 commit comments

Comments
 (0)