Skip to content

Commit

Permalink
New feature: Accept native parent window handle
Browse files Browse the repository at this point in the history
This is necessary for platforms to present the dialog properly, e.g. ensuring that the dialog never goes behind the parent window.
  • Loading branch information
btzy committed May 25, 2024
1 parent 38da115 commit edcfc7c
Show file tree
Hide file tree
Showing 11 changed files with 707 additions and 84 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,29 @@ jobs:
path: |
build/src/*
build/test/*
build-ubuntu-sdl2:

name: Ubuntu latest - GCC, ${{ matrix.portal.name }}, Static, SDL2
runs-on: ubuntu-latest

strategy:
matrix:
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)

steps:
- name: Checkout
uses: actions/checkout@v2
- name: Installing Dependencies
run: sudo apt-get update && sudo apt-get install ${{ matrix.portal.dep }} libsdl2-dev
- name: Configure
run: mkdir build && mkdir install && cd build && cmake -DCMAKE_INSTALL_PREFIX="../install" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-Wall -Wextra -Werror -pedantic" -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror -pedantic" -DNFD_PORTAL=${{ matrix.portal.flag }} -DNFD_APPEND_EXTENSION=OFF -DNFD_BUILD_SDL2_TESTS=ON ..
- name: Build
run: cmake --build build --target install
- name: Upload test binaries
uses: actions/upload-artifact@v2
with:
name: Ubuntu latest - GCC, ${{ matrix.portal.name }}, Static, SDL2
path: |
build/src/*
build/test/*
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ endif ()

option(BUILD_SHARED_LIBS "Build a shared library instead of static" OFF)
option(NFD_BUILD_TESTS "Build tests for nfd" ${nfd_ROOT_PROJECT})
option(NFD_BUILD_SDL2_TESTS "Build SDL2 tests for nfd" OFF)
option(NFD_INSTALL "Generate install target for nfd" ${nfd_ROOT_PROJECT})

set(nfd_PLATFORM Undefined)
Expand Down Expand Up @@ -48,6 +49,6 @@ endif()

add_subdirectory(src)

if(${NFD_BUILD_TESTS})
if(${NFD_BUILD_TESTS} OR ${NFD_BUILD_SDL2_TESTS})
add_subdirectory(test)
endif()
24 changes: 24 additions & 0 deletions src/include/nfd.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,19 +93,39 @@ typedef struct {
typedef nfdu8filteritem_t nfdnfilteritem_t;
#endif // _WIN32

// The native window handle type.
enum {
NFD_WINDOW_HANDLE_TYPE_UNSET = 0,
// Windows: handle is HWND (the Windows API typedefs this to void*)
NFD_WINDOW_HANDLE_TYPE_WINDOWS = 1,
// Cocoa: handle is NSWindow*
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
};
// The native window handle. If using a platform abstraction framework (e.g. SDL), this should be
// obtained using the corresponding NFD glue header (e.g. nfd_sdl.h).
typedef struct {
size_t type; // this is one of the values of the enum above
void* handle;
} nfdwindowhandle_t;

typedef size_t nfdversion_t;

typedef struct {
const nfdu8filteritem_t* filterList;
nfdfiltersize_t filterCount;
const nfdu8char_t* defaultPath;
nfdwindowhandle_t parentWindow;
} nfdopendialogu8args_t;

#ifdef _WIN32
typedef struct {
const nfdnfilteritem_t* filterList;
nfdfiltersize_t filterCount;
const nfdnchar_t* defaultPath;
nfdwindowhandle_t parentWindow;
} nfdopendialognargs_t;
#else
typedef nfdopendialogu8args_t nfdopendialognargs_t;
Expand All @@ -116,6 +136,7 @@ typedef struct {
nfdfiltersize_t filterCount;
const nfdu8char_t* defaultPath;
const nfdu8char_t* defaultName;
nfdwindowhandle_t parentWindow;
} nfdsavedialogu8args_t;

#ifdef _WIN32
Expand All @@ -124,18 +145,21 @@ typedef struct {
nfdfiltersize_t filterCount;
const nfdnchar_t* defaultPath;
const nfdnchar_t* defaultName;
nfdwindowhandle_t parentWindow;
} nfdsavedialognargs_t;
#else
typedef nfdsavedialogu8args_t nfdsavedialognargs_t;
#endif // _WIN32

typedef struct {
const nfdu8char_t* defaultPath;
nfdwindowhandle_t parentWindow;
} nfdpickfolderu8args_t;

#ifdef _WIN32
typedef struct {
const nfdnchar_t* defaultPath;
nfdwindowhandle_t parentWindow;
} nfdpickfoldernargs_t;
#else
typedef nfdpickfolderu8args_t nfdpickfoldernargs_t;
Expand Down
69 changes: 69 additions & 0 deletions src/include/nfd_glfw.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo
This header contains a function to convert a GLFW window handle to a native window handle for
passing to NFDe.
*/

#ifndef _NFD_GLFW_H
#define _NFD_GLFW_H

