summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXWm.c
diff options
context:
space:
mode:
authorculler <culler>2020-05-11 21:24:02 (GMT)
committerculler <culler>2020-05-11 21:24:02 (GMT)
commitedeeece7a1d441c68a4fd5669a7fc0de31fc213d (patch)
tree92f4d1f2d84e2f3759e2c779deaeb81e8d554d7f /macosx/tkMacOSXWm.c
parentc68e6846fa095947b92145b27d472d5df463d616 (diff)
downloadtk-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.c117
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];
/*