Skip to content

Commit

Permalink
Add GTK4 support
Browse files Browse the repository at this point in the history
  • Loading branch information
btzy committed Apr 30, 2021
1 parent 300203a commit d883012
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 23 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ set (CMAKE_CXX_STANDARD 17)
add_subdirectory(src)

option(NFD_BUILD_TESTS "Build tests for nfd" OFF)
if(${NFD_BUILD_TESTS})
if(NFD_BUILD_TESTS)
add_subdirectory(test)
endif()
27 changes: 23 additions & 4 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,27 @@ endif()

if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
message("Using GTK version: ${GTK3_VERSION}")
set(NFD_GTK_VERSION "" CACHE STRING "GTK version for Linux builds ('3' or '4')")
set_property(CACHE NFD_GTK_VERSION PROPERTY STRINGS "" 3 4)
# For Linux, we support both GTK3 and GTK4.
# If NFD_GTK_VERSION is not explicitly set, then we take one that is available.
# Otherwise, we find the version that the user wants.
if(NFD_GTK_VERSION STREQUAL "")
pkg_search_module(GTK REQUIRED gtk+-3.0 gtk4)
if(DEFINED GTK_gtk+-3.0_VERSION)
set(GTK_VERSION ${GTK_gtk+-3.0_VERSION})
elseif(DEFINED GTK_gtk4_VERSION)
set(GTK_VERSION ${GTK_gtk4_VERSION})
endif()
elseif(NFD_GTK_VERSION STREQUAL 3)
pkg_check_modules(GTK REQUIRED gtk+-3.0)
elseif(NFD_GTK_VERSION STREQUAL 4)
pkg_check_modules(GTK REQUIRED gtk4)
else()
message(FATAL_ERROR "Unsupported GTK version: ${NFD_GTK_VERSION}")
endif()

message("Using GTK version: ${GTK_VERSION}")
list(APPEND SOURCE_FILES nfd_gtk.cpp)
endif()

Expand All @@ -32,9 +51,9 @@ target_include_directories(${TARGET_NAME}

if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
target_include_directories(${TARGET_NAME}
PRIVATE ${GTK3_INCLUDE_DIRS})
PRIVATE ${GTK_INCLUDE_DIRS})
target_link_libraries(${TARGET_NAME}
PRIVATE ${GTK3_LIBRARIES})
PRIVATE ${GTK_LIBRARIES})
endif()

if(nfd_PLATFORM STREQUAL PLATFORM_MACOS)
Expand Down
128 changes: 110 additions & 18 deletions src/nfd_gtk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
Authors: Bernard Teo, Michael Labbe
Note: We do not check for malloc failure on Linux - Linux overcommits memory!
Note: The GTK4 implementation does not distinguish between local and network files,
so it is possible for the file picker to return a network URI instead of a local filename.
*/

#include <assert.h>
#include <gtk/gtk.h>
#if defined(GDK_WINDOWING_X11)
#if GTK_MAJOR_VERSION == 3
#include <gdk/gdkx.h>
#elif GTK_MAJOR_VERSION == 4
#include <gdk/x11/gdkx.h>
#endif
#endif
#include <stddef.h>
#include <stdio.h>
Expand All @@ -19,6 +25,8 @@

#include "nfd.h"

#define UNSUPPORTED_ERROR() static_assert(false, "Unsupported GTK version, this is an NFD bug.")

namespace {

template <typename T>
Expand All @@ -37,6 +45,14 @@ struct FreeCheck_Guard {
}
};

template <typename T>
struct GUnref_Guard {
T* data;
GUnref_Guard(T* object) noexcept : data(object) {}
~GUnref_Guard() { g_object_unref(data); }
T* get() const noexcept { return data; }
};

/* current error */
const char* g_errorstr = nullptr;

Expand Down Expand Up @@ -278,33 +294,74 @@ Pair_GtkFileFilter_FileExtension* AddFiltersToDialogWithMap(GtkFileChooser* choo
return map;
}

void SetDefaultPath(GtkFileChooser* chooser, const char* defaultPath) {
if (!defaultPath || !*defaultPath) return;
/*
Note: GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
/* GTK+ manual recommends not specifically setting the default path.
We do it anyway in order to be consistent across platforms.
If consistency with the native OS is preferred,
then this function should be made a no-op.
*/
#if GTK_MAJOR_VERSION == 3
nfdresult_t SetDefaultPath(GtkFileChooser* chooser, const char* defaultPath) {
if (!defaultPath || !*defaultPath) return NFD_OKAY;

If consistency with the native OS is preferred, this is the line
to comment out. -ml */
gtk_file_chooser_set_current_folder(chooser, defaultPath);
return NFD_OKAY;
}
#elif GTK_MAJOR_VERSION == 4
nfdresult_t SetDefaultPath(GtkFileChooser* chooser, const char* defaultPath) {
if (!defaultPath || !*defaultPath) return NFD_OKAY;

GUnref_Guard<GFile> file(g_file_new_for_path(defaultPath));

if (!gtk_file_chooser_set_current_folder(chooser, file.get(), nullptr)) {
NFDi_SetError("Failed to set default path.");
return NFD_ERROR;
}
return NFD_OKAY;
}
#endif

void SetDefaultName(GtkFileChooser* chooser, const char* defaultName) {
if (!defaultName || !*defaultName) return;

gtk_file_chooser_set_current_name(chooser, defaultName);
}

char* GetSingleFileName(GtkFileChooser* chooser) {
#if GTK_MAJOR_VERSION == 3
return gtk_file_chooser_get_filename(chooser);
#elif GTK_MAJOR_VERSION == 4
GUnref_Guard<GFile> file(gtk_file_chooser_get_file(chooser));
return g_file_get_path(file.get());
#else
UNSUPPORTED_ERROR();
#endif
}

void WaitForCleanup() {
#if GTK_MAJOR_VERSION == 3
while (gtk_events_pending()) gtk_main_iteration();
#elif GTK_MAJOR_VERSION == 4
while (g_main_context_iteration(NULL, FALSE))
;
#else
UNSUPPORTED_ERROR();
#endif
}

struct Widget_Guard {
GtkWidget* data;
Widget_Guard(GtkWidget* widget) : data(widget) {}
~Widget_Guard() {
WaitForCleanup();
#if GTK_MAJOR_VERSION == 3
gtk_widget_destroy(data);
#elif GTK_MAJOR_VERSION == 4
gtk_window_destroy(GTK_WINDOW(data));
#else
UNSUPPORTED_ERROR();
#endif
WaitForCleanup();
}
};
Expand Down Expand Up @@ -362,12 +419,20 @@ void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) {
g_free(currentFileName);
}

