Skip to content

Commit 853e207

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

2 files changed

Lines changed: 207 additions & 111 deletions

File tree

src/nfd_gtk.cpp

Lines changed: 206 additions & 110 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,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.
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+
}
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

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

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)