diff options
author | culler <culler> | 2020-05-12 15:49:44 (GMT) |
---|---|---|
committer | culler <culler> | 2020-05-12 15:49:44 (GMT) |
commit | 56b8b24c8a7a16bb713b397cfd7567bb74e0a231 (patch) | |
tree | c2e96ff2fe13976d6be2bc8b1152096a15723527 | |
parent | f295f92dd0c2e638798d2e8c937eb6dd15090d45 (diff) | |
parent | 8ad5f9058858a5dfbac3f099ed0bd50187a05810 (diff) | |
download | tk-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.c | 2 | ||||
-rw-r--r-- | macosx/tkMacOSXPrivate.h | 13 | ||||
-rw-r--r-- | macosx/tkMacOSXWindowEvent.c | 14 | ||||
-rw-r--r-- | macosx/tkMacOSXWm.c | 117 | ||||
-rw-r--r-- | tests/canvImg.test | 2 | ||||
-rw-r--r-- | tests/event.test | 1 | ||||
-rw-r--r-- | tests/frame.test | 27 | ||||
-rw-r--r-- | tests/text.test | 3 |
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 |