Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions src/capi/window_manager_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ void native_window_manager_set_will_hide_hook(native_window_manager_set_will_hid
}
}

void native_window_manager_set_will_close_hook(native_window_manager_set_will_close_hook_callback_t hook, void* hook_user_data) {
try {
std::optional<std::function<void(unsigned int)>> hook_cpp;
if (hook) {
hook_cpp = [hook, hook_user_data](unsigned int arg0) { hook(arg0, hook_user_data); };
}
nativeapi::WindowManager::GetInstance().SetWillCloseHook(hook_cpp);
return;
} catch (...) {
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_manager_set_will_close_hook");
return;
}
}

bool native_window_manager_has_will_show_hook(void) {
try {
return nativeapi::WindowManager::GetInstance().HasWillShowHook();
Expand All @@ -104,6 +118,15 @@ bool native_window_manager_has_will_hide_hook(void) {
}
}

bool native_window_manager_has_will_close_hook(void) {
try {
return nativeapi::WindowManager::GetInstance().HasWillCloseHook();
} catch (...) {
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_manager_has_will_close_hook");
return false;
}
}

void native_window_manager_handle_will_show(native_window_id_t id) {
try {
nativeapi::WindowManager::GetInstance().HandleWillShow(id);
Expand All @@ -124,6 +147,16 @@ void native_window_manager_handle_will_hide(native_window_id_t id) {
}
}

void native_window_manager_handle_will_close(native_window_id_t id) {
try {
nativeapi::WindowManager::GetInstance().HandleWillClose(id);
return;
} catch (...) {
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_manager_handle_will_close");
return;
}
}

bool native_window_manager_call_original_show(native_window_id_t id) {
try {
return nativeapi::WindowManager::GetInstance().CallOriginalShow(id);
Expand All @@ -142,6 +175,15 @@ bool native_window_manager_call_original_hide(native_window_id_t id) {
}
}

bool native_window_manager_call_original_close(native_window_id_t id) {
try {
return nativeapi::WindowManager::GetInstance().CallOriginalClose(id);
} catch (...) {
fprintf(stderr, "[nativeapi] %s: unexpected exception\n", "native_window_manager_call_original_close");
return false;
}
}

native_listener_id_t native_window_manager_add_listener(native_window_event_callback_t callback, void* user_data) {
if (!callback) {
return 0;
Expand Down
14 changes: 14 additions & 0 deletions src/capi/window_manager_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ typedef void (*native_window_manager_set_will_show_hook_callback_t)(unsigned int

typedef void (*native_window_manager_set_will_hide_hook_callback_t)(unsigned int arg0, void* user_data);

typedef void (*native_window_manager_set_will_close_hook_callback_t)(unsigned int arg0, void* user_data);

/// Caller owns the returned handle; release it with native_window_free().
FFI_PLUGIN_EXPORT
native_window_t native_window_manager_get(native_window_id_t id);
Expand All @@ -40,24 +42,36 @@ void native_window_manager_set_will_show_hook(native_window_manager_set_will_sho
FFI_PLUGIN_EXPORT
void native_window_manager_set_will_hide_hook(native_window_manager_set_will_hide_hook_callback_t hook, void* hook_user_data);

FFI_PLUGIN_EXPORT
void native_window_manager_set_will_close_hook(native_window_manager_set_will_close_hook_callback_t hook, void* hook_user_data);

FFI_PLUGIN_EXPORT
bool native_window_manager_has_will_show_hook(void);

FFI_PLUGIN_EXPORT
bool native_window_manager_has_will_hide_hook(void);

FFI_PLUGIN_EXPORT
bool native_window_manager_has_will_close_hook(void);

FFI_PLUGIN_EXPORT
void native_window_manager_handle_will_show(native_window_id_t id);

FFI_PLUGIN_EXPORT
void native_window_manager_handle_will_hide(native_window_id_t id);

FFI_PLUGIN_EXPORT
void native_window_manager_handle_will_close(native_window_id_t id);

FFI_PLUGIN_EXPORT
bool native_window_manager_call_original_show(native_window_id_t id);

FFI_PLUGIN_EXPORT
bool native_window_manager_call_original_hide(native_window_id_t id);

FFI_PLUGIN_EXPORT
bool native_window_manager_call_original_close(native_window_id_t id);

/// Registers @p callback for every WindowEvent this WindowManager emits.
/// @return the listener id, or NATIVE_INVALID_LISTENER_ID on failure.
FFI_PLUGIN_EXPORT
Expand Down
65 changes: 63 additions & 2 deletions src/platform/linux/window_manager_linux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,27 @@ static gboolean on_hide_emission_hook(GSignalInvocationHint* ihint,
return TRUE; // Continue emission
}

// Signal emission hook for delete-event signal
static gboolean on_delete_event_emission_hook(GSignalInvocationHint* ihint,
guint n_param_values,
const GValue* param_values,
gpointer data) {
(void)ihint;
(void)n_param_values;
(void)data;

GtkWidget* widget = GTK_WIDGET(g_value_get_object(&param_values[0]));
if (widget && GTK_IS_WINDOW(widget)) {
GdkWindow* gdk_window = gtk_widget_get_window(widget);
if (gdk_window) {
WindowId id = GetOrCreateWindowId(gdk_window);
WindowManager::GetInstance().HandleWillClose(id);
}
}

return TRUE; // Continue emission
}

// GTK signal callbacks to invoke hooks (used as fallback)
static gboolean OnGtkMapEvent(GtkWidget* widget, GdkEvent* event, gpointer user_data) {
(void)event;
Expand Down Expand Up @@ -181,9 +202,10 @@ static void InstallGlobalSwizzling() {
return;
}

// Get the show and hide signal IDs for GtkWidget
// Get the show, hide, and delete-event signal IDs for GtkWidget
guint show_signal_id = g_signal_lookup("show", GTK_TYPE_WIDGET);
guint hide_signal_id = g_signal_lookup("hide", GTK_TYPE_WIDGET);
guint delete_event_signal_id = g_signal_lookup("delete-event", GTK_TYPE_WIDGET);

if (show_signal_id != 0) {
// Add emission hook for show signal
Expand All @@ -195,6 +217,11 @@ static void InstallGlobalSwizzling() {
g_signal_add_emission_hook(hide_signal_id, 0, on_hide_emission_hook, nullptr, nullptr);
}

if (delete_event_signal_id != 0) {
// Add emission hook for delete-event signal
g_signal_add_emission_hook(delete_event_signal_id, 0, on_delete_event_emission_hook, nullptr, nullptr);
}

g_swizzle_installed = true;
}

Expand Down Expand Up @@ -228,9 +255,10 @@ class WindowManager::Impl {

private:
WindowManager* manager_;
// Optional pre-show/hide hooks
// Optional pre-show/hide/close hooks
std::optional<WindowManager::WindowWillShowHook> will_show_hook_;
std::optional<WindowManager::WindowWillHideHook> will_hide_hook_;
std::optional<WindowManager::WindowWillCloseHook> will_close_hook_;

friend class WindowManager;
};
Expand Down Expand Up @@ -372,6 +400,14 @@ void WindowManager::SetWillHideHook(std::optional<WindowWillHideHook> hook) {
}
}

void WindowManager::SetWillCloseHook(std::optional<WindowWillCloseHook> hook) {
pimpl_->will_close_hook_ = std::move(hook);
if (pimpl_->will_close_hook_) {
// Ensure global swizzling is installed when hook is set
InstallGlobalSwizzling();
}
}

bool WindowManager::HasWillShowHook() const {
return pimpl_->will_show_hook_.has_value();
}
Expand All @@ -380,6 +416,10 @@ bool WindowManager::HasWillHideHook() const {
return pimpl_->will_hide_hook_.has_value();
}

bool WindowManager::HasWillCloseHook() const {
return pimpl_->will_close_hook_.has_value();
}

void WindowManager::HandleWillShow(WindowId id) {
if (pimpl_->will_show_hook_) {
(*pimpl_->will_show_hook_)(id);
Expand All @@ -392,6 +432,12 @@ void WindowManager::HandleWillHide(WindowId id) {
}
}

void WindowManager::HandleWillClose(WindowId id) {
if (pimpl_->will_close_hook_) {
(*pimpl_->will_close_hook_)(id);
}
}

bool WindowManager::CallOriginalShow(WindowId id) {
GdkWindow* gdk_window = FindGdkWindowById(id);
if (!gdk_window) {
Expand All @@ -414,6 +460,21 @@ bool WindowManager::CallOriginalHide(WindowId id) {
return true;
}

bool WindowManager::CallOriginalClose(WindowId id) {
GdkWindow* gdk_window = FindGdkWindowById(id);
if (!gdk_window) {
return false;
}

// On Linux, destroy the underlying GtkWidget to close the window
GtkWidget* widget = gtk_widget_get_toplevel(GTK_WIDGET(gdk_window));
if (widget && GTK_IS_WINDOW(widget)) {
gtk_widget_destroy(widget);
return true;
}
return false;
}

void WindowManager::StartEventListening() {
pimpl_->StartEventListening();
}
Expand Down
65 changes: 64 additions & 1 deletion src/platform/macos/window_manager_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@
WindowManager* manager_;
NativeAPIWindowManagerDelegate* delegate_;

// Optional pre-show/hide hooks
// Optional pre-show/hide/close hooks
std::optional<WindowManager::WindowWillShowHook> will_show_hook_;
std::optional<WindowManager::WindowWillHideHook> will_hide_hook_;
std::optional<WindowManager::WindowWillCloseHook> will_close_hook_;

friend class WindowManager;
};
Expand All @@ -44,6 +45,7 @@
@interface NSWindow (NativeAPISwizzle)
- (void)na_swizzled_makeKeyAndOrderFront:(id)sender;
- (void)na_swizzled_orderOut:(id)sender;
- (void)na_swizzled_performClose:(id)sender;
@end

@implementation NSWindow (NativeAPISwizzle)
Expand Down Expand Up @@ -79,6 +81,22 @@ - (void)na_swizzled_orderOut:(id)sender {
[self na_swizzled_orderOut:sender];
}

- (void)na_swizzled_performClose:(id)sender {
// Resolve window id and handle hook if present
if (nativeapi::WindowManager::GetInstance().HasWillCloseHook()) {
auto windows = nativeapi::WindowManager::GetInstance().GetAll();
for (const auto& window : windows) {
if (window->GetNativeObject() == (__bridge void*)self) {
nativeapi::WindowManager::GetInstance().HandleWillClose(window->GetId());
// Hook handles all logic; never call original here
return;
}
}
}
// No window found in registry, call original implementation (swapped)
[self na_swizzled_performClose:sender];
}

@end

static void NativeAPIInstallNSWindowWillShowSwizzleOnce() {
Expand Down Expand Up @@ -109,6 +127,20 @@ static void NativeAPIInstallNSWindowWillHideSwizzleOnce() {
});
}

static void NativeAPIInstallNSWindowWillCloseSwizzleOnce() {
static dispatch_once_t onceTokenClose;
dispatch_once(&onceTokenClose, ^{
Class cls = [NSWindow class];
SEL originalSel = @selector(performClose:);
SEL swizzledSel = @selector(na_swizzled_performClose:);
Method original = class_getInstanceMethod(cls, originalSel);
Method swizzled = class_getInstanceMethod(cls, swizzledSel);
if (original && swizzled) {
method_exchangeImplementations(original, swizzled);
}
});
}

// Objective-C delegate class to handle NSWindow notifications
@interface NativeAPIWindowManagerDelegate : NSObject
@property(nonatomic, assign) void* impl; // Use void* instead of private class
Expand Down Expand Up @@ -341,6 +373,13 @@ - (void)windowWillClose:(NSNotification*)notification {
}
}

void WindowManager::SetWillCloseHook(std::optional<WindowWillCloseHook> hook) {
pimpl_->will_close_hook_ = std::move(hook);
if (pimpl_->will_close_hook_) {
NativeAPIInstallNSWindowWillCloseSwizzleOnce();
}
}

bool WindowManager::HasWillShowHook() const {
return pimpl_->will_show_hook_.has_value();
}
Expand All @@ -349,6 +388,10 @@ - (void)windowWillClose:(NSNotification*)notification {
return pimpl_->will_hide_hook_.has_value();
}

bool WindowManager::HasWillCloseHook() const {
return pimpl_->will_close_hook_.has_value();
}

void WindowManager::HandleWillShow(WindowId id) {
if (pimpl_->will_show_hook_) {
(*pimpl_->will_show_hook_)(id);
Expand All @@ -361,6 +404,12 @@ - (void)windowWillClose:(NSNotification*)notification {
}
}

void WindowManager::HandleWillClose(WindowId id) {
if (pimpl_->will_close_hook_) {
(*pimpl_->will_close_hook_)(id);
}
}

bool WindowManager::CallOriginalShow(WindowId id) {
auto window = Get(id);
if (!window) {
Expand Down Expand Up @@ -389,6 +438,20 @@ - (void)windowWillClose:(NSNotification*)notification {
return true;
}

bool WindowManager::CallOriginalClose(WindowId id) {
auto window = Get(id);
if (!window) {
return false;
}
void* native = window->GetNativeObject();
if (!native) {
return false;
}
NSWindow* ns_window = (__bridge NSWindow*)native;
[ns_window na_swizzled_performClose:nil];
return true;
}

void WindowManager::StartEventListening() {
pimpl_->StartEventListening();
}
Expand Down
Loading