Skip to content

Commit 0285b9b

Browse files
committed
Add pre-show/hide window hooks to C API and macOS
1 parent 30850ac commit 0285b9b

File tree

3 files changed

+156
-4
lines changed

3 files changed

+156
-4
lines changed

src/capi/window_manager_c.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "window_manager_c.h"
22
#include <memory>
33
#include <mutex>
4+
#include <optional>
45
#include <unordered_map>
56
#include <vector>
67
#include "../window_manager.h"
@@ -137,6 +138,13 @@ class CEventListener {
137138

138139
static std::unique_ptr<CEventListener> g_event_listener;
139140

141+
// Hook callbacks (C API)
142+
static std::mutex g_hook_mutex;
143+
static native_window_will_show_callback_t g_will_show_cb = nullptr;
144+
static void* g_will_show_ud = nullptr;
145+
static native_window_will_hide_callback_t g_will_hide_cb = nullptr;
146+
static void* g_will_hide_ud = nullptr;
147+
140148
// Window manager operations
141149
FFI_PLUGIN_EXPORT
142150
native_window_t native_window_manager_create(const native_window_options_t* options) {
@@ -282,4 +290,50 @@ void native_window_manager_shutdown(void) {
282290

283291
// Note: We don't explicitly destroy the WindowManager singleton
284292
// as it will be cleaned up automatically when the application exits
293+
}
294+
295+
FFI_PLUGIN_EXPORT
296+
void native_window_manager_set_will_show_hook(native_window_will_show_callback_t callback,
297+
void* user_data) {
298+
std::lock_guard<std::mutex> lock(g_hook_mutex);
299+
g_will_show_cb = callback;
300+
g_will_show_ud = user_data;
301+
302+
auto& manager = WindowManager::GetInstance();
303+
if (callback == nullptr) {
304+
manager.SetWillShowHook(std::nullopt);
305+
return;
306+
}
307+
308+
// Bridge C callback through C++ hook
309+
manager.SetWillShowHook([cb = callback, ud = user_data](WindowId id) {
310+
try {
311+
cb(id, ud);
312+
} catch (...) {
313+
// Swallow exceptions to avoid unwinding across API boundary
314+
}
315+
});
316+
}
317+
318+
FFI_PLUGIN_EXPORT
319+
void native_window_manager_set_will_hide_hook(native_window_will_hide_callback_t callback,
320+
void* user_data) {
321+
std::lock_guard<std::mutex> lock(g_hook_mutex);
322+
g_will_hide_cb = callback;
323+
g_will_hide_ud = user_data;
324+
325+
auto& manager = WindowManager::GetInstance();
326+
if (callback == nullptr) {
327+
manager.SetWillHideHook(std::nullopt);
328+
return;
329+
}
330+
331+
// Bridge C callback through C++ hook
332+
manager.SetWillHideHook([cb = callback, ud = user_data](WindowId id) {
333+
try {
334+
cb(id, ud);
335+
} catch (...) {
336+
// Swallow exceptions to avoid unwinding across API boundary
337+
}
338+
});
285339
}

src/capi/window_manager_c.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,33 @@ bool native_window_manager_unregister_event_callback(int registration_id);
117117
FFI_PLUGIN_EXPORT
118118
void native_window_manager_shutdown(void);
119119

120+
/**
121+
* Hooks called BEFORE a native window is shown/hidden.
122+
* Passing NULL clears the corresponding hook.
123+
*/
124+
typedef void (*native_window_will_show_callback_t)(native_window_id_t window_id, void* user_data);
125+
typedef void (*native_window_will_hide_callback_t)(native_window_id_t window_id, void* user_data);
126+
127+
/**
128+
* Set (or clear) the "will show" hook.
129+
* @param callback Function called before window is shown (e.g., makeKeyAndOrderFront: on macOS).
130+
* NULL to clear.
131+
* @param user_data Opaque pointer passed back to callback.
132+
*/
133+
FFI_PLUGIN_EXPORT
134+
void native_window_manager_set_will_show_hook(native_window_will_show_callback_t callback,
135+
void* user_data);
136+
137+
/**
138+
* Set (or clear) the "will hide" hook.
139+
* @param callback Function called before window is hidden (e.g., orderOut: on macOS). NULL to
140+
* clear.
141+
* @param user_data Opaque pointer passed back to callback.
142+
*/
143+
FFI_PLUGIN_EXPORT
144+
void native_window_manager_set_will_hide_hook(native_window_will_hide_callback_t callback,
145+
void* user_data);
146+
120147
#ifdef __cplusplus
121148
}
122149
#endif

src/platform/macos/window_manager_macos.mm

Lines changed: 75 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import <Cocoa/Cocoa.h>
2+
#import <objc/runtime.h>
23
#include <cstring>
34
#include <iostream>
45
#include <string>
@@ -23,10 +24,70 @@
2324
private:
2425
WindowManager* manager_;
2526
NativeAPIWindowManagerDelegate* delegate_;
27+
28+
// Optional pre-show/hide hooks
29+
std::optional<WindowManager::WindowWillShowHook> will_show_hook_;
30+
std::optional<WindowManager::WindowWillHideHook> will_hide_hook_;
31+
32+
friend class WindowManager;
2633
};
2734

