Skip to content

Commit 08aa20c

Browse files
committed
Refactor macOS window ID management and registry sync
Replaces static map-based window ID tracking with Objective-C associated objects for NSWindow, ensuring consistent and unique WindowId assignment. Updates window creation, wrapping, and retrieval logic to use associated IDs, and synchronizes the WindowRegistry with all NSWindows. Refactors swizzled NSWindow methods to resolve WindowId via registry, improving reliability and correctness of window event hooks.
1 parent 7e377a9 commit 08aa20c

File tree

2 files changed

+86
-84
lines changed

2 files changed

+86
-84
lines changed

src/platform/macos/window_macos.mm

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
#include <iostream>
2-
#include <mutex>
3-
#include <unordered_map>
42
#include "../../foundation/id_allocator.h"
53
#include "../../window.h"
64
#include "../../window_manager.h"
@@ -9,25 +7,54 @@
97

108
// Import Cocoa headers
119
#import <Cocoa/Cocoa.h>
10+
#import <objc/runtime.h>
11+
12+
// Static key for associated objects
13+
static const void* kWindowIdKey = &kWindowIdKey;
1214

1315
namespace nativeapi {
1416

1517
// Private implementation class
1618
class Window::Impl {
1719
public:
18-
Impl(NSWindow* window) : ns_window_(window) {}
20+
Impl(WindowId id, NSWindow* window) : id_(id), ns_window_(window) {}
21+
WindowId id_;
1922
NSWindow* ns_window_;
2023
};
2124

22-
Window::Window() {
23-
NSWindow* ns_window = [[NSWindow alloc] init];
24-
ns_window.styleMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled |
25-
NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
26-
pimpl_ = std::make_unique<Impl>(ns_window);
27-
}
25+
Window::Window() : Window(nullptr) {}
26+
27+
Window::Window(void* native_window) {
28+
NSWindow* ns_window = nullptr;
29+
WindowId id;
30+
31+
if (native_window == nullptr) {
32+
// Create new platform object
33+
id = IdAllocator::Allocate<Window>();
34+
ns_window = [[NSWindow alloc] init];
35+
ns_window.styleMask = NSWindowStyleMaskResizable | NSWindowStyleMaskTitled |
36+
NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
37+
// Store the ID as associated object
38+
objc_setAssociatedObject(ns_window, kWindowIdKey, [NSNumber numberWithUnsignedLongLong:id],
39+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
40+
} else {
41+
// Wrap existing platform object - check if it already has an ID
42+
ns_window = (__bridge NSWindow*)native_window;
43+
NSNumber* existingId = objc_getAssociatedObject(ns_window, kWindowIdKey);
44+
if (existingId) {
45+
// Use existing ID
46+
id = [existingId unsignedLongLongValue];
47+
} else {
48+
// Allocate new ID and store it
49+
id = IdAllocator::Allocate<Window>();
50+
objc_setAssociatedObject(ns_window, kWindowIdKey, [NSNumber numberWithUnsignedLongLong:id],
51+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
52+
}
53+
}
2854

29-
Window::Window(void* native_window)
30-
: pimpl_(std::make_unique<Impl>((__bridge NSWindow*)native_window)) {}
55+
// All initialization logic in one place
56+
pimpl_ = std::make_unique<Impl>(id, ns_window);
57+
}
3158

3259
Window::~Window() {}
3360

@@ -365,37 +392,7 @@
365392
void Window::StartResizing() {}
366393

367394
WindowId Window::GetId() const {
368-
if (!pimpl_ || !pimpl_->ns_window_) {
369-
return IdAllocator::kInvalidId;
370-
}
371-
372-
// Store the allocated ID in a static map to ensure consistency
373-
// Use void* as key to avoid hash function issues with Objective-C pointers
374-
static std::unordered_map<void*, WindowId> window_id_map;
375-
static std::mutex map_mutex;
376-
377-
void* window_ptr = (__bridge void*)pimpl_->ns_window_;
378-
std::lock_guard<std::mutex> lock(map_mutex);
379-
auto it = window_id_map.find(window_ptr);
380-
if (it != window_id_map.end()) {
381-
return it->second;
382-
}
383-
384-
// Allocate new ID using the IdAllocator
385-
WindowId new_id = IdAllocator::Allocate<Window>();
386-
if (new_id != IdAllocator::kInvalidId) {
387-
window_id_map[window_ptr] = new_id;
388-
389-
// Register window in registry (delayed registration)
390-
// This requires the Window to be managed by shared_ptr
391-
try {
392-
WindowRegistry::GetInstance().Add(new_id, const_cast<Window*>(this)->shared_from_this());
393-
} catch (const std::bad_weak_ptr&) {
394-
// Window not yet managed by shared_ptr, skip registration
395-
// Registration will happen when window is properly managed by shared_ptr
396-
}
397-
}
398-
return new_id;
395+
return pimpl_->id_;
399396
}
400397

401398
void* Window::GetNativeObjectInternal() const {

src/platform/macos/window_manager_macos.mm

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -46,28 +46,27 @@ - (void)na_swizzled_orderOut:(id)sender;
4646
@implementation NSWindow (NativeAPISwizzle)
4747

4848
- (void)na_swizzled_makeKeyAndOrderFront:(id)sender {
49-
// Invoke hook before showing
50-
nativeapi::WindowManager::GetInstance().InvokeWillShowHook([self windowNumber]);
51-
52-
// Defer calling original to the next runloop turn so Dart hook can run first
53-
// Guard with associated flag to avoid multiple schedules for the same call
54-
static const void* kNAWillShowScheduledKey = &kNAWillShowScheduledKey;
55-
id scheduled = objc_getAssociatedObject(self, kNAWillShowScheduledKey);
56-
if (!scheduled) {
57-
objc_setAssociatedObject(self, kNAWillShowScheduledKey, @YES,
58-
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
59-
dispatch_async(dispatch_get_main_queue(), ^{
60-
// Clear flag
61-
objc_setAssociatedObject(self, kNAWillShowScheduledKey, nil, OBJC_ASSOCIATION_ASSIGN);
62-
// Call original implementation (swapped)
63-
[self na_swizzled_makeKeyAndOrderFront:sender];
64-
});
65-
}
49+
// Ensure registry is in sync and then invoke hook with correct WindowId
50+
auto windows = nativeapi::WindowManager::GetInstance().GetAll();
51+
for (const auto& window : windows) {
52+
if (window->GetNativeObject() == (__bridge void*)self) {
53+
nativeapi::WindowManager::GetInstance().InvokeWillShowHook(window->GetId());
54+
break;
55+
}
56+
}
57+
// First call original implementation so properties like title are up-to-date
58+
[self na_swizzled_makeKeyAndOrderFront:sender];
6659
}
6760

6861
- (void)na_swizzled_orderOut:(id)sender {
69-
// Invoke hook before hiding
70-
nativeapi::WindowManager::GetInstance().InvokeWillHideHook([self windowNumber]);
62+
// Invoke hook before hiding, resolving id via registry without using windowNumber
63+
auto windows = nativeapi::WindowManager::GetInstance().GetAll();
64+
for (const auto& window : windows) {
65+
if (window->GetNativeObject() == (__bridge void*)self) {
66+
nativeapi::WindowManager::GetInstance().InvokeWillHideHook(window->GetId());
67+
break;
68+
}
69+
}
7170
// Call original implementation (swapped)
7271
[self na_swizzled_orderOut:sender];
7372
}
@@ -277,48 +276,54 @@ - (void)windowWillClose:(NSNotification*)notification {
277276
}
278277

279278
std::shared_ptr<Window> WindowManager::Get(WindowId id) {
280-
auto cached = WindowRegistry::GetInstance().Get(id);
281-
if (cached) {
282-
return cached;
283-
}
284-
NSArray* ns_windows = [[NSApplication sharedApplication] windows];
285-
for (NSWindow* ns_window in ns_windows) {
286-
if ([ns_window windowNumber] == id) {
287-
auto window = std::make_shared<Window>((__bridge void*)ns_window);
288-
WindowRegistry::GetInstance().Add(id, window);
289-
return window;
290-
}
279+
// First check if it's already in the registry
280+
auto window = WindowRegistry::GetInstance().Get(id);
281+
if (window) {
282+
return window;
291283
}
292-
return nullptr;
284+
285+
// If not found, ensure all NSWindows are registered and try again
286+
GetAll();
287+
return WindowRegistry::GetInstance().Get(id);
293288
}
294289

295290
std::vector<std::shared_ptr<Window>> WindowManager::GetAll() {
296-
std::vector<std::shared_ptr<Window>> windows;
297291
NSArray* ns_windows = [[NSApplication sharedApplication] windows];
292+
293+
// First, ensure all NSWindows are registered
298294
for (NSWindow* ns_window in ns_windows) {
299-
WindowId window_id = [ns_window windowNumber];
295+
// Create or get Window wrapper - this will handle ID assignment via associated object
296+
auto window = std::make_shared<Window>((__bridge void*)ns_window);
297+
WindowId window_id = window->GetId();
298+
299+
// Add to registry if not already present
300300
if (!WindowRegistry::GetInstance().Get(window_id)) {
301-
auto window = std::make_shared<Window>((__bridge void*)ns_window);
302301
WindowRegistry::GetInstance().Add(window_id, window);
303302
}
304303
}
305-
// Merge cached windows from registry to ensure consistency
306-
auto cached = WindowRegistry::GetInstance().GetAll();
307-
for (const auto& w : cached) {
308-
windows.push_back(w);
309-
}
310-
return windows;
304+
305+
// Then return all windows from registry (which now includes all NSWindows)
306+
return WindowRegistry::GetInstance().GetAll();
311307
}
312308

313309
std::shared_ptr<Window> WindowManager::GetCurrent() {
314310
NSApplication* app = [NSApplication sharedApplication];
315311
NSArray* ns_windows = [[NSApplication sharedApplication] windows];
316312
NSWindow* ns_window = [app mainWindow];
317-
if (ns_window == nil) {
313+
if (ns_window == nil && [ns_windows count] > 0) {
318314
ns_window = [ns_windows objectAtIndex:0];
319315
}
320316
if (ns_window != nil) {
321-
return Get([ns_window windowNumber]);
317+
// Create or get Window wrapper - this will handle ID retrieval via associated object
318+
auto window = std::make_shared<Window>((__bridge void*)ns_window);
319+
WindowId window_id = window->GetId();
320+
321+
// Ensure it's in the registry
322+
if (!WindowRegistry::GetInstance().Get(window_id)) {
323+
WindowRegistry::GetInstance().Add(window_id, window);
324+
}
325+
326+
return window;
322327
}
323328
return nullptr;
324329
}

0 commit comments

Comments
 (0)