#include <GLFW/glfw3native.h>
#include <nfd.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/**
* Converts a GLFW window handle to a native window handle that can be passed to NFDe.
* @param sdlWindow The GLFW window handle.
* @param[out] nativeWindow The output native window handle, populated if and only if this function
* returns true.
* @return Either true to indicate success, or false to indicate failure. It is intended that
* users ignore the error and simply pass a value-initialized nfdwindowhandle_t to NFDe if this
* function fails. */
inline bool NFD_GetNativeWindowFromGLFWWindow(GLFWwindow* glfwWindow,
nfdwindowhandle_t* nativeWindow) {
GLFWerrorfun* oldCallback = glfwSetErrorCallback(NULL);
bool success = false;
#if defined(GLFW_EXPOSE_NATIVE_WIN32)
if (!success) {
const HWND hwnd = glfwGetWin32Window(glfwWindow);
if (hwnd) {
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_WINDOWS, hwnd};
success = true;
}
}
#endif
#if defined(GLFW_EXPOSE_NATIVE_COCOA)
if (!success) {
NSWindow* const cocoa_window = glfwGetCocoaWindow(glfwWindow);
if (cocoa_window) {
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_COCOA, cocoa_window};
success = true;
}
}
#endif
#if defined(NFD_WINDOW_HANDLE_TYPE_X11)
if (!success) {
const Window x11_window = glfwGetX11Window(glfwWindow);
if (x11_window != None) {
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_X11, x11_window};
success = true;
}
}
#endif
glfwSetErrorCallback(oldCallback);
return success;
}

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // _NFD_GLFW_H
66 changes: 66 additions & 0 deletions src/include/nfd_sdl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Native File Dialog Extended
Repository: https://github.com/btzy/nativefiledialog-extended
License: Zlib
Authors: Bernard Teo
This header contains a function to convert an SDL window handle to a native window handle for
passing to NFDe.
This is meant to be used with SDL2, but if there are incompatibilities with future SDL versions,
we can conditionally compile based on SDL_MAJOR_VERSION.
*/

#ifndef _NFD_SDL_H
#define _NFD_SDL_H

#include <SDL_error.h>
#include <SDL_syswm.h>
#include <nfd.h>

#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

/**
* Converts an SDL window handle to a native window handle that can be passed to NFDe.
* @param sdlWindow The SDL window handle.
* @param[out] nativeWindow The output native window handle, populated if and only if this function
* returns true.
* @return Either true to indicate success, or false to indicate failure. If false is returned,
* you can call SDL_GetError() for more information. However, it is intended that users ignore the
* error and simply pass a value-initialized nfdwindowhandle_t to NFDe if this function fails. */
inline bool NFD_GetNativeWindowFromSDLWindow(SDL_Window* sdlWindow,
nfdwindowhandle_t* nativeWindow) {
SDL_GetWindowWMInfo info;
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(window, &info)) {
return false;
}
switch (info.subsystem) {
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
case SDL_SYSWM_WINDOWS:
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_WINDOWS, info.info.win.window};
return true;
#endif
#if defined(SDL_VIDEO_DRIVER_COCOA)
case SDL_SYSWM_COCOA:
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_COCOA, info.info.cocoa.window};
return true;
#endif
#if defined(SDL_VIDEO_DRIVER_X11)
case SDL_SYSWM_X11:
*nativeWindow = {NFD_WINDOW_HANDLE_TYPE_X11, info.info.x11.window};
return true;
#endif
default:
SDL_SetError("Unsupported native window type.");
return false;
}
}

#ifdef __cplusplus
}
#endif // __cplusplus

#endif // _NFD_SDL_H
42 changes: 37 additions & 5 deletions src/nfd_cocoa.m
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,13 @@ static nfdresult_t CopyUtf8String(const char* utf8Str, nfdnchar_t** out) {
return NFD_ERROR;
}

static NSWindow* GetNativeWindowHandle(const nfdwindowhandle_t* parentWindow) {
if (parentWindow->type != NFD_WINDOW_HANDLE_TYPE_COCOA) {
return NULL;
}
return (NSWindow*)parentWindow->handle;
}

/* public */

const char* NFD_GetError(void) {
Expand Down Expand Up @@ -230,7 +237,12 @@ nfdresult_t NFD_OpenDialogN_With_Impl(nfdversion_t version,

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSWindow* keyWindow = GetNativeWindowHandle(&args->parentWindow);
if (keyWindow) {
[keyWindow makeKeyAndOrderFront:nil];
} else {
keyWindow = [[NSApplication sharedApplication] keyWindow];
}

NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
Expand Down Expand Up @@ -285,7 +297,12 @@ nfdresult_t NFD_OpenDialogMultipleN_With_Impl(nfdversion_t version,

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSWindow* keyWindow = GetNativeWindowHandle(&args->parentWindow);
if (keyWindow) {
[keyWindow makeKeyAndOrderFront:nil];
} else {
keyWindow = [[NSApplication sharedApplication] keyWindow];
}

NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
Expand Down Expand Up @@ -347,7 +364,12 @@ nfdresult_t NFD_SaveDialogN_With_Impl(nfdversion_t version,

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSWindow* keyWindow = GetNativeWindowHandle(&args->parentWindow);
if (keyWindow) {
[keyWindow makeKeyAndOrderFront:nil];
} else {
keyWindow = [[NSApplication sharedApplication] keyWindow];
}

NSSavePanel* dialog = [NSSavePanel savePanel];
[dialog setExtensionHidden:NO];
Expand Down Expand Up @@ -404,7 +426,12 @@ nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version,

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSWindow* keyWindow = GetNativeWindowHandle(&args->parentWindow);
if (keyWindow) {
[keyWindow makeKeyAndOrderFront:nil];
} else {
keyWindow = [[NSApplication sharedApplication] keyWindow];
}

NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:NO];
Expand Down Expand Up @@ -451,7 +478,12 @@ nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version,

nfdresult_t result = NFD_CANCEL;
@autoreleasepool {
NSWindow* keyWindow = [[NSApplication sharedApplication] keyWindow];
NSWindow* keyWindow = GetNativeWindowHandle(&args->parentWindow);
if (keyWindow) {
[keyWindow makeKeyAndOrderFront:nil];
} else {
keyWindow = [[NSApplication sharedApplication] keyWindow];
}

NSOpenPanel* dialog = [NSOpenPanel openPanel];
[dialog setAllowsMultipleSelection:YES];
Expand Down
Loading

0 comments on commit edcfc7c

Please sign in to comment.