2835
} // namespace nativeapi
2936

37+
// MARK: - NSWindow Swizzling
38+
39+
// Swizzled implementations call into WindowManager hooks, then forward to original implementations
40+
@interface NSWindow (NativeAPISwizzle)
41+
- (void)na_swizzled_makeKeyAndOrderFront:(id)sender;
42+
- (void)na_swizzled_orderOut:(id)sender;
43+
@end
44+
45+
@implementation NSWindow (NativeAPISwizzle)
46+
47+
- (void)na_swizzled_makeKeyAndOrderFront:(id)sender {
48+
// Invoke hook before showing
49+
nativeapi::WindowManager::GetInstance().InvokeWillShowHook([self windowNumber]);
50+
// Call original implementation (swapped)
51+
[self na_swizzled_makeKeyAndOrderFront:sender];
52+
}
53+
54+
- (void)na_swizzled_orderOut:(id)sender {
55+
// Invoke hook before hiding
56+
nativeapi::WindowManager::GetInstance().InvokeWillHideHook([self windowNumber]);
57+
// Call original implementation (swapped)
58+
[self na_swizzled_orderOut:sender];
59+
}
60+
61+
@end
62+
63+
static void NativeAPIInstallNSWindowWillShowSwizzleOnce() {
64+
static dispatch_once_t onceTokenShow;
65+
dispatch_once(&onceTokenShow, ^{
66+
Class cls = [NSWindow class];
67+
SEL originalSel = @selector(makeKeyAndOrderFront:);
68+
SEL swizzledSel = @selector(na_swizzled_makeKeyAndOrderFront:);
69+
Method original = class_getInstanceMethod(cls, originalSel);
70+
Method swizzled = class_getInstanceMethod(cls, swizzledSel);
71+
if (original && swizzled) {
72+
method_exchangeImplementations(original, swizzled);
73+
}
74+
});
75+
}
76+
77+
static void NativeAPIInstallNSWindowWillHideSwizzleOnce() {
78+
static dispatch_once_t onceTokenHide;
79+
dispatch_once(&onceTokenHide, ^{
80+
Class cls = [NSWindow class];
81+
SEL originalSel = @selector(orderOut:);
82+
SEL swizzledSel = @selector(na_swizzled_orderOut:);
83+
Method original = class_getInstanceMethod(cls, originalSel);
84+
Method swizzled = class_getInstanceMethod(cls, swizzledSel);
85+
if (original && swizzled) {
86+
method_exchangeImplementations(original, swizzled);
87+
}
88+
});
89+
}
90+
3091
// Objective-C delegate class to handle NSWindow notifications
3192
@interface NativeAPIWindowManagerDelegate : NSObject
3293
@property(nonatomic, assign) void* impl; // Use void* instead of private class
@@ -286,19 +347,29 @@ - (void)windowWillClose:(NSNotification*)notification {
286347
}
287348

288349
void WindowManager::SetWillShowHook(std::optional<WindowWillShowHook> hook) {
289-
// Empty implementation
350+
pimpl_->will_show_hook_ = std::move(hook);
351+
if (pimpl_->will_show_hook_) {
352+
NativeAPIInstallNSWindowWillShowSwizzleOnce();
353+
}
290354
}
291355

292356
void WindowManager::SetWillHideHook(std::optional<WindowWillHideHook> hook) {
293-
// Empty implementation
357+
pimpl_->will_hide_hook_ = std::move(hook);
358+
if (pimpl_->will_hide_hook_) {
359+
NativeAPIInstallNSWindowWillHideSwizzleOnce();
360+
}
294361
}
295362

296363
void WindowManager::InvokeWillShowHook(WindowId id) {
297-
// Empty implementation
364+
if (pimpl_->will_show_hook_) {
365+
(*pimpl_->will_show_hook_)(id);
366+
}
298367
}
299368

300369
void WindowManager::InvokeWillHideHook(WindowId id) {
301-
// Empty implementation
370+
if (pimpl_->will_hide_hook_) {
371+
(*pimpl_->will_hide_hook_)(id);
372+
}
302373
}
303374

304375
} // namespace nativeapi

0 commit comments

Comments
 (0)