diff options
author | culler <culler> | 2020-05-11 21:24:02 (GMT) |
---|---|---|
committer | culler <culler> | 2020-05-11 21:24:02 (GMT) |
commit | edeeece7a1d441c68a4fd5669a7fc0de31fc213d (patch) | |
tree | 92f4d1f2d84e2f3759e2c779deaeb81e8d554d7f /macosx/tkMacOSXWm.c | |
parent | c68e6846fa095947b92145b27d472d5df463d616 (diff) | |
download | tk-edeeece7a1d441c68a4fd5669a7fc0de31fc213d.zip tk-edeeece7a1d441c68a4fd5669a7fc0de31fc213d.tar.gz tk-edeeece7a1d441c68a4fd5669a7fc0de31fc213d.tar.bz2 |
Fix [411359dc3b]: Clean up Aqua window destruction in TkWmDeadWindow to prevent crashes and zombies on systems with a Touchbar.
Diffstat (limited to 'macosx/tkMacOSXWm.c')
-rw-r--r-- | macosx/tkMacOSXWm.c | 117 |
1 files changed, 67 insertions, 50 deletions
diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 45d1458..9117159 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -22,6 +22,12 @@ #include "tkMacOSXDebug.h" #include "tkMacOSXConstants.h" +/* + * Setting this to 1 prints when each window is freed, setting it to 2 adds + * dumps of the autorelease pools, and setting it to 3 also shows each retain + * and release. + */ + #define DEBUG_ZOMBIES 0 /* @@ -357,7 +363,6 @@ static void RemoveTransient(TkWindow *winPtr); #pragma mark TKWindow(TKWm) @implementation TKWindow: NSWindow - @end @implementation TKWindow(TKWm) @@ -430,7 +435,8 @@ static void RemoveTransient(TkWindow *winPtr); - (BOOL) canBecomeKeyWindow { TkWindow *winPtr = TkMacOSXGetTkWindow(self); - if (!winPtr) { + + if (!winPtr || !winPtr->wmInfoPtr) { return NO; } return (winPtr->wmInfoPtr && @@ -447,7 +453,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1) { + if (DEBUG_ZOMBIES > 2) { fprintf(stderr, "Retained <%s>. Count is: %lu\n", title, [self retainCount]); } @@ -461,7 +467,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1) { + if (DEBUG_ZOMBIES > 2) { fprintf(stderr, "Autoreleased <%s>. Count is %lu\n", title, [self retainCount]); } @@ -473,7 +479,7 @@ static void RemoveTransient(TkWindow *winPtr); if (title == nil) { title = "unnamed window"; } - if (DEBUG_ZOMBIES > 1) { + if (DEBUG_ZOMBIES > 2) { fprintf(stderr, "Releasing <%s>. Count is %lu\n", title, [self retainCount]); } @@ -879,6 +885,7 @@ TkWmDeadWindow( TkWindow *winPtr) /* Top-level window that's being deleted. */ { WmInfo *wmPtr = winPtr->wmInfoPtr, *wmPtr2; + NSWindow *ourNSWindow; if (wmPtr == NULL) { return; @@ -952,77 +959,87 @@ TkWmDeadWindow( } /* - * Delete the Mac window and remove it from the windowTable. The window - * could be nil if the window was never mapped. However, we don't do this - * for embedded windows, they don't go in the window list, and they do not - * own their portPtr's. + * Unregister the NSWindow and remove all references to it from the Tk + * data structures. If the NSWindow is a child, disassociate it from + * the parent. Then close and release the NSWindow. */ - NSWindow *window = wmPtr->window; - - if (window && !Tk_IsEmbedded(winPtr)) { - NSWindow *parent = [window parentWindow]; + ourNSWindow = wmPtr->window; + if (ourNSWindow && !Tk_IsEmbedded(winPtr)) { + NSWindow *parent = [ourNSWindow parentWindow]; + TkMacOSXUnregisterMacWindow(ourNSWindow); + if (winPtr->window) { + ((MacDrawable *) winPtr->window)->view = nil; + } + wmPtr->window = NULL; if (parent) { - [parent removeChildWindow:window]; + [parent removeChildWindow:ourNSWindow]; } -#if DEBUG_ZOMBIES > 0 + +#if DEBUG_ZOMBIES > 1 { - const char *title = [[window title] UTF8String]; + const char *title = [[ourNSWindow title] UTF8String]; if (title == nil) { title = "unnamed window"; } fprintf(stderr, ">>>> Closing <%s>. Count is: %lu\n", title, - [window retainCount]); + [ourNSWindow retainCount]); } #endif - [window close]; - TkMacOSXUnregisterMacWindow(window); - if (winPtr->window) { - ((MacDrawable *) winPtr->window)->view = nil; - } - wmPtr->window = NULL; - [window release]; - - /* Activate the highest window left on the screen. */ - NSArray *windows = [NSApp orderedWindows]; - for (id nswindow in windows) { - TkWindow *winPtr2 = TkMacOSXGetTkWindow(nswindow); - if (winPtr2 && nswindow != window) { - WmInfo *wmPtr = winPtr2->wmInfoPtr; - BOOL minimized = (wmPtr->hints.initial_state == IconicState - || wmPtr->hints.initial_state == WithdrawnState); + /* + * When a window is closed we want to move the focus to the next + * highest window. Apple's documentation says that calling the + * orderOut method of the key window will accomplish this. But + * experiment shows that this is not the case. So we have to reset the + * key window ourselves. When the window is the last one on the screen + * there is no choice for a new key window. Moreover, if the host + * computer has a TouchBar then the TouchBar holds a reference to the + * key window which prevents it from being deallocated until it stops + * being the key window. On these systems the only option for + * preventing zombies is to set the key window to nil. + */ - /* - * If no windows are left on the screen and the next window is - * iconified or withdrawn, we don't want to make it be the - * KeyWindow because that would cause it to be displayed on the - * screen. - */ + for (NSWindow *w in [NSApp orderedWindows]) { + TkWindow *winPtr2 = TkMacOSXGetTkWindow(w); + BOOL isOnScreen; - if ([nswindow canBecomeKeyWindow] && !minimized) { - [nswindow makeKeyAndOrderFront:NSApp]; - break; - } + if (!winPtr2 || !winPtr2->wmInfoPtr) { + continue; + } + wmPtr2 = winPtr2->wmInfoPtr; + isOnScreen = (wmPtr2->hints.initial_state != IconicState && + wmPtr2->hints.initial_state != WithdrawnState); + if (w != ourNSWindow && isOnScreen && [w canBecomeKeyWindow]) { + [w makeKeyAndOrderFront:NSApp]; + break; } } /* - * Process all window events immediately to force the closed window to - * be deallocated. But don't do this for the root window as that is - * unnecessary and can lead to segfaults. + * Prevent zombies on systems with a TouchBar. */ - if (winPtr->parentPtr) { - while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS|TCL_DONT_WAIT)) {} + if (ourNSWindow == [NSApp keyWindow]) { + [NSApp _setKeyWindow:nil]; + [NSApp _setMainWindow:nil]; } + [ourNSWindow close]; + [ourNSWindow release]; [NSApp _resetAutoreleasePool]; -#if DEBUG_ZOMBIES > 0 + +#if DEBUG_ZOMBIES > 1 fprintf(stderr, "================= Pool dump ===================\n"); [NSAutoreleasePool showPools]; #endif + } + + /* + * Deallocate the wmInfo and clear the wmInfoPtr. + */ + ckfree(wmPtr); winPtr->wmInfoPtr = NULL; } @@ -3789,7 +3806,7 @@ WmWithdrawCmd( TkpWmSetState(winPtr, WithdrawnState); NSWindow *win = TkMacOSXDrawableWindow(winPtr->window); - [win orderOut:nil]; + [win orderOut:NSApp]; [win setExcludedFromWindowsMenu:YES]; /* |