Skip to content

Commit 1097ca6

Browse files
committed
Read X11 always-on-top/bottom from _NET_WM_STATE
GDK never reports the above/below states it requests on X11, so IsAlwaysOnTop() and IsAlwaysOnBottom() were always false there even though the window manager had applied them (seen with xprop under GNOME's Xwayland). Read _NET_WM_STATE directly, and refresh the reported properties on its PropertyNotify so WindowPropertyChangedEvent follows. Wayland keeps GDK's state. The property test now probes keep-above through the public getter; on GNOME/Xwayland (Mutter) all always-on-top checks run and pass.
1 parent c38b73e commit 1097ca6

2 files changed

Lines changed: 61 additions & 8 deletions

File tree

‎src/platform/linux/window_linux.cpp‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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) {
15421586
bool 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) {
15581606
bool 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
}

‎tests/window_property_changed_test.cpp‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,19 @@ int main(int argc, char** argv) {
7474
#endif
7575
Pump([] { return false; }, 300); // Linux subscribes on the next main loop turn.
7676

77-
// GTK reports keep-above only once the window manager confirms it, which
78-
// not every one does (Openbox on Xvfb does not); check with a plain window.
77+
// Keep-above is up to the window manager (Wayland has none for clients);
78+
// check with a separate shown window whether this one honors it.
7979
bool keep_above_confirmed = true;
8080
#if !defined(__APPLE__) && !defined(_WIN32)
8181
{
82-
GtkWidget* plain = gtk_window_new(GTK_WINDOW_TOPLEVEL);
83-
gtk_widget_show(plain);
82+
Window probe;
83+
probe.Show();
8484
Pump([] { return false; }, 300);
85-
gtk_window_set_keep_above(GTK_WINDOW(plain), TRUE);
86-
Pump([&] { return !!(gdk_window_get_state(gtk_widget_get_window(plain)) & GDK_WINDOW_STATE_ABOVE); });
87-
keep_above_confirmed = gdk_window_get_state(gtk_widget_get_window(plain)) & GDK_WINDOW_STATE_ABOVE;
88-
gtk_widget_destroy(plain);
85+
probe.SetAlwaysOnTop(true);
86+
Pump([&] { return probe.IsAlwaysOnTop(); });
87+
keep_above_confirmed = probe.IsAlwaysOnTop();
88+
gtk_widget_destroy(GTK_WIDGET(probe.GetNativeObject()));
89+
Pump([] { return false; }, 100);
8990
if (!keep_above_confirmed)
9091
std::cout << "SKIP always-on-top: the window manager does not confirm keep-above" << std::endl;
9192
}

0 commit comments

Comments
 (0)