Skip to content

Commit d8d6fdc

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

1 file changed

Lines changed: 191 additions & 112 deletions

File tree

src/nfd_gtk.cpp

Lines changed: 191 additions & 112 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
}
@@ -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.
463488
struct 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

646725
void 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

Comments
 (0)