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}
@@ -453,106 +474,122 @@ void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char
453474 gdk_wayland_window_set_transient_for_exported (childWindow, const_cast <char *>(handle));
454475}
455476
456- constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener {
457- &zxdg_exported_v1_handle
458- };
477+ constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
478+ &zxdg_exported_v1_handle};
459479#endif
460480
461- void RealizedSignalHandler (GtkWidget* childWindow, void * userdata);
462-
481+ // This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
482+ // To parent a window on GTK, the child GdkWindow needs to be on the same screen as the parent.
483+ // Before the GtkWidget is realized (i.e. the GdkWindow is created for it), we need to tell it the
484+ // GdkScreen to use. Then, after realization, we can get the GtkWidget's GdkWindow and set its
485+ // transient parent to the parent's GdkWindow (but this only works if the parent window uses the
486+ // display server (i.e. X11 or Wayland)). So before realization, we give the GtkWidget a GdkScreen
487+ // for the parent's display server, and after realization we set its transient parent.
463488struct NativeWindowParenter {
464489 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+ {
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 (G_OBJECT (w),
505+ " realize" ,
506+ G_CALLBACK (realized_handler),
507+ static_cast <void *>(this ));
508+ } else {
509+ widget = nullptr ;
510+ }
511+
473512 }
513+
474514 ~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 );
515+ // unset the handler
516+ if (widget) {
517+ g_signal_handler_disconnect (G_OBJECT (widget), handlerID);
479518 }
519+ // No need to call destroy.fn because it is destroyed in the destructor of DestroyFunc.
480520 }
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);
489521
490- switch (data.parentWindow .type ) {
522+ static void GetScreenAndHandler (size_t parentWindowType,
523+ GdkScreen*& outScreen,
524+ void (*&outHandler)(GtkWidget*, void *)) {
525+ switch (parentWindowType) {
491526#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);
527+ case NFD_WINDOW_HANDLE_TYPE_X11 : {
528+ if (x11_gdk_screen) {
529+ outScreen = x11_gdk_screen;
530+ outHandler = &RealizedSignalHandler<&NativeWindowParenter::SetParentX11>;
531+ return ;
516532 }
517- }
518533
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 );
534+ GdkDisplayManager* display_manager = gdk_display_manager_get ();
535+
536+ // If we can find an existing X11 display, use it.
537+ GSList* gdk_display_list = gdk_display_manager_list_displays (display_manager);
538+ while (gdk_display_list) {
539+ GSList* node = gdk_display_list;
540+ GdkDisplay* display = GDK_DISPLAY (node->data );
541+ if (GDK_IS_X11_DISPLAY (display)) {
542+ g_slist_free (node);
543+ x11_gdk_display = display;
544+ break ;
545+ } else {
546+ gdk_display_list = node->next ;
547+ g_slist_free_1 (node);
548+ }
549+ }
550+
551+ // Otherwise, we have to create our own X11 display.
552+ if (!x11_gdk_display) {
553+ // This is not very nice, because we are always resetting the allowed backends
554+ // setting to NULL (which means all backends are allowed), even though we can't
555+ // be sure that the user didn't call gdk_set_allowed_backends() earlier to force
556+ // a specific backend. But well if the user doesn't have an X11 display already
557+ // open and yet is telling us with have an X11 window as parent, they probably
558+ // don't use GTK in their application at all so they probably won't notice this.
559+ //
560+ // There is no way, AFAIK, to get the allowed backends first so we can restore
561+ // it later, and gdk_x11_display_open() is GTK4-only (the GTK3 version is a
562+ // private implementation detail).
563+ //
564+ // Also, we don't close the display we specially opened, since GTK will need it
565+ // to show the dialog. Though it probably doesn't matter very much if we want
566+ // to free up resources and clean it up.
567+ gdk_set_allowed_backends (" x11" );
568+ x11_gdk_display = gdk_display_manager_open_display (display_manager, nullptr );
569+ gdk_set_allowed_backends (nullptr );
570+ }
571+
572+ if (x11_gdk_display) {
573+ // Set the screen if we have a display.
574+ x11_gdk_screen = gdk_display_get_default_screen (x11_gdk_display);
575+ // In the unlikely situation that we can't get the default screen, set the
576+ // display back to null.
577+ if (!x11_gdk_screen) x11_gdk_display = nullptr ;
578+ }
579+
580+ outScreen = x11_gdk_screen;
581+ outHandler = x11_gdk_screen ? &RealizedSignalHandler<&NativeWindowParenter::SetParentX11> : nullptr ;
582+ return ;
538583 }
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- }
548584#endif
549585#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) {
586+ case NFD_WINDOW_HANDLE_TYPE_WAYLAND : {
587+ if (wayland_gdk_screen) {
588+ outScreen = wayland_gdk_screen;
589+ outHandler = &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland>;
590+ return ;
591+ }
592+
556593 // This will contain the Wayland display we want to use.
557594 GdkDisplay* wayland_gdk_display = nullptr ;
558595 GdkDisplayManager* display_manager = gdk_display_manager_get ();
@@ -593,31 +630,73 @@ void RealizedSignalHandler(GtkWidget* childWindow, void* userdata) {
593630 wayland_gdk_display = gdk_display_manager_open_display (display_manager, NULL );
594631 gdk_set_allowed_backends (NULL );
595632 }
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 ;
633+ if (wayland_gdk_display) {
634+ // Set the screen if we have a display.
635+ wayland_gdk_screen = gdk_display_get_default_screen (wayland_gdk_display);
604636 }
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);
637+ outScreen = wayland_gdk_screen;
638+ outHandler = wayland_gdk_screen ? &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland> : nullptr ;
639+ return ;
613640 }
614- return ;
615- }
616641#endif
617- default :
618- return ;
642+ default :
643+ outScreen = nullptr ;
644+ outHandler = nullptr ;
645+ return ;
646+ }
647+ }
648+
649+ template <void (NativeWindowParenter::*Func)(GdkWindow*)>
650+ static void RealizedSignalHandler(GtkWidget* childWindow, void * userdata) {
651+ NativeWindowParenter& data = *static_cast <NativeWindowParenter*>(userdata);
652+ (data.*Func)(gtk_widget_get_window (childWindow));
653+ }
654+
655+ void SetParentX11 (GdkWindow* childWindow) {
656+ const Window x11_handle = reinterpret_cast <Window>(parentWindowHandle);
657+ GdkWindow* gdk_window = gdk_x11_window_foreign_new_for_display (x11_gdk_display, x11_handle);
658+ gdk_window_set_transient_for (childWindow, gdk_window);
659+ destroy.fn = &g_object_unref;
660+ destroy.context = static_cast <void *>(gdk_window);
619661 }
620- }
662+
663+ void SetParentWayland (GdkWindow* childWindow) {
664+ if (wayland_display && wayland_xdg_exporter_v1) {
665+ struct zxdg_exported_v1 * exported = zxdg_exporter_v1_export (
666+ wayland_xdg_exporter_v1, static_cast <struct wl_surface *>(parentWindowHandle));
667+ if (!exported) {
668+ // if we fail to export the wl_surface, act as if the window has no parent
669+ return ;
670+ }
671+ zxdg_exported_v1_add_listener (exported,
672+ &wayland_xdg_exported_v1_listener,
673+ static_cast <void *>(childWindow));
674+ wl_display_roundtrip (wayland_display);
675+ zxdg_exported_v1_set_user_data (exported, nullptr );
676+ destroy.fn = &DestroyXdgExported;
677+ destroy.context = static_cast <void *>(exported);
678+ }
679+ }
680+
681+ GtkWidget* widget;
682+ void * parentWindowHandle;
683+ DestroyFunc destroy;
684+ gulong handlerID;
685+ #if defined(NFD_X11)
686+ static GdkDisplay* x11_gdk_display;
687+ static GdkScreen* x11_gdk_screen;
688+ #endif
689+ #if defined(NFD_WAYLAND)
690+ static GdkScreen* wayland_gdk_screen;
691+ #endif
692+ };
693+ #if defined(NFD_X11)
694+ GdkDisplay* NativeWindowParenter::x11_gdk_display = nullptr ;
695+ GdkScreen* NativeWindowParenter::x11_gdk_screen = nullptr ;
696+ #endif
697+ #if defined(NFD_WAYLAND)
698+ GdkScreen* NativeWindowParenter::wayland_gdk_screen = nullptr ;
699+ #endif
621700
622701} // namespace
623702
@@ -637,14 +716,14 @@ nfdresult_t NFD_Init(void) {
637716 NFDi_SetError (" Failed to initialize GTK+ with gtk_init_check." );
638717 return NFD_ERROR ;
639718 }
640- #ifdef NFD_WAYLAND
719+ #if defined( NFD_WAYLAND)
641720 NFD_Wayland_Init ();
642721#endif
643722 return NFD_OKAY ;
644723}
645724
646725void NFD_Quit (void ) {
647- #ifdef NFD_WAYLAND
726+ #if defined( NFD_WAYLAND)
648727 NFD_Wayland_Quit ();
649728#endif
650729 // do nothing about GTK since it cannot be de-initialized
0 commit comments