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;
}
static CFTypeRef windowForView(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef;
return (__bridge CFTypeRef)view.window;
}
static void raiseWindow(CFTypeRef windowRef) {
NSWindow* window = (__bridge NSWindow *)windowRef;
[window makeKeyAndOrderFront:nil];
@@ -237,7 +242,6 @@ type ViewEvent struct {
type window struct {
view C.CFTypeRef
window C.CFTypeRef
w *callbacks
stage system.Stage
displayLink *displayLink
@@ -305,7 +309,7 @@ func (w *window) WriteClipboard(s string) {
}
func (w *window) updateWindowMode() {
style := int(C.getWindowStyleMask(w.window))
style := int(C.getWindowStyleMask(C.windowForView(w.view)))
if style&C.NSWindowStyleMaskFullScreen != 0 {
w.config.Mode = Fullscreen
} else {
@@ -321,70 +325,71 @@ func (w *window) Configure(options []Option) {
w.updateWindowMode()
cnf := w.config
cnf.apply(cfg, options)
window := C.windowForView(w.view)
switch cnf.Mode {
case Fullscreen:
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(w.window)
C.unhideWindow(window)
fallthrough
default:
w.config.Mode = Fullscreen
C.toggleFullScreen(w.window)
C.toggleFullScreen(window)
}
case Minimized:
switch prev.Mode {
case Minimized, Fullscreen:
default:
w.config.Mode = Minimized
C.hideWindow(w.window)
C.hideWindow(window)
}
case Maximized:
switch prev.Mode {
case Fullscreen:
case Minimized:
C.unhideWindow(w.window)
C.unhideWindow(window)
fallthrough
default:
w.config.Mode = Maximized
w.setTitle(prev, cnf)
if C.isWindowZoomed(w.window) == 0 {
C.zoomWindow(w.window)
if C.isWindowZoomed(window) == 0 {
C.zoomWindow(window)
}
}
case Windowed:
switch prev.Mode {
case Fullscreen:
C.toggleFullScreen(w.window)
C.toggleFullScreen(window)
case Minimized:
C.unhideWindow(w.window)
C.unhideWindow(window)
case Maximized:
}
w.config.Mode = Windowed
if C.isWindowZoomed(w.window) != 0 {
C.zoomWindow(w.window)
if C.isWindowZoomed(window) != 0 {
C.zoomWindow(window)
}
w.setTitle(prev, cnf)
if prev.Size != cnf.Size {
w.config.Size = cnf.Size
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 {
w.config.MinSize = cnf.MinSize
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 {
w.config.MaxSize = cnf.MaxSize
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 {
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.NSWindowStyleMaskFullSizeContentView
mask &^= style
@@ -395,12 +400,12 @@ func (w *window) Configure(options []Option) {
barTrans = C.YES
titleVis = C.NSWindowTitleHidden
}
C.setWindowTitlebarAppearsTransparent(w.window, barTrans)
C.setWindowTitleVisibility(w.window, titleVis)
C.setWindowStyleMask(w.window, mask)
C.setWindowStandardButtonHidden(w.window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(w.window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(w.window, C.NSWindowZoomButton, barTrans)
C.setWindowTitlebarAppearsTransparent(window, barTrans)
C.setWindowTitleVisibility(window, titleVis)
C.setWindowStyleMask(window, mask)
C.setWindowStandardButtonHidden(window, C.NSWindowCloseButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowMiniaturizeButton, barTrans)
C.setWindowStandardButtonHidden(window, C.NSWindowZoomButton, barTrans)
}
w.w.Event(ConfigEvent{Config: w.config})
}
@@ -410,25 +415,26 @@ func (w *window) setTitle(prev, cnf Config) {
w.config.Title = cnf.Title
title := stringToNSString(cnf.Title)
defer C.CFRelease(title)
C.setTitle(w.window, title)
C.setTitle(C.windowForView(w.view), title)
}
}
func (w *window) Perform(acts system.Action) {
window := C.windowForView(w.view)
walkActions(acts, func(a system.Action) {
switch a {
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
x := (int(r.size.width) - sz.X) / 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:
C.raiseWindow(w.window)
C.raiseWindow(window)
}
})
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 {
switch act {
case system.ActionMove:
C.performWindowDragWithEvent(w.window, evt)
C.performWindowDragWithEvent(C.windowForView(w.view), evt)
return
}
}
@@ -781,14 +787,12 @@ func configFor(scale float32) unit.Metric {
//export gio_onClose
func gio_onClose(view C.CFTypeRef) {
w := mustView(view)
w.displayLink.Close()
w.w.Event(ViewEvent{})
deleteView(view)
w.w.Event(system.DestroyEvent{})
w.displayLink.Close()
C.CFRelease(w.view)
C.CFRelease(w.window)
w.view = 0
w.window = 0
w.displayLink = nil
}
@@ -848,17 +852,18 @@ func newWindow(win *callbacks, options []Option) error {
}
errch <- nil
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()
win.SetDriver(w)
w.Configure(options)
if nextTopLeft.x == 0 && nextTopLeft.y == 0 {
// cascadeTopLeftFromPoint treats (0, 0) as a no-op,
// 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)
C.makeKeyAndOrderFront(w.window)
nextTopLeft = C.cascadeTopLeftFromPoint(window, nextTopLeft)
// makeKeyAndOrderFront assumes ownership of our window reference.
C.makeKeyAndOrderFront(window)
layer := C.layerForView(w.view)
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];
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
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;
return layer;
}
- (void)viewDidMoveToWindow {
if (self.window == nil) {
gio_onClose((__bridge CFTypeRef)self);
}
}
- (void)mouseDown:(NSEvent *)event {
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;
[window setContentView:view];
[window makeFirstResponder:view];
window.releasedWhenClosed = NO;
window.delegate = globalWindowDel;
return (__bridge_retained CFTypeRef)window;
}