summaryrefslogtreecommitdiffstats
path: root/macosx
diff options
context:
space:
mode:
authorculler <culler>2020-05-12 15:49:15 (GMT)
committerculler <culler>2020-05-12 15:49:15 (GMT)
commit8ad5f9058858a5dfbac3f099ed0bd50187a05810 (patch)
treecc1e6ca27cbbc5e13c308e12490012dadfbc158b /macosx
parent523aa062a1f89ea321f1d046e44486dc6523d3f1 (diff)
parent81efacd24cdb3ae469b8dd7a6d9b8524ead62dc7 (diff)
downloadtk-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.c2
-rw-r--r--macosx/tkMacOSXPrivate.h13
-rw-r--r--macosx/tkMacOSXWindowEvent.c14
-rw-r--r--macosx/tkMacOSXWm.c117
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];
/*