summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorculler <culler>2020-05-12 15:49:44 (GMT)
committerculler <culler>2020-05-12 15:49:44 (GMT)
commit56b8b24c8a7a16bb713b397cfd7567bb74e0a231 (patch)
treec2e96ff2fe13976d6be2bc8b1152096a15723527
parentf295f92dd0c2e638798d2e8c937eb6dd15090d45 (diff)
parent8ad5f9058858a5dfbac3f099ed0bd50187a05810 (diff)
downloadtk-56b8b24c8a7a16bb713b397cfd7567bb74e0a231.zip
tk-56b8b24c8a7a16bb713b397cfd7567bb74e0a231.tar.gz
tk-56b8b24c8a7a16bb713b397cfd7567bb74e0a231.tar.bz2
Fix [411359dc3b]: crashes and zombies on Macintosh computers with a TouchBar.
-rw-r--r--macosx/tkMacOSXInit.c2
-rw-r--r--macosx/tkMacOSXPrivate.h13
-rw-r--r--macosx/tkMacOSXWindowEvent.c14
-rw-r--r--macosx/tkMacOSXWm.c117
-rw-r--r--tests/canvImg.test2
-rw-r--r--tests/event.test1
-rw-r--r--tests/frame.test27
-rw-r--r--tests/text.test3
8 files changed, 121 insertions, 58 deletions
diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c
index 4d94952..04ee001 100644
--- a/macosx/tkMacOSXInit.c
+++ b/macosx/tkMacOSXInit.c
@@ -82,7 +82,7 @@ static int TkMacOSXGetAppPathCmd(ClientData cd, Tcl_Interp *ip,
#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 c887d4d..205bbde 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 914a15a..71e687b 100644
--- a/macosx/tkMacOSXWindowEvent.c
+++ b/macosx/tkMacOSXWindowEvent.c
@@ -322,6 +322,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 2323c58..f24b8d4 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;
}
@@ -3790,7 +3807,7 @@ WmWithdrawCmd(
TkpWmSetState(winPtr, WithdrawnState);
NSWindow *win = TkMacOSXDrawableWindow(winPtr->window);
- [win orderOut:nil];
+ [win orderOut:NSApp];
[win setExcludedFromWindowsMenu:YES];
/*
diff --git a/tests/canvImg.test b/tests/canvImg.test
index bd9edb5..d6ed9a8 100644
--- a/tests/canvImg.test
+++ b/tests/canvImg.test
@@ -803,7 +803,7 @@ test canvImg-11.3 {ImageChangedProc procedure} -constraints {
foo2 changed 0 0 0 0 80 60
.c create image 50 100 -image foo -tags image -anchor nw
.c create image 70 110 -image foo2 -anchor nw
- update
+ update idletasks
set z {}
set timer [after 500 {lappend z "timed out"}]
image create test foo -variable x
diff --git a/tests/event.test b/tests/event.test
index b4d2ce9..2e53196 100644
--- a/tests/event.test
+++ b/tests/event.test
@@ -860,6 +860,7 @@ test event-8 {event generate with keysyms corresponding to
} -result {OK}
# cleanup
+update
unset -nocomplain keypress_lookup
rename _init_keypress_lookup {}
rename _keypress_lookup {}
diff --git a/tests/frame.test b/tests/frame.test
index 77eae31..bdeb2e9 100644
--- a/tests/frame.test
+++ b/tests/frame.test
@@ -670,11 +670,19 @@ test frame-3.10 {TkCreateFrame procedure, -use option} -constraints {
} -body {
toplevel .t -container 1 -width 300 -height 120
wm geometry .t +0+0
+ if {[tk windowingsystem] eq "aqua"} {
+ update idletasks
+ } else {
+ update
+ }
option add *x.use [winfo id .t]
- update
toplevel .x -width 140 -height 300 -bg green
- tkwait visibility .x
- update
+ if {[tk windowingsystem] eq "aqua"} {
+ update idletasks
+ } else {
+ tkwait visibility .x
+ update
+ }
list [expr {[winfo rootx .x] - [winfo rootx .t]}] \
[expr {[winfo rooty .x] - [winfo rooty .t]}] \
[winfo width .t] [winfo height .t]
@@ -1133,17 +1141,26 @@ test frame-12.2 {FrameWorldChanged procedure} -setup {
} -result {1 1 1 1 1 1 1 1 1 1 1 1}
test frame-12.3 {FrameWorldChanged procedure} -setup {
deleteWindows
+ update idletasks
} -body {
# Check reaction on font change
font create myfont -family courier -size 10
labelframe .f -font myfont -text Mupp
place .f -x 0 -y 0 -width 40 -height 40
pack [frame .f.f] -fill both -expand 1
- update
+ if {[tk windowingsystem] eq "aqua"} {
+ update idletasks
+ } else {
+ update
+ }
set h1 [font metrics myfont -linespace]
set y1 [winfo y .f.f]
font configure myfont -size 20
- update
+ if {[tk windowingsystem] eq "aqua"} {
+ update idletasks
+ } else {
+ update
+ }
set h2 [font metrics myfont -linespace]
set y2 [winfo y .f.f]
expr {($h2 - $h1) - ($y2 - $y1)}
diff --git a/tests/text.test b/tests/text.test
index 8019e0d..7770084 100644
--- a/tests/text.test
+++ b/tests/text.test
@@ -6705,7 +6705,8 @@ test text-27.15d {<<Selection>> virtual event on <Delete> with cursor inside sel
update
set ::retval no_<<Selection>>_event_fired
.t mark set insert 1.15
- focus .t
+ update idletasks
+ focus -force .t
event generate .t <Delete>
update
set ::retval