Skip to content

Commit c172bb4

Browse files
committed
Set screen from display server
1 parent 8909d4c commit c172bb4

2 files changed

Lines changed: 193 additions & 110 deletions

File tree

src/nfd_gtk.cpp

Lines changed: 192 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,35 @@
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
431452
gint 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)
446467
void DestroyXdgExported(void* context) {
447468
zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
448469
}
@@ -458,101 +479,116 @@ 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.
463489
struct 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+
}
546+
}
547+
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+
x11_gdk_display = gdk_display_manager_open_display(display_manager, nullptr);
566+
gdk_set_allowed_backends(nullptr);
516567
}
517-
}
518568

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);
569+
if (x11_gdk_display) {
570+
// Set the screen if we have a display.
571+
x11_gdk_screen = gdk_display_get_default_screen(x11_gdk_display);
572+
// In the unlikely situation that we can't get the default screen, set the
573+
// display back to null.
574+
if (!x11_gdk_screen) x11_gdk_display = nullptr;
575+
}
576+
577+
outScreen = x11_gdk_screen;
578+
outHandler = x11_gdk_screen
579+
? &RealizedSignalHandler<&NativeWindowParenter::SetParentX11>
580+
: nullptr;
581+
return;
538582
}
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-
}
548583
#endif
549584
#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) {
585+
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
586+
if (wayland_gdk_screen) {
587+
outScreen = wayland_gdk_screen;
588+
outHandler = &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland>;
589+
return;
590+
}
591+
556592
// This will contain the Wayland display we want to use.
557593
GdkDisplay* wayland_gdk_display = nullptr;
558594
GdkDisplayManager* display_manager = gdk_display_manager_get();
@@ -593,31 +629,78 @@ void RealizedSignalHandler(GtkWidget* childWindow, void* userdata) {
593629
wayland_gdk_display = gdk_display_manager_open_display(display_manager, NULL);
594630
gdk_set_allowed_backends(NULL);
595631
}
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;
632+
if (wayland_gdk_display) {
633+
// Set the screen if we have a display.
634+
wayland_gdk_screen = gdk_display_get_default_screen(wayland_gdk_display);
604635
}
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);
636+
outScreen = wayland_gdk_screen;
637+
outHandler = wayland_gdk_screen
638+
? &RealizedSignalHandler<&NativeWindowParenter::SetParentWayland>
639+
: nullptr;
640+
return;
613641
}
614-
return;
642+
#endif
643+
default:
644+
outScreen = nullptr;
645+
outHandler = nullptr;
646+
return;
615647
}
648+
}
649+
650+
template <void (NativeWindowParenter::*Func)(GdkWindow*)>
651+
static void RealizedSignalHandler(GtkWidget* childWindow, void* userdata) {
652+
NativeWindowParenter& data = *static_cast<NativeWindowParenter*>(userdata);
653+
(data.*Func)(gtk_widget_get_window(childWindow));
654+
}
655+
656+
#if defined(NFD_X11)
657+
void SetParentX11(GdkWindow* childWindow) {
658+
const Window x11_handle = reinterpret_cast<Window>(parentWindowHandle);
659+
GdkWindow* gdk_window = gdk_x11_window_foreign_new_for_display(x11_gdk_display, x11_handle);
660+
gdk_window_set_transient_for(childWindow, gdk_window);
661+
destroy.fn = &g_object_unref;
662+
destroy.context = static_cast<void*>(gdk_window);
663+
}
616664
#endif
617-
default:
618-
return;
665+
666+
#if defined(NFD_WAYLAND)
667+
void SetParentWayland(GdkWindow* childWindow) {
668+
if (wayland_display && wayland_xdg_exporter_v1) {
669+
struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
670+
wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindowHandle));
671+
if (!exported) {
672+
// if we fail to export the wl_surface, act as if the window has no parent
673+
return;
674+
}
675+
zxdg_exported_v1_add_listener(
676+
exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(childWindow));
677+
wl_display_roundtrip(wayland_display);
678+
zxdg_exported_v1_set_user_data(exported, nullptr);
679+
destroy.fn = &DestroyXdgExported;
680+
destroy.context = static_cast<void*>(exported);
681+
}
619682
}
620-
}
683+
#endif
684+
685+
GtkWidget* widget;
686+
void* parentWindowHandle;
687+
DestroyFunc destroy;
688+
gulong handlerID;
689+
#if defined(NFD_X11)
690+
static GdkDisplay* x11_gdk_display;
691+
static GdkScreen* x11_gdk_screen;
692+
#endif
693+
#if defined(NFD_WAYLAND)
694+
static GdkScreen* wayland_gdk_screen;
695+
#endif
696+
};
697+
#if defined(NFD_X11)
698+
GdkDisplay* NativeWindowParenter::x11_gdk_display = nullptr;
699+
GdkScreen* NativeWindowParenter::x11_gdk_screen = nullptr;
700+
#endif
701+
#if defined(NFD_WAYLAND)
702+
GdkScreen* NativeWindowParenter::wayland_gdk_screen = nullptr;
703+
#endif
621704

622705
} // namespace
623706

@@ -637,14 +720,14 @@ nfdresult_t NFD_Init(void) {
637720
NFDi_SetError("Failed to initialize GTK+ with gtk_init_check.");
638721
return NFD_ERROR;
639722
}
640-
#ifdef NFD_WAYLAND
723+
#if defined(NFD_WAYLAND)
641724
NFD_Wayland_Init();
642725
#endif
643726
return NFD_OKAY;
644727
}
645728

646729
void NFD_Quit(void) {
647-
#ifdef NFD_WAYLAND
730+
#if defined(NFD_WAYLAND)
648731
NFD_Wayland_Quit();
649732
#endif
650733
// do nothing about GTK since it cannot be de-initialized

src/nfd_linux_shared.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,4 @@ nfdresult_t NFD_SetWaylandDisplay(wl_display* display) {
8888
(void)display;
8989
#endif
9090
return NFD_OKAY;
91-
}
91+
}

0 commit comments

Comments
 (0)