Skip to content

Commit 62aaba6

Browse files
committed
Add pre-show/hide window hooks to C API and Linux
1 parent 9feb63d commit 62aaba6

File tree

1 file changed

+141
-17
lines changed

1 file changed

+141
-17
lines changed

src/platform/linux/window_manager_linux.cpp

Lines changed: 141 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <mutex>
44
#include <string>
55
#include <unordered_map>
6+
#include <set>
67

78
#include "../../window.h"
89
#include "../../window_manager.h"
@@ -17,6 +18,13 @@ namespace nativeapi {
1718
static std::unordered_map<GdkWindow*, WindowId> g_window_id_map;
1819
static std::mutex g_map_mutex;
1920

21+
// Track widgets that have been hooked to avoid duplicate connections
22+
static std::set<GtkWidget*> g_hooked_widgets;
23+
static std::mutex g_hook_mutex;
24+
25+
// Flag to indicate if global swizzling has been installed
26+
static bool g_swizzle_installed = false;
27+
2028
// Helper function to manage mapping between GdkWindow pointers and WindowIds
2129
static WindowId GetOrCreateWindowId(GdkWindow* gdk_window) {
2230
if (!gdk_window) {
@@ -48,15 +56,64 @@ static GdkWindow* FindGdkWindowById(WindowId id) {
4856
return nullptr;
4957
}
5058

51-
// GTK signal callbacks to invoke hooks
59+
// Forward declarations for swizzling functions
60+
static void InstallShowHideHooks(GtkWidget* widget);
61+
static void InstallGlobalSwizzling();
62+
63+
// Signal emission hook for show signal
64+
static gboolean on_show_emission_hook(GSignalInvocationHint* ihint,
65+
guint n_param_values,
66+
const GValue* param_values,
67+
gpointer data) {
68+
(void)ihint;
69+
(void)n_param_values;
70+
(void)data;
71+
72+
GtkWidget* widget = GTK_WIDGET(g_value_get_object(&param_values[0]));
73+
if (widget && GTK_IS_WINDOW(widget)) {
74+
GdkWindow* gdk_window = gtk_widget_get_window(widget);
75+
if (gdk_window) {
76+
WindowId id = GetOrCreateWindowId(gdk_window);
77+
WindowManager::GetInstance().InvokeWillShowHook(id);
78+
}
79+
}
80+
81+
return TRUE; // Continue emission
82+
}
83+
84+
// Signal emission hook for hide signal
85+
static gboolean on_hide_emission_hook(GSignalInvocationHint* ihint,
86+
guint n_param_values,
87+
const GValue* param_values,
88+
gpointer data) {
89+
(void)ihint;
90+
(void)n_param_values;
91+
(void)data;
92+
93+
GtkWidget* widget = GTK_WIDGET(g_value_get_object(&param_values[0]));
94+
if (widget && GTK_IS_WINDOW(widget)) {
95+
GdkWindow* gdk_window = gtk_widget_get_window(widget);
96+
if (gdk_window) {
97+
WindowId id = GetOrCreateWindowId(gdk_window);
98+
WindowManager::GetInstance().InvokeWillHideHook(id);
99+
}
100+
}
101+
102+
return TRUE; // Continue emission
103+
}
104+
105+
// GTK signal callbacks to invoke hooks (used as fallback)
52106
static gboolean OnGtkMapEvent(GtkWidget* widget, GdkEvent* event, gpointer user_data) {
53107
(void)event;
54108
(void)user_data;
55-
auto& manager = WindowManager::GetInstance();
56-
GdkWindow* gdk_window = gtk_widget_get_window(widget);
57-
if (gdk_window) {
58-
WindowId id = GetOrCreateWindowId(gdk_window);
59-
manager.InvokeWillShowHook(id);
109+
110+
if (GTK_IS_WINDOW(widget)) {
111+
auto& manager = WindowManager::GetInstance();
112+
GdkWindow* gdk_window = gtk_widget_get_window(widget);
113+
if (gdk_window) {
114+
WindowId id = GetOrCreateWindowId(gdk_window);
115+
manager.InvokeWillShowHook(id);
116+
}
60117
}
61118
// Return FALSE to propagate event further
62119
return FALSE;
@@ -65,28 +122,88 @@ static gboolean OnGtkMapEvent(GtkWidget* widget, GdkEvent* event, gpointer user_
65122
static gboolean OnGtkUnmapEvent(GtkWidget* widget, GdkEvent* event, gpointer user_data) {
66123
(void)event;
67124
(void)user_data;
68-
auto& manager = WindowManager::GetInstance();
69-
GdkWindow* gdk_window = gtk_widget_get_window(widget);
70-
if (gdk_window) {
71-
WindowId id = GetOrCreateWindowId(gdk_window);
72-
manager.InvokeWillHideHook(id);
125+
126+
if (GTK_IS_WINDOW(widget)) {
127+
auto& manager = WindowManager::GetInstance();
128+
GdkWindow* gdk_window = gtk_widget_get_window(widget);
129+
if (gdk_window) {
130+
WindowId id = GetOrCreateWindowId(gdk_window);
131+
manager.InvokeWillHideHook(id);
132+
}
73133
}
74134
// Return FALSE to propagate event further
75135
return FALSE;
76136
}
77137

78-
// Private implementation for Linux (stub for now)
138+
// Install hooks for a specific widget
139+
static void InstallShowHideHooks(GtkWidget* widget) {
140+
if (!widget || !GTK_IS_WINDOW(widget)) {
141+
return;
142+
}
143+
144+
std::lock_guard<std::mutex> lock(g_hook_mutex);
145+
146+
// Check if already hooked
147+
if (g_hooked_widgets.find(widget) != g_hooked_widgets.end()) {
148+
return;
149+
}
150+
151+
// Connect map/unmap events as fallback
152+
g_signal_connect(G_OBJECT(widget), "map-event", G_CALLBACK(OnGtkMapEvent), nullptr);
153+
g_signal_connect(G_OBJECT(widget), "unmap-event", G_CALLBACK(OnGtkUnmapEvent), nullptr);
154+
155+
g_hooked_widgets.insert(widget);
156+
}
157+
158+
// Install global swizzling using signal emission hooks
159+
static void InstallGlobalSwizzling() {
160+
if (g_swizzle_installed) {
161+
return;
162+
}
163+
164+
// Get the show and hide signal IDs for GtkWidget
165+
guint show_signal_id = g_signal_lookup("show", GTK_TYPE_WIDGET);
166+
guint hide_signal_id = g_signal_lookup("hide", GTK_TYPE_WIDGET);
167+
168+
if (show_signal_id != 0) {
169+
// Add emission hook for show signal
170+
g_signal_add_emission_hook(show_signal_id, 0, on_show_emission_hook, nullptr, nullptr);
171+
}
172+
173+
if (hide_signal_id != 0) {
174+
// Add emission hook for hide signal
175+
g_signal_add_emission_hook(hide_signal_id, 0, on_hide_emission_hook, nullptr, nullptr);
176+
}
177+
178+
g_swizzle_installed = true;
179+
}
180+
181+
// Private implementation for Linux
79182
class WindowManager::Impl {
80183
public:
81184
Impl(WindowManager* manager) : manager_(manager) {}
82185
~Impl() {}
83186

84187
void SetupEventMonitoring() {
85-
// TODO: Implement Linux-specific event monitoring using GTK signals
188+
// Install global swizzling for show/hide interception
189+
InstallGlobalSwizzling();
190+
191+
// Monitor all existing windows
192+
GdkDisplay* display = gdk_display_get_default();
193+
if (display) {
194+
GList* toplevels = gtk_window_list_toplevels();
195+
for (GList* l = toplevels; l != nullptr; l = l->next) {
196+
GtkWindow* gtk_window = GTK_WINDOW(l->data);
197+
InstallShowHideHooks(GTK_WIDGET(gtk_window));
198+
}
199+
g_list_free(toplevels);
200+
}
86201
}
87202

88203
void CleanupEventMonitoring() {
89-
// TODO: Implement Linux-specific cleanup
204+
// Clear hooked widgets set
205+
std::lock_guard<std::mutex> lock(g_hook_mutex);
206+
g_hooked_widgets.clear();
90207
}
91208

92209
private:
@@ -250,9 +367,8 @@ std::shared_ptr<Window> WindowManager::Create(const WindowOptions& options) {
250367
return nullptr;
251368
}
252369

253-
// Connect map/unmap events to invoke hooks
254-
g_signal_connect(G_OBJECT(gtk_window), "map-event", G_CALLBACK(OnGtkMapEvent), nullptr);
255-
g_signal_connect(G_OBJECT(gtk_window), "unmap-event", G_CALLBACK(OnGtkUnmapEvent), nullptr);
370+
// Install show/hide hooks for this window
371+
InstallShowHideHooks(gtk_window);
256372

257373
// Set window properties from options
258374
if (!options.title.empty()) {
@@ -326,10 +442,18 @@ bool WindowManager::Destroy(WindowId id) {
326442

327443
void WindowManager::SetWillShowHook(std::optional<WindowWillShowHook> hook) {
328444
pimpl_->will_show_hook_ = std::move(hook);
445+
if (pimpl_->will_show_hook_) {
446+
// Ensure global swizzling is installed when hook is set
447+
InstallGlobalSwizzling();
448+
}
329449
}
330450

331451
void WindowManager::SetWillHideHook(std::optional<WindowWillHideHook> hook) {
332452
pimpl_->will_hide_hook_ = std::move(hook);
453+
if (pimpl_->will_hide_hook_) {
454+
// Ensure global swizzling is installed when hook is set
455+
InstallGlobalSwizzling();
456+
}
333457
}
334458

335459
void WindowManager::InvokeWillShowHook(WindowId id) {

0 commit comments

Comments
 (0)