diff options
author | culler <culler> | 2020-05-12 15:49:15 (GMT) |
---|---|---|
committer | culler <culler> | 2020-05-12 15:49:15 (GMT) |
commit | 8ad5f9058858a5dfbac3f099ed0bd50187a05810 (patch) | |
tree | cc1e6ca27cbbc5e13c308e12490012dadfbc158b /macosx | |
parent | 523aa062a1f89ea321f1d046e44486dc6523d3f1 (diff) | |
parent | 81efacd24cdb3ae469b8dd7a6d9b8524ead62dc7 (diff) | |
download | tk-8ad5f9058858a5dfbac3f099ed0bd50187a05810.zip tk-8ad5f9058858a5dfbac3f099ed0bd50187a05810.tar.gz tk-8ad5f9058858a5dfbac3f099ed0bd50187a05810.tar.bz2 |
Fix [411359dc3b]: crashes and zombies on Macintosh computers with a TouchBar.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/tkMacOSXInit.c | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 13 | ||||
-rw-r--r-- | macosx/tkMacOSXWindowEvent.c | 14 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 117 |
4 files changed, 95 insertions, 51 deletions
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c index 551577e..b13b855 100644 --- a/macosx/tkMacOSXInit.c +++ b/macosx/tkMacOSXInit.c @@ -75,7 +75,7 @@ static char scriptPath[PATH_MAX + 1] = ""; #define observe(n, s) \ [nc addObserver:self selector:@selector(s) name:(n) object:nil] observe(NSApplicationDidBecomeActiveNotification, applicationActivate:); - observe(NSApplicationDidResignActiveNotification, applicationDeactivate:); + observe(NSApplicationWillResignActiveNotification, applicationDeactivate:); observe(NSApplicationDidUnhideNotification, applicationShowHide:); observe(NSApplicationDidHideNotification, applicationShowHide:); observe(NSApplicationDidChangeScreenParametersNotification, displayChanged:); diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 747ebd4..b5b93d5 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -500,6 +500,19 @@ VISIBILITY_HIDDEN @end /* + * These methods are exposed because they are needed to prevent zombie windows + * on systems with a TouchBar. The TouchBar Key-Value observer holds a + * reference to the key window, which prevents deallocation of the key window + * when it is closed. + */ + +@interface NSApplication(TkWm) +- (id) _setKeyWindow: (NSWindow *) window; +- (id) _setMainWindow: (NSWindow *) window; +@end + + +/* *--------------------------------------------------------------------------- * * TKNSString -- diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index be3439f..cb4ffd1 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -315,6 +315,20 @@ extern NSString *NSWindowDidOrderOffScreenNotification; #ifdef TK_MAC_DEBUG_NOTIFICATIONS TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); #endif + + /* + * To prevent zombie windows on systems with a TouchBar, set the key window + * to nil if the current key window is not visible. This allows a closed + * Help or About window to be deallocated so it will not reappear as a + * zombie when the app is reactivated. + */ + + NSWindow *keywindow = [NSApp keyWindow]; + if (keywindow && ![keywindow isVisible]) { + [NSApp _setKeyWindow:nil]; + [NSApp _setMainWindow:nil]; + } + } - (BOOL)applicationShouldHandleReopen:(NSApplication *)sender 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]; /* |