@@ -770,6 +770,33 @@ class Window::Impl {
770770 Size requested_content_size_ = {0 , 0 };
771771};
772772
773+ #ifdef GDK_WINDOWING_X11
774+ // Whether the window manager lists |state| (an atom name such as
775+ // "_NET_WM_STATE_ABOVE") in the window's _NET_WM_STATE. GDK does not track the
776+ // above/below states it requests, so its window state never shows them.
777+ static bool HasX11WindowState (GdkWindow* surface, const char * state) {
778+ auto * display = gdk_window_get_display (surface);
779+ auto * xdisplay = GDK_DISPLAY_XDISPLAY (display);
780+ const Atom wanted = gdk_x11_get_xatom_by_name_for_display (display, state);
781+ Atom type = 0 ;
782+ int format = 0 ;
783+ unsigned long count = 0 , remaining = 0 ;
784+ unsigned char * data = nullptr ;
785+ bool found = false ;
786+ gdk_x11_display_error_trap_push (display);
787+ if (XGetWindowProperty (xdisplay, GDK_WINDOW_XID (surface),
788+ gdk_x11_get_xatom_by_name_for_display (display, " _NET_WM_STATE" ), 0 , 64 ,
789+ False, XA_ATOM , &type, &format, &count, &remaining, &data) == Success &&
790+ data && type == XA_ATOM && format == 32 ) {
791+ const auto * atoms = reinterpret_cast <Atom*>(data);
792+ for (unsigned long i = 0 ; i < count && !found; ++i) found = atoms[i] == wanted;
793+ }
794+ if (data) XFree (data);
795+ gdk_x11_display_error_trap_pop_ignored (display);
796+ return found;
797+ }
798+ #endif
799+
773800// Reports direct changes to the properties WindowPropertyChangedEvent covers:
774801// the GtkWindow's title, resizable and deletable properties, and the window
775802// manager's above/below state. The handlers sit on the widget itself, so GTK
@@ -807,6 +834,23 @@ static bool ObserveWindowProperties(GtkWidget* widget) {
807834 return FALSE ;
808835 }),
809836 nullptr );
837+ #ifdef GDK_WINDOWING_X11
838+ // The above/below states only show in _NET_WM_STATE (see HasX11WindowState).
839+ if (auto * surface = gtk_widget_get_window (widget); surface && GDK_IS_X11_WINDOW (surface)) {
840+ gdk_window_add_filter (
841+ surface,
842+ +[](GdkXEvent* native, GdkEvent*, gpointer data) -> GdkFilterReturn {
843+ auto * event = static_cast <XEvent*>(native);
844+ auto * self = static_cast <GtkWidget*>(data);
845+ if (event->type == PropertyNotify &&
846+ event->xproperty .atom == gdk_x11_get_xatom_by_name_for_display (
847+ gtk_widget_get_display (self), " _NET_WM_STATE" ))
848+ RefreshPropertiesOf (self);
849+ return GDK_FILTER_CONTINUE ;
850+ },
851+ widget);
852+ }
853+ #endif
810854 // Occlusion: shown and hidden (minimizing is the window-state-event above).
811855 auto on_mapping = +[](GtkWidget* self, gpointer) { RefreshOcclusionOf (self); };
812856 g_signal_connect_after (widget, " map" , G_CALLBACK (on_mapping), nullptr );
@@ -1542,6 +1586,10 @@ void Window::SetAlwaysOnTop(bool is_always_on_top) {
15421586bool Window::IsAlwaysOnTop () const {
15431587 if (!pimpl_->gdk_window_ )
15441588 return false ;
1589+ #ifdef GDK_WINDOWING_X11
1590+ if (GDK_IS_X11_WINDOW (pimpl_->gdk_window_ ))
1591+ return HasX11WindowState (pimpl_->gdk_window_ , " _NET_WM_STATE_ABOVE" );
1592+ #endif
15451593 GdkWindowState state = gdk_window_get_state (pimpl_->gdk_window_ );
15461594 return state & GDK_WINDOW_STATE_ABOVE ;
15471595}
@@ -1558,6 +1606,10 @@ void Window::SetAlwaysOnBottom(bool is_always_on_bottom) {
15581606bool Window::IsAlwaysOnBottom () const {
15591607 if (!pimpl_->gdk_window_ )
15601608 return false ;
1609+ #ifdef GDK_WINDOWING_X11
1610+ if (GDK_IS_X11_WINDOW (pimpl_->gdk_window_ ))
1611+ return HasX11WindowState (pimpl_->gdk_window_ , " _NET_WM_STATE_BELOW" );
1612+ #endif
15611613 GdkWindowState state = gdk_window_get_state (pimpl_->gdk_window_ );
15621614 return state & GDK_WINDOW_STATE_BELOW ;
15631615}
0 commit comments