app: [macOS] defer Window destroy to after window close

The windowWillClose callback is too soon to destroy our Window:
at least draw callbacks may be called after windowWillClose but
before the window is gone. This change moves cleanup to the
viewDidMoveToWindow callback where we're sure the NSView is no longer
active.

Fixes: https://todo.sr.ht/~eliasnaur/gio/466
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2022-12-13 21:59:52 -06:00
parent 98f098f53f
commit 513250122c
2 changed files with 45 additions and 41 deletions
+40 -35
View File
@@ -186,6 +186,11 @@ static CFTypeRef layerForView(CFTypeRef viewRef) {
return (__bridge CFTypeRef)view.layer; return (__bridge CFTypeRef)view.layer;
} }
static CFTypeRef windowForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.window;
}
static void raiseWindow(CFTypeRef windowRef) { static void raiseWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef; NSWindow* window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil]; [window makeKeyAndOrderFront:nil];
@@ -237,7 +242,6 @@ type ViewEvent struct {
type window struct { type window struct {
view C.CFTypeRef view C.CFTypeRef
window C.CFTypeRef
w *callbacks w *callbacks
stage system.Stage stage system.Stage
displayLink *displayLink displayLink *displayLink
@@ -305,7 +309,7 @@ func (w *window) WriteClipboard(s string) {
} }
func (w *window) updateWindowMode() { func (w *window) updateWindowMode() {
style := int(C.getWindowStyleMask(w.window)) style := int(C.getWindowStyleMask(C.windowForView(w.view)))
if style&C.NSWindowStyleMaskFullScreen != 0 { if style&C.NSWindowStyleMaskFullScreen != 0 {
w.config.Mode = Fullscreen w.config.Mode = Fullscreen
} else { } else {
@@ -321,70 +325,71 @@ func (w *window) Configure(options []Option) {
w.updateWindowMode() w.updateWindowMode()
cnf := w.config cnf := w.config
cnf.apply(cfg, options) cnf.apply(cfg, options)
window := C.windowForView(w.view)
switch cnf.Mode { switch cnf.Mode {
case Fullscreen: case Fullscreen:
switch prev.Mode { switch prev.Mode {
case Fullscreen: case Fullscreen:
case Minimized: case Minimized:
C.unhideWindow(w.window) C.unhideWindow(window)
fallthrough fallthrough
default: default:
w.config.Mode = Fullscreen w.config.Mode = Fullscreen
C.toggleFullScreen(w.window) C.toggleFullScreen(window)
} }
case Minimized: case Minimized:
switch prev.Mode { switch prev.Mode {
case Minimized, Fullscreen: case Minimized, Fullscreen:
default: default:
w.config.Mode = Minimized w.config.Mode = Minimized
C.hideWindow(w.window) C.hideWindow(window)
} }
case Maximized: case Maximized:
switch prev.Mode { switch prev.Mode {
case Fullscreen: case Fullscreen:
case Minimized: case Minimized:
C.unhideWindow(w.window) C.unhideWindow(window)
fallthrough fallthrough
default: default:
w.config.Mode = Maximized w.config.Mode = Maximized
w.setTitle(prev, cnf) w.setTitle(prev, cnf)
if C.isWindowZoomed(w.window) == 0 { if C.isWindowZoomed(window) == 0 {
C.zoomWindow(w.window) C.zoomWindow(window)
} }
} }
case Windowed: case Windowed:
switch prev.Mode { switch prev.Mode {
case Fullscreen: case Fullscreen:
C.toggleFullScreen(w.window) C.toggleFullScreen(window)
case Minimized: case Minimized:
C.unhideWindow(w.window) C.unhideWindow(window)
case Maximized: case Maximized:
} }
w.config.Mode = Windowed w.config.Mode = Windowed
if C.isWindowZoomed(w.window) != 0 { if C.isWindowZoomed(window) != 0 {
C.zoomWindow(w.window) C.zoomWindow(window)
} }
w.setTitle(prev, cnf) w.setTitle(prev, cnf)
if prev.Size != cnf.Size { if prev.Size != cnf.Size {
w.config.Size = cnf.Size w.config.Size = cnf.Size
cnf.Size = cnf.Size.Div(int(screenScale)) cnf.Size = cnf.Size.Div(int(screenScale))
C.setSize(w.window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y)) C.setSize(window, C.CGFloat(cnf.Size.X), C.CGFloat(cnf.Size.Y))
} }
if prev.MinSize != cnf.MinSize { if prev.MinSize != cnf.MinSize {
w.config.MinSize = cnf.MinSize w.config.MinSize = cnf.MinSize
cnf.MinSize = cnf.MinSize.Div(int(screenScale)) cnf.MinSize = cnf.MinSize.Div(int(screenScale))
C.setMinSize(w.window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y)) C.setMinSize(window, C.CGFloat(cnf.MinSize.X), C.CGFloat(cnf.MinSize.Y))
} }
if prev.MaxSize != cnf.MaxSize { if prev.MaxSize != cnf.MaxSize {
w.config.MaxSize = cnf.MaxSize w.config.MaxSize = cnf.MaxSize
cnf.MaxSize = cnf.MaxSize.Div(int(screenScale)) cnf.MaxSize = cnf.MaxSize.Div(int(screenScale))
C.setMaxSize(w.window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y)) C.setMaxSize(window, C.CGFloat(cnf.MaxSize.X), C.CGFloat(cnf.MaxSize.Y))
} }
} }
if cnf.Decorated != prev.Decorated { if cnf.Decorated != prev.Decorated {
w.config.Decorated = cnf.Decorated w.config.Decorated = cnf.Decorated
mask := C.getWindowStyleMask(w.window) mask := C.getWindowStyleMask(window)
style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable) style := C.NSWindowStyleMask(C.NSWindowStyleMaskTitled | C.NSWindowStyleMaskResizable | C.NSWindowStyleMaskMiniaturizable | C.NSWindowStyleMaskClosable)
style = C.NSWindowStyleMaskFullSizeContentView style = C.NSWindowStyleMaskFullSizeContentView
mask &^= style mask &^= style
@@ -395,12 +400,12 @@ func (w *window) Configure(options []Option) {
barTrans = C.YES barTrans = C.YES
titleVis = C.NSWindowTitleHidden titleVis = C.NSWindowTitleHidden
} }
C.setWindowTitlebarAppearsTransparent(w.window, barTrans) C.setWindowTitlebarAppearsTransparent(window, barTrans)
C.setWindowTitleVisibility(w.window, titleVis) C.setWindowTitleVisibility(window, titleVis)
C.setWindowStyleMask(w.window, mask) C.setWindowStyleMask(window, mask)
C.setWindowStandardButtonHidden(w.window, C.NSWindowCloseButton, barTrans) C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(w.window, C.NSWindowMiniaturizeButton, barTrans) C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(w.window, C.NSWindowZoomButton, barTrans) C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
} }
w.w.Event(ConfigEvent{Config: w.config}) w.w.Event(ConfigEvent{Config: w.config})
} }
@@ -410,25 +415,26 @@ func (w *window) setTitle(prev, cnf Config) {
w.config.Title = cnf.Title w.config.Title = cnf.Title
title := stringToNSString(cnf.Title) title := stringToNSString(cnf.Title)
defer C.CFRelease(title) defer C.CFRelease(title)
C.setTitle(w.window, title) C.setTitle(C.windowForView(w.view), title)
} }
} }
func (w *window) Perform(acts system.Action) { func (w *window) Perform(acts system.Action) {
window := C.windowForView(w.view)
walkActions(acts, func(a system.Action) { walkActions(acts, func(a system.Action) {
switch a { switch a {
case system.ActionCenter: case system.ActionCenter:
r := C.getScreenFrame(w.window) // the screen size of the window r := C.getScreenFrame(window) // the screen size of the window
sz := w.config.Size sz := w.config.Size
x := (int(r.size.width) - sz.X) / 2 x := (int(r.size.width) - sz.X) / 2
y := (int(r.size.height) - sz.Y) / 2 y := (int(r.size.height) - sz.Y) / 2
C.setScreenFrame(w.window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y)) C.setScreenFrame(window, C.CGFloat(x), C.CGFloat(y), C.CGFloat(sz.X), C.CGFloat(sz.Y))
case system.ActionRaise: case system.ActionRaise:
C.raiseWindow(w.window) C.raiseWindow(window)
} }
}) })
if acts&system.ActionClose != 0 { if acts&system.ActionClose != 0 {
C.closeWindow(w.window) C.closeWindow(window)
} }
} }
@@ -531,7 +537,7 @@ func gio_onMouse(view, evt C.CFTypeRef, cdir C.int, cbtn C.NSInteger, x, y, dx,
if ok && w.config.Mode != Fullscreen { if ok && w.config.Mode != Fullscreen {
switch act { switch act {
case system.ActionMove: case system.ActionMove:
C.performWindowDragWithEvent(w.window, evt) C.performWindowDragWithEvent(C.windowForView(w.view), evt)
return return
} }
} }
@@ -781,14 +787,12 @@ func configFor(scale float32) unit.Metric {
//export gio_onClose //export gio_onClose
func gio_onClose(view C.CFTypeRef) { func gio_onClose(view C.CFTypeRef) {
w := mustView(view) w := mustView(view)
w.displayLink.Close()
w.w.Event(ViewEvent{}) w.w.Event(ViewEvent{})
deleteView(view) deleteView(view)
w.w.Event(system.DestroyEvent{}) w.w.Event(system.DestroyEvent{})
w.displayLink.Close()
C.CFRelease(w.view) C.CFRelease(w.view)
C.CFRelease(w.window)
w.view = 0 w.view = 0
w.window = 0
w.displayLink = nil w.displayLink = nil
} }
@@ -848,17 +852,18 @@ func newWindow(win *callbacks, options []Option) error {
} }
errch <- nil errch <- nil
w.w = win w.w = win
w.window = C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0) window := C.gio_createWindow(w.view, 0, 0, 0, 0, 0, 0)
w.updateWindowMode() w.updateWindowMode()
win.SetDriver(w) win.SetDriver(w)
w.Configure(options) w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 { if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op, // cascadeTopLeftFromPoint treats (0, 0) as a no-op,
// and just returns the offset we need for the first window. // and just returns the offset we need for the first window.
nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
} }
nextTopLeft = C.cascadeTopLeftFromPoint(w.window, nextTopLeft) nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
C.makeKeyAndOrderFront(w.window) // makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window)
layer := C.layerForView(w.view) layer := C.layerForView(w.view)
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
}) })
+5 -6
View File
@@ -45,11 +45,6 @@ __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(void);
NSWindow *window = (NSWindow *)[notification object]; NSWindow *window = (NSWindow *)[notification object];
gio_onFocus((__bridge CFTypeRef)window.contentView, 0); gio_onFocus((__bridge CFTypeRef)window.contentView, 0);
} }
- (void)windowWillClose:(NSNotification *)notification {
NSWindow *window = (NSWindow *)[notification object];
window.delegate = nil;
gio_onClose((__bridge CFTypeRef)window.contentView);
}
@end @end
static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
@@ -86,6 +81,11 @@ static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFlo
layer.delegate = self; layer.delegate = self;
return layer; return layer;
} }
- (void)viewDidMoveToWindow {
if (self.window == nil) {
gio_onClose((__bridge CFTypeRef)self);
}
}
- (void)mouseDown:(NSEvent *)event { - (void)mouseDown:(NSEvent *)event {
handleMouse(self, event, MOUSE_DOWN, 0, 0); handleMouse(self, event, MOUSE_DOWN, 0, 0);
} }
@@ -357,7 +357,6 @@ CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGF
NSView *view = (__bridge NSView *)viewRef; NSView *view = (__bridge NSView *)viewRef;
[window setContentView:view]; [window setContentView:view];
[window makeFirstResponder:view]; [window makeFirstResponder:view];
window.releasedWhenClosed = NO;
window.delegate = globalWindowDel; window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window; return (__bridge_retained CFTypeRef)window;
} }