#if GTK_MAJOR_VERSION == 4
void DialogResponseHandler(GtkDialog*, gint resp, gpointer out_resp_gp) {
gint* out_resp = static_cast<gint*>(out_resp_gp);
*out_resp = resp;
}
#endif

// wrapper for gtk_dialog_run() that brings the dialog to the front
// see issues at:
// https://github.com/btzy/nativefiledialog-extended/issues/31
// https://github.com/mlabbe/nativefiledialog/pull/92
// https://github.com/guillaumechereau/noc/pull/11
gint RunDialogWithFocus(GtkDialog* dialog) {
#if GTK_MAJOR_VERSION == 3
#if defined(GDK_WINDOWING_X11)
gtk_widget_show_all(GTK_WIDGET(dialog)); // show the dialog so that it gets a display
if (GDK_IS_X11_DISPLAY(gtk_widget_get_display(GTK_WIDGET(dialog)))) {
Expand All @@ -379,6 +444,20 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
}
#endif
return gtk_dialog_run(dialog);
#elif GTK_MAJOR_VERSION == 4
// TODO: the X11 popup issues
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
gtk_widget_show(GTK_WIDGET(dialog));
gint resp = 0;
g_signal_connect(G_OBJECT(dialog),
"response",
G_CALLBACK(DialogResponseHandler),
static_cast<gpointer>(&resp));
while (resp == 0) g_main_context_iteration(NULL, TRUE);
return resp;
#else
UNSUPPORTED_ERROR();
#endif
}

} // namespace
Expand All @@ -395,7 +474,14 @@ void NFD_ClearError(void) {

nfdresult_t NFD_Init(void) {
// Init GTK
if (!gtk_init_check(NULL, NULL)) {
#if GTK_MAJOR_VERSION == 3
if (!gtk_init_check(NULL, NULL))
#elif GTK_MAJOR_VERSION == 4
if (!gtk_init_check())
#else
UNSUPPORTED_ERROR();
#endif
{
NFDi_SetError("gtk_init_check failed to initilaize GTK+");
return NFD_ERROR;
}
Expand Down Expand Up @@ -430,13 +516,14 @@ nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath,
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);

/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
nfdresult_t res = SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (res != NFD_OKAY) return res;

if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
*outPath = GetSingleFileName(GTK_FILE_CHOOSER(widget));

return NFD_OKAY;
return *outPath ? NFD_OKAY : NFD_CANCEL;
} else {
return NFD_CANCEL;
}
Expand Down Expand Up @@ -465,7 +552,8 @@ nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths,
AddFiltersToDialog(GTK_FILE_CHOOSER(widget), filterList, filterCount);

/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
nfdresult_t res = SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (res != NFD_OKAY) return res;

if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
Expand Down Expand Up @@ -495,8 +583,10 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,

GtkWidget* saveButton = gtk_dialog_add_button(GTK_DIALOG(widget), "_Save", GTK_RESPONSE_ACCEPT);

// Prompt on overwrite
// Prompt on overwrite (GTK3 only, because GTK4 automatically prompts)
#if GTK_MAJOR_VERSION == 3
gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(widget), TRUE);
#endif

/* Build the filter list */
ButtonClickedArgs buttonClickedArgs;
Expand All @@ -505,7 +595,8 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,
AddFiltersToDialogWithMap(GTK_FILE_CHOOSER(widget), filterList, filterCount);

/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
nfdresult_t res = SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (res != NFD_OKAY) return res;

/* Set the default file name */
SetDefaultName(GTK_FILE_CHOOSER(widget), defaultName);
Expand All @@ -526,9 +617,9 @@ nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath,

if (result == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
*outPath = GetSingleFileName(GTK_FILE_CHOOSER(widget));

return NFD_OKAY;
return *outPath ? NFD_OKAY : NFD_CANCEL;
} else {
return NFD_CANCEL;
}
Expand All @@ -548,13 +639,14 @@ nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath)
Widget_Guard widgetGuard(widget);

/* Set the default path */
SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
nfdresult_t res = SetDefaultPath(GTK_FILE_CHOOSER(widget), defaultPath);
if (res != NFD_OKAY) return res;

if (RunDialogWithFocus(GTK_DIALOG(widget)) == GTK_RESPONSE_ACCEPT) {
// write out the file name
*outPath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
*outPath = GetSingleFileName(GTK_FILE_CHOOSER(widget));

return NFD_OKAY;
return *outPath ? NFD_OKAY : NFD_CANCEL;
} else {
return NFD_CANCEL;
}
Expand Down

0 comments on commit d883012

Please sign in to comment.