Skip to content

Commit e791090

Browse files
committed
Keep one window ID on both GTK objects (#65)
WindowManager keys a window by its GdkWindow and stored the ID there alone; a Window wrapper made afterwards found it there but left the GtkWindow without one. The close gate and the per-window event subscription check the ID on the GtkWindow, so a window WindowManager saw first - a Flutter app's, through GetCurrent() - got no events of its own and its close requests could not be cancelled: the GNOME close button closed it regardless. The widget's ID now wins wherever one is found or allocated, and goes on both objects. tests/window_close_manager_first_linux_test fails without this (no close request, no property event) and passes with it.
1 parent e21c922 commit e791090

4 files changed

Lines changed: 85 additions & 7 deletions

File tree

‎src/platform/linux/window_linux.cpp‎

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -932,18 +932,24 @@ Window::Window(void* native_window) {
932932
}
933933

934934
// Like the other platforms, a wrapped window gets an ID on first sight, stored
935-
// on the native objects so every later wrapper and WindowManager agree on it.
935+
// on both native objects so every later wrapper and WindowManager agree on it.
936+
// The widget's ID wins: it is the identity GetNativeObject() reports, and the
937+
// close gate and the per-window events check it there. WindowManager, which
938+
// sees windows through their GdkWindow, may have stored one on that alone.
936939
gpointer existing_id = nullptr;
937-
if (gdk_window) {
938-
existing_id = g_object_get_data(G_OBJECT(gdk_window), kWindowIdKey);
939-
}
940-
if (!existing_id && widget) {
940+
if (widget) {
941941
existing_id = g_object_get_data(G_OBJECT(widget), kWindowIdKey);
942942
}
943+
if (!existing_id && gdk_window) {
944+
existing_id = g_object_get_data(G_OBJECT(gdk_window), kWindowIdKey);
945+
}
943946
if (existing_id) {
944947
if (gdk_window) {
945948
g_object_set_data(G_OBJECT(gdk_window), kWindowIdKey, existing_id);
946949
}
950+
if (widget) {
951+
g_object_set_data(G_OBJECT(widget), kWindowIdKey, existing_id);
952+
}
947953
} else if (gdk_window || widget) {
948954
WindowId id = IdAllocator::Allocate<Window>();
949955
if (id != IdAllocator::kInvalidId) {

‎src/platform/linux/window_manager_linux.cpp‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,17 @@ static WindowId GetOrCreateWindowId(GdkWindow* gdk_window) {
5353
return IdAllocator::kInvalidId;
5454
}
5555

56-
// First, try to read ID attached to the GObject
57-
gpointer data = g_object_get_data(G_OBJECT(gdk_window), kWindowIdKey);
56+
// The owning widget's ID first (see Window(void*)), then the GdkWindow's; an ID
57+
// found or allocated here goes on both, or a Window wrapper made later would
58+
// not match the widget its close gate and events are keyed on.
59+
gpointer owner = nullptr;
60+
gdk_window_get_user_data(gdk_window, &owner);
61+
GObject* widget = owner && GTK_IS_WIDGET(owner) ? G_OBJECT(owner) : nullptr;
62+
gpointer data = widget ? g_object_get_data(widget, kWindowIdKey) : nullptr;
63+
if (!data) data = g_object_get_data(G_OBJECT(gdk_window), kWindowIdKey);
5864
if (data) {
65+
g_object_set_data(G_OBJECT(gdk_window), kWindowIdKey, data);
66+
if (widget) g_object_set_data(widget, kWindowIdKey, data);
5967
WindowId id = static_cast<WindowId>(reinterpret_cast<uintptr_t>(data));
6068
// Cache it in the map for faster lookup next time
6169
std::lock_guard<std::mutex> lock(g_map_mutex);
@@ -77,6 +85,9 @@ static WindowId GetOrCreateWindowId(GdkWindow* gdk_window) {
7785
if (new_id != IdAllocator::kInvalidId) {
7886
g_object_set_data(G_OBJECT(gdk_window), kWindowIdKey,
7987
reinterpret_cast<gpointer>(static_cast<uintptr_t>(new_id)));
88+
if (widget)
89+
g_object_set_data(widget, kWindowIdKey,
90+
reinterpret_cast<gpointer>(static_cast<uintptr_t>(new_id)));
8091
std::lock_guard<std::mutex> lock(g_map_mutex);
8192
g_window_id_map[gdk_window] = new_id;
8293
}

‎tests/CMakeLists.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
391391
target_link_libraries(window_close_linux_test PRIVATE nativeapi Threads::Threads)
392392
add_test(NAME window_close_linux_test COMMAND window_close_linux_test)
393393
set_tests_properties(window_close_linux_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 10)
394+
add_executable(window_close_manager_first_linux_test window_close_manager_first_linux_test.cpp)
395+
target_link_libraries(window_close_manager_first_linux_test PRIVATE nativeapi)
396+
add_test(NAME window_close_manager_first_linux_test COMMAND window_close_manager_first_linux_test)
397+
set_tests_properties(window_close_manager_first_linux_test PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 10)
394398
endif()
395399

396400
if(APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// A window that WindowManager sees before any Window wraps it - the way a
2+
// Flutter app asks for its window with GetCurrent() - still gets its own
3+
// events and cancellable close requests. WindowManager keys windows by their
4+
// GdkWindow; the close gate checks the ID on the GtkWindow, so the two must
5+
// carry the same one. Private display, no input.
6+
#include <gtk/gtk.h>
7+
#include <iostream>
8+
#include "nativeapi.h"
9+
namespace {
10+
int failures = 0;
11+
void Check(bool ok, const char* label) {
12+
std::cout << (ok ? "PASS " : "FAIL ") << label << std::endl;
13+
failures += !ok;
14+
}
15+
void Drain() {
16+
while (g_main_context_pending(nullptr))
17+
g_main_context_iteration(nullptr, FALSE);
18+
}
19+
} // namespace
20+
int main() {
21+
if (!gtk_init_check(nullptr, nullptr))
22+
return 77;
23+
auto* widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
24+
g_object_ref_sink(widget);
25+
gtk_widget_realize(widget); // A real GdkWindow, not shown yet (as a Flutter runner's at start).
26+
27+
auto current = nativeapi::WindowManager::GetInstance().GetCurrent();
28+
Check(current && current->GetNativeObject() == widget, "GetCurrent finds the window");
29+
if (!current) return 1;
30+
nativeapi::Window wrapper(widget);
31+
Check(wrapper.GetId() == current->GetId(), "a wrapper of the widget has the same ID");
32+
33+
int requested = 0;
34+
current->AddListener<nativeapi::WindowCloseRequestedEvent>([&](const auto& event) {
35+
++requested;
36+
event.GetRequest()->Cancel();
37+
});
38+
Drain(); // The subscription is set up on the next main loop turn.
39+
gtk_window_close(GTK_WINDOW(widget));
40+
Drain();
41+
Check(requested == 1, "the close request reaches the window found through WindowManager");
42+
Check(!gtk_widget_in_destruction(widget), "and cancelling it keeps the window");
43+
44+
int titles = 0;
45+
current->AddListener<nativeapi::WindowPropertyChangedEvent>([&](const auto& event) {
46+
if (event.GetProperty() == nativeapi::WindowProperty::Title) ++titles;
47+
});
48+
Drain();
49+
gtk_window_set_title(GTK_WINDOW(widget), "renamed");
50+
Drain();
51+
Check(titles == 1, "its own property events arrive too");
52+
53+
gtk_widget_destroy(widget);
54+
g_object_unref(widget);
55+
std::cout << (failures ? "FAILED" : "OK") << std::endl;
56+
return failures ? 1 : 0;
57+
}

0 commit comments

Comments
 (0)