1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h
index 9417b62e6..dff6a200d 100644
--- a/macosx/tkMacOSXPrivate.h
+++ b/macosx/tkMacOSXPrivate.h
@@ -433,6 +433,18 @@ VISIBILITY_HIDDEN
- (void) setAppleMenu: (NSMenu *) menu;
@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
+
#endif /* _TKMACPRIV */
int TkMacOSXGetAppPath(ClientData cd, Tcl_Interp *ip, int objc, Tcl_Obj *const objv[]);
diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c
index ceb3f3f7e..f24f198f0 100644
--- a/macosx/tkMacOSXWm.c
+++ b/macosx/tkMacOSXWm.c
@@ -879,6 +879,7 @@ TkWmDeadWindow(
TkWindow *winPtr) /* Top-level window that's being deleted. */
{
WmInfo *wmPtr = winPtr->wmInfoPtr, *wmPtr2;
+ NSWindow *ourNSWindow;
if (wmPtr == NULL) {
return;
@@ -952,77 +953,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;
}
|