Skip to content

Commit 0b3aafa

Browse files
fix(macos): dispatch NSWindow UI ops to main thread
Flutter calls Window::Show(), Focus(), etc. from the io.flutter.ui thread, not the main thread. macOS requires NSWindow ordering/visibility operations on the main thread — calling makeKeyAndOrderFront: from another thread triggers EXC_BREAKPOINT with 'Must only be used from the main thread'. All void methods that touch window ordering, visibility, key state, frame, or dragging now dispatch async to the main queue via RunOnMainThread(). Getters stay synchronous (they read state and don't crash off-main-thread).
1 parent 5453312 commit 0b3aafa

1 file changed

Lines changed: 78 additions & 56 deletions

File tree

packages/cnativeapi/cxx_impl/src/platform/macos/window_macos.mm

Lines changed: 78 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212
// Key for associated objects (used by both window_macos.mm and window_manager_macos.mm)
1313
const void* kWindowIdKey = &kWindowIdKey;
1414

15+
// NSWindow UI operations (ordering, visibility, key state) must run on the main
16+
// thread. Flutter calls these from the UI thread (io.flutter.ui), not the main
17+
// thread, so we dispatch async. The old window_manager package did the same.
18+
// ponytail: async means Show() returns before the window is actually visible;
19+
// acceptable — callers don't depend on synchronous visibility.
20+
static inline void RunOnMainThread(dispatch_block_t block) {
21+
if ([NSThread isMainThread]) {
22+
block();
23+
} else {
24+
dispatch_async(dispatch_get_main_queue(), block);
25+
}
26+
}
27+
1528
namespace nativeapi {
1629

1730
// Private implementation class
@@ -67,82 +80,85 @@
6780
Window::~Window() {}
6881

6982
void Window::Focus() {
70-
[pimpl_->ns_window_ makeKeyAndOrderFront:nil];
83+
NSWindow* w = pimpl_->ns_window_;
84+
RunOnMainThread(^{ [w makeKeyAndOrderFront:nil]; });
7185
}
7286

7387
void Window::Blur() {
74-
[pimpl_->ns_window_ orderBack:nil];
88+
NSWindow* w = pimpl_->ns_window_;
89+
RunOnMainThread(^{ [w orderBack:nil]; });
7590
}
7691

7792
bool Window::IsFocused() const {
7893
return [pimpl_->ns_window_ isKeyWindow];
7994
}
8095

8196
void Window::Show() {
82-
[pimpl_->ns_window_ setIsVisible:YES];
83-
// Panels receive key focus when shown but should not activate the app.
84-
if (![pimpl_->ns_window_ isKindOfClass:[NSPanel class]]) {
85-
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
86-
}
87-
[pimpl_->ns_window_ makeKeyAndOrderFront:nil];
97+
NSWindow* w = pimpl_->ns_window_;
98+
RunOnMainThread(^{
99+
[w setIsVisible:YES];
100+
// Panels receive key focus when shown but should not activate the app.
101+
if (![w isKindOfClass:[NSPanel class]]) {
102+
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
103+
}
104+
[w makeKeyAndOrderFront:nil];
105+
});
88106
}
89107

90108
void Window::ShowInactive() {
91-
[pimpl_->ns_window_ setIsVisible:YES];
92-
[pimpl_->ns_window_ orderFrontRegardless];
109+
NSWindow* w = pimpl_->ns_window_;
110+
RunOnMainThread(^{
111+
[w setIsVisible:YES];
112+
[w orderFrontRegardless];
113+
});
93114
}
94115

95116
void Window::Hide() {
96-
[pimpl_->ns_window_ setIsVisible:NO];
97-
[pimpl_->ns_window_ orderOut:nil];
117+
NSWindow* w = pimpl_->ns_window_;
118+
RunOnMainThread(^{
119+
[w setIsVisible:NO];
120+
[w orderOut:nil];
121+
});
98122
}
99123

100124
bool Window::IsVisible() const {
101125
return [pimpl_->ns_window_ isVisible];
102126
}
103127

104128
void Window::Maximize() {
105-
if (!IsMaximized()) {
106-
[pimpl_->ns_window_ zoom:nil];
107-
}
129+
NSWindow* w = pimpl_->ns_window_;
130+
RunOnMainThread(^{ if (![w isZoomed]) [w zoom:nil]; });
108131
}
109132

110133
void Window::Unmaximize() {
111-
if (IsMaximized()) {
112-
[pimpl_->ns_window_ zoom:nil];
113-
}
134+
NSWindow* w = pimpl_->ns_window_;
135+
RunOnMainThread(^{ if ([w isZoomed]) [w zoom:nil]; });
114136
}
115137

116138
bool Window::IsMaximized() const {
117139
return [pimpl_->ns_window_ isZoomed];
118140
}
119141

120142
void Window::Minimize() {
121-
if (!IsMinimized()) {
122-
[pimpl_->ns_window_ miniaturize:nil];
123-
}
143+
NSWindow* w = pimpl_->ns_window_;
144+
RunOnMainThread(^{ if (![w isMiniaturized]) [w miniaturize:nil]; });
124145
}
125146

126147
void Window::Restore() {
127-
if (IsMinimized()) {
128-
[pimpl_->ns_window_ deminiaturize:nil];
129-
}
148+
NSWindow* w = pimpl_->ns_window_;
149+
RunOnMainThread(^{ if ([w isMiniaturized]) [w deminiaturize:nil]; });
130150
}
131151

132152
bool Window::IsMinimized() const {
133153
return [pimpl_->ns_window_ isMiniaturized];
134154
}
135155

136156
void Window::SetFullScreen(bool is_full_screen) {
137-
if (is_full_screen) {
138-
if (!IsFullScreen()) {
139-
[pimpl_->ns_window_ toggleFullScreen:nil];
140-
}
141-
} else {
142-
if (IsFullScreen()) {
143-
[pimpl_->ns_window_ toggleFullScreen:nil];
144-
}
145-
}
157+
NSWindow* w = pimpl_->ns_window_;
158+
RunOnMainThread(^{
159+
bool fs = ([w styleMask] & NSWindowStyleMaskFullScreen) != 0;
160+
if (is_full_screen != fs) [w toggleFullScreen:nil];
161+
});
146162
}
147163

148164
bool Window::IsFullScreen() const {
@@ -153,10 +169,10 @@
153169
//// Color Window::GetBackgroundColor() const;
154170

155171
void Window::SetBounds(Rectangle bounds) {
156-
// Convert from topLeft coordinate system to bottom-left (macOS default)
172+
NSWindow* w = pimpl_->ns_window_;
157173
NSRect topLeftRect = NSMakeRect(bounds.x, bounds.y, bounds.width, bounds.height);
158174
NSRect nsRect = NSRectExt::bottomLeft(topLeftRect);
159-
[pimpl_->ns_window_ setFrame:nsRect display:YES];
175+
RunOnMainThread(^{ [w setFrame:nsRect display:YES]; });
160176
}
161177

162178
Rectangle Window::GetBounds() const {
@@ -169,15 +185,18 @@
169185
}
170186

171187
void Window::SetSize(Size size, bool animate) {
172-
NSRect frame = [pimpl_->ns_window_ frame];
173-
frame.origin.y += (frame.size.height - size.height);
174-
frame.size.width = size.width;
175-
frame.size.height = size.height;
176-
if (animate) {
177-
[[pimpl_->ns_window_ animator] setFrame:frame display:YES animate:YES];
178-
} else {
179-
[pimpl_->ns_window_ setFrame:frame display:YES];
180-
}
188+
NSWindow* w = pimpl_->ns_window_;
189+
RunOnMainThread(^{
190+
NSRect frame = [w frame];
191+
frame.origin.y += (frame.size.height - size.height);
192+
frame.size.width = size.width;
193+
frame.size.height = size.height;
194+
if (animate) {
195+
[[w animator] setFrame:frame display:YES animate:YES];
196+
} else {
197+
[w setFrame:frame display:YES];
198+
}
199+
});
181200
}
182201

183202
Size Window::GetSize() const {
@@ -338,12 +357,13 @@
338357
}
339358

340359
void Window::SetPosition(Point point) {
341-
// Convert from topLeft coordinate system to bottom-left (macOS default)
342-
// We need the window height to correctly convert the top-left position
343-
NSRect frame = [pimpl_->ns_window_ frame];
344-
CGPoint topLeftPoint = {point.x, point.y};
345-
NSPoint bottomLeft = NSPointExt::bottomLeftForWindow(topLeftPoint, frame.size.height);
346-
[pimpl_->ns_window_ setFrameOrigin:bottomLeft];
360+
NSWindow* w = pimpl_->ns_window_;
361+
RunOnMainThread(^{
362+
NSRect frame = [w frame];
363+
CGPoint topLeftPoint = {point.x, point.y};
364+
NSPoint bottomLeft = NSPointExt::bottomLeftForWindow(topLeftPoint, frame.size.height);
365+
[w setFrameOrigin:bottomLeft];
366+
});
347367
}
348368

349369
Point Window::GetPosition() const {
@@ -355,8 +375,8 @@
355375
}
356376

357377
void Window::Center() {
358-
// Use NSWindow's center method which automatically centers on the main screen
359-
[pimpl_->ns_window_ center];
378+
NSWindow* w = pimpl_->ns_window_;
379+
RunOnMainThread(^{ [w center]; });
360380
}
361381

362382
void Window::SetTitle(std::string title) {
@@ -525,10 +545,12 @@
525545
}
526546

527547
void Window::StartDragging() {
528-
NSWindow* window = pimpl_->ns_window_;
529-
if (window.currentEvent) {
530-
[window performWindowDragWithEvent:window.currentEvent];
531-
}
548+
NSWindow* w = pimpl_->ns_window_;
549+
RunOnMainThread(^{
550+
if (w.currentEvent) {
551+
[w performWindowDragWithEvent:w.currentEvent];
552+
}
553+
});
532554
}
533555

534556
void Window::StartResizing() {}

0 commit comments

Comments
 (0)