77 Note: We do not check for malloc failure on Linux - Linux overcommits memory!
88*/
99
10- #include < assert.h>
1110#include < gtk/gtk.h>
11+
12+ #if defined(NFD_X11)
13+ #if !defined(GDK_WINDOWING_X11)
14+ #if defined(__GNUC__)
15+ #pragma GCC warning \
16+ " NFD is built with X11 but GTK does not support X11, so window parenting will not work."
17+ #endif
18+ #undef NFD_X11
19+ #endif
20+ #endif
21+ #if defined(NFD_WAYLAND)
22+ #if !defined(GDK_WINDOWING_WAYLAND)
23+ #if defined(__GNUC__)
24+ #pragma GCC warning \
25+ " NFD is built with Wayland but GTK does not support Wayland, so window parenting will not work."
26+ #endif
27+ #undef NFD_WAYLAND
28+ #endif
29+ #endif
30+
1231#if defined(NFD_X11)
1332#include < gdk/gdkx.h>
1433#endif
15- #ifdef NFD_WAYLAND
34+ #if defined( NFD_WAYLAND)
1635#include < gdk/gdkwayland.h>
1736#endif
37+
38+ #include < assert.h>
1839#include < stddef.h>
1940#include < stdio.h>
2041#include < stdlib.h>
@@ -429,7 +450,7 @@ void FileActivatedSignalHandler(GtkButton* saveButton, void* userdata) {
429450// https://github.com/mlabbe/nativefiledialog/pull/92
430451// https://github.com/guillaumechereau/noc/pull/11
431452gint RunDialogWithFocus (GtkDialog* dialog) {
432- #if defined(GDK_WINDOWING_X11 )
453+ #if defined(NFD_X11 )
433454 gtk_widget_show_all (GTK_WIDGET (dialog)); // show the dialog so that it gets a display
434455 if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (dialog)))) {
435456 GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (dialog));
@@ -442,7 +463,7 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
442463 return gtk_dialog_run (dialog);
443464}
444465
445- #ifdef NFD_WAYLAND
466+ #if defined( NFD_WAYLAND)
446467void DestroyXdgExported (void * context) {
447468 zxdg_exported_v1_destroy (static_cast <struct zxdg_exported_v1 *>(context));
448469}
@@ -458,101 +479,123 @@ constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener {
458479};
459480#endif
460481
461- void RealizedSignalHandler (GtkWidget* childWindow, void * userdata);
462-
482+ // This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
483+ // To parent a window on GTK, the child GdkWindow needs to be on the same screen as the parent.
484+ // Before the GtkWidget is realized (i.e. the GdkWindow is created for it), we need to tell it the
485+ // GdkScreen to use. Then, after realization, we can get the GtkWidget's GdkWindow and set its
486+ // transient parent to the parent's GdkWindow (but this only works if the parent window uses the
487+ // display server (i.e. X11 or Wayland)). So before realization, we give the GtkWidget a GdkScreen
488+ // for the parent's display server, and after realization we set its transient parent.
463489struct NativeWindowParenter {
464- NativeWindowParenter (GtkWidget* w, const nfdwindowhandle_t & parentHandle) noexcept
465- : widget(w),
466- parentWindow (parentHandle),
467- handlerID(g_signal_connect(G_OBJECT (widget),
468- "realize",
469- G_CALLBACK(RealizedSignalHandler),
470- static_cast<void*>(this ))) {
471- // make the dialog window use the same GtkScreen as the parent (so that parenting works)
472- // gtk_window_set_screen(GTK_WINDOW(widget), gdk_window_get_screen(parent));
490+ NativeWindowParenter (GtkWidget* w, const nfdwindowhandle_t & parentHandle) noexcept {
491+ GdkScreen* gdk_screen;
492+ void (*realized_handler)(GtkWidget*, void *);
493+ GetScreenAndHandler (parentHandle.type , gdk_screen, realized_handler);
494+
495+ if (gdk_screen && realized_handler) {
496+ widget = w;
497+
498+ parentWindowHandle = parentHandle.handle ;
499+
500+ // make the dialog window use a GtkScreen with the same display server as the parent (so
501+ // that parenting works)
502+ gtk_window_set_screen (GTK_WINDOW (w), gdk_screen);
503+
504+ handlerID = g_signal_connect (
505+ G_OBJECT (w), " realize" , G_CALLBACK (realized_handler), static_cast <void *>(this ));
506+ } else {
507+ widget = nullptr ;
508+ }
473509 }
510+
474511 ~NativeWindowParenter () {
475- // unset the handler and delete the parent GdkWindow
476- g_signal_handler_disconnect (G_OBJECT (widget), handlerID);
477- if (destroy.fn ) {
478- destroy.fn (destroy.context );
512+ // unset the handler
513+ if (widget) {
514+ g_signal_handler_disconnect (G_OBJECT (widget), handlerID);
479515 }
516+ // No need to call destroy.fn because it is destroyed in the destructor of DestroyFunc.
480517 }
481- GtkWidget* const widget;
482- const nfdwindowhandle_t & parentWindow;
483- DestroyFunc destroy;
484- gulong handlerID;
485- };
486-
487- void RealizedSignalHandler (GtkWidget* childWindow, void * userdata) {
488- NativeWindowParenter& data = *static_cast <NativeWindowParenter*>(userdata);
489518
490- switch (data.parentWindow .type ) {
519+ static void GetScreenAndHandler (size_t parentWindowType,
520+ GdkScreen*& outScreen,
521+ void (*&outHandler)(GtkWidget*, void *)) {
522+ switch (parentWindowType) {
491523#if defined(NFD_X11)
492- #if !defined(GDK_WINDOWING_X11) && __GNUC__
493- #pragma GCC warning \
494- " NFD is built with X11 but GTK does not support X11, so window parenting will not work."
495- #endif
496- case NFD_WINDOW_HANDLE_TYPE_X11 : {
497- // AFAIK, _any_ X11 display will do, because Windows are not associated to a specific
498- // Display. Supposedly, a Display is just a connection to the X server.
499-
500- // This will contain the X11 display we want to use.
501- GdkDisplay* x11_gdk_display = nullptr ;
502- GdkDisplayManager* display_manager = gdk_display_manager_get ();
503-
504- // If we can find an existing X11 display, use it.
505- GSList* gdk_display_list = gdk_display_manager_list_displays (display_manager);
506- while (gdk_display_list) {
507- GSList* node = gdk_display_list;
508- GdkDisplay* display = GDK_DISPLAY (node->data );
509- if (GDK_IS_X11_DISPLAY (display)) {
510- g_slist_free (node);
511- x11_gdk_display = display;
512- break ;
513- } else {
514- gdk_display_list = node->next ;
515- g_slist_free_1 (node);
524+ case NFD_WINDOW_HANDLE_TYPE_X11 : {
525+ if (x11_gdk_screen) {
526+ outScreen = x11_gdk_screen;
527+ outHandler = &RealizedSignalHandler<&NativeWindowParenter::SetParentX11>;
528+ return ;
529+ }
530+
531+ GdkDisplayManager* display_manager = gdk_display_manager_get ();
532+
533+ // If we can find an existing X11 display, use it.
534+ GSList* gdk_display_list = gdk_display_manager_list_displays (display_manager);
535+ while (gdk_display_list) {
536+ GSList* node = gdk_display_list;
537+ GdkDisplay* display = GDK_DISPLAY (node->data );
538+ if (GDK_IS_X11_DISPLAY (display)) {
539+ g_slist_free (node);
540+ x11_gdk_display = display;
541+ break ;
542+ } else {
543+ gdk_display_list = node->next ;
544+ g_slist_free_1 (node);
545+ }
516546 }
517- }
518547
519- // Otherwise, we have to create our own X11 display.
520- if (!x11_gdk_display) {
521- // This is not very nice, because we are always resetting the allowed backends
522- // setting to NULL (which means all backends are allowed), even though we can't be
523- // sure that the user didn't call gdk_set_allowed_backends() earlier to force a
524- // specific backend. But well if the user doesn't have an X11 display already open
525- // and yet is telling us with have an X11 window as parent, they probably don't use
526- // GTK in their application at all so they probably won't notice this.
527- //
528- // There is no way, AFAIK, to get the allowed backends first so we can restore it
529- // later, and gdk_x11_display_open() is GTK4-only (the GTK3 version is a private
530- // implementation detail).
531- //
532- // Also, we don't close the display we specially opened, since GTK will need it to
533- // show the dialog. Though it probably doesn't matter very much if we want to free
534- // up resources and clean it up.
535- gdk_set_allowed_backends (" x11" );
536- x11_gdk_display = gdk_display_manager_open_display (display_manager, NULL );
537- gdk_set_allowed_backends (NULL );
548+ // Otherwise, we have to create our own X11 display.
549+ if (!x11_gdk_display) {
550+ // This is not very nice, because we are always resetting the allowed backends
551+ // setting to NULL (which means all backends are allowed), even though we can't
552+ // be sure that the user didn't call gdk_set_allowed_backends() earlier to force
553+ // a specific backend. But well if the user doesn't have an X11 display already
554+ // open and yet is telling us with have an X11 window as parent, they probably
555+ // don't use GTK in their application at all so they probably won't notice this.
556+ //
557+ // There is no way, AFAIK, to get the allowed backends first so we can restore
558+ // it later, and gdk_x11_display_open() is GTK4-only (the GTK3 version is a
559+ // private implementation detail).
560+ //
561+ // Also, we don't close the display we specially opened, since GTK will need it
562+ // to show the dialog. Though it probably doesn't matter very much if we want
563+ // to free up resources and clean it up.
564+ gdk_set_allowed_backends (" x11" );
565+ GdkDisplay* display =
566+ gdk_display_manager_open_display (display_manager, nullptr );
567+ gdk_set_allowed_backends (nullptr );
568+ if (display) {
569+ if (GDK_IS_X11_DISPLAY (display))
570+ x11_gdk_display = display;
571+ else
572+ gdk_display_close (display);
573+ }
574+ }
575+
576+ if (x11_gdk_display) {
577+ // Set the screen if we have a display.
578+ x11_gdk_screen = gdk_display_get_default_screen (x11_gdk_display);
579+ // In the unlikely situation that we can't get the default screen, set the
580+ // display back to null.
581+ if (!x11_gdk_screen) x11_gdk_display = nullptr ;
582+ }
583+
584+ outScreen = x11_gdk_screen;
585+ outHandler = x11_gdk_screen
586+ ? &RealizedSignalHandler<&NativeWindowParenter::SetParentX11>
587+ : nullptr ;
588+ return ;
538589 }
539- if (!x11_gdk_display) return ;
540- const Window x11_handle = reinterpret_cast <Window>(data.parentWindow .handle );
541- GdkWindow* gdk_window =
542- gdk_x11_window_foreign_new_for_display (x11_gdk_display, x11_handle);
543- gdk_window_set_transient_for (gtk_widget_get_window (childWindow), gdk_window);
544- data.destroy .fn = &g_object_unref;
545- data.destroy .context = static_cast <void *>(gdk_window);
546- return ;
547- }
548590#endif
549591#if defined(NFD_WAYLAND)
550- #if !defined(GDK_WINDOWING_WAYLAND) && __GNUC__
551- #pragma GCC warning \
552- " NFD is built with Wayland but GTK does not support Wayland, so window parenting will not work."
553- #endif
554- case NFD_WINDOW_HANDLE_TYPE_WAYLAND : {
555- if (wayland_display && wayland_xdg_exporter_v1) {
592+ case NFD_WINDOW_HANDLE_TYPE_WAYLAND : {
593+ if (wayland_gdk_screen) {
594+ outScreen = wayland_gdk_screen;
595+ outHandler = &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland>;
596+ return ;
597+ }
598+
556599 // This will contain the Wayland display we want to use.
557600 GdkDisplay* wayland_gdk_display = nullptr ;
558601 GdkDisplayManager* display_manager = gdk_display_manager_get ();
@@ -590,34 +633,87 @@ void RealizedSignalHandler(GtkWidget* childWindow, void* userdata) {
590633 // to show the dialog. Though it probably doesn't matter very much if we want
591634 // to free up resources and clean it up.
592635 gdk_set_allowed_backends (" wayland" );
593- wayland_gdk_display = gdk_display_manager_open_display (display_manager, NULL );
636+ GdkDisplay* display = gdk_display_manager_open_display (display_manager, NULL );
594637 gdk_set_allowed_backends (NULL );
638+ if (display) {
639+ if (GDK_IS_WAYLAND_DISPLAY (display))
640+ wayland_gdk_display = display;
641+ else
642+ gdk_display_close (display);
643+ }
595644 }
596- if (!wayland_gdk_display) return ;
597-
598- struct zxdg_exported_v1 * exported = zxdg_exporter_v1_export (
599- wayland_xdg_exporter_v1,
600- static_cast <struct wl_surface *>(data.parentWindow .handle ));
601- if (!exported) {
602- // if we fail to export the wl_surface, act as if the window has no parent
603- return ;
645+ if (wayland_gdk_display) {
646+ // Set the screen if we have a display.
647+ wayland_gdk_screen = gdk_display_get_default_screen (wayland_gdk_display);
604648 }
605- zxdg_exported_v1_add_listener (
606- exported,
607- &wayland_xdg_exported_v1_listener,
608- static_cast <void *>(gtk_widget_get_window (childWindow)));
609- wl_display_roundtrip (wayland_display);
610- zxdg_exported_v1_set_user_data (exported, nullptr );
611- data.destroy .fn = &DestroyXdgExported;
612- data.destroy .context = static_cast <void *>(exported);
649+ outScreen = wayland_gdk_screen;
650+ outHandler = wayland_gdk_screen
651+ ? &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland>
652+ : nullptr ;
653+ return ;
613654 }
614- return ;
655+ #endif
656+ default :
657+ outScreen = nullptr ;
658+ outHandler = nullptr ;
659+ return ;
615660 }
661+ }
662+
663+ template <void (NativeWindowParenter::*Func)(GdkWindow*)>
664+ static void RealizedSignalHandler(GtkWidget* childWindow, void * userdata) {
665+ NativeWindowParenter& data = *static_cast <NativeWindowParenter*>(userdata);
666+ (data.*Func)(gtk_widget_get_window (childWindow));
667+ }
668+
669+ #if defined(NFD_X11)
670+ void SetParentX11 (GdkWindow* childWindow) {
671+ const Window x11_handle = reinterpret_cast <Window>(parentWindowHandle);
672+ GdkWindow* gdk_window = gdk_x11_window_foreign_new_for_display (x11_gdk_display, x11_handle);
673+ gdk_window_set_transient_for (childWindow, gdk_window);
674+ destroy.fn = &g_object_unref;
675+ destroy.context = static_cast <void *>(gdk_window);
676+ }
616677#endif
617- default :
618- return ;
678+
679+ #if defined(NFD_WAYLAND)
680+ void SetParentWayland (GdkWindow* childWindow) {
681+ if (wayland_display && wayland_xdg_exporter_v1) {
682+ struct zxdg_exported_v1 * exported = zxdg_exporter_v1_export (
683+ wayland_xdg_exporter_v1, static_cast <struct wl_surface *>(parentWindowHandle));
684+ if (!exported) {
685+ // if we fail to export the wl_surface, act as if the window has no parent
686+ return ;
687+ }
688+ zxdg_exported_v1_add_listener (
689+ exported, &wayland_xdg_exported_v1_listener, static_cast <void *>(childWindow));
690+ wl_display_roundtrip (wayland_display);
691+ zxdg_exported_v1_set_user_data (exported, nullptr );
692+ destroy.fn = &DestroyXdgExported;
693+ destroy.context = static_cast <void *>(exported);
694+ }
619695 }
620- }
696+ #endif
697+
698+ GtkWidget* widget;
699+ void * parentWindowHandle;
700+ DestroyFunc destroy;
701+ gulong handlerID;
702+ #if defined(NFD_X11)
703+ static GdkDisplay* x11_gdk_display;
704+ static GdkScreen* x11_gdk_screen;
705+ #endif
706+ #if defined(NFD_WAYLAND)
707+ static GdkScreen* wayland_gdk_screen;
708+ #endif
709+ };
710+ #if defined(NFD_X11)
711+ GdkDisplay* NativeWindowParenter::x11_gdk_display = nullptr ;
712+ GdkScreen* NativeWindowParenter::x11_gdk_screen = nullptr ;
713+ #endif
714+ #if defined(NFD_WAYLAND)
715+ GdkScreen* NativeWindowParenter::wayland_gdk_screen = nullptr ;
716+ #endif
621717
622718} // namespace
623719
@@ -637,14 +733,14 @@ nfdresult_t NFD_Init(void) {
637733 NFDi_SetError (" Failed to initialize GTK+ with gtk_init_check." );
638734 return NFD_ERROR ;
639735 }
640- #ifdef NFD_WAYLAND
736+ #if defined( NFD_WAYLAND)
641737 NFD_Wayland_Init ();
642738#endif
643739 return NFD_OKAY ;
644740}
645741
646742void NFD_Quit (void ) {
647- #ifdef NFD_WAYLAND
743+ #if defined( NFD_WAYLAND)
648744 NFD_Wayland_Quit ();
649745#endif
650746 // do nothing about GTK since it cannot be de-initialized
0 commit comments