From 0e8376bacdeb2bfe083cb4836c52c1c95a16a105 Mon Sep 17 00:00:00 2001 From: Kevin Walzer Date: Wed, 11 Oct 2017 00:44:12 +0000 Subject: Numerous fixes for Tk on macOS 10.13; eliminate memory leaks, override system fullscreen API that confuses window geometry, improve implementation of Tk fullscreen API. Thanks to Marc Culler for patches to address memory leaks. --- generic/tkTextDisp.c | 4 ++-- macosx/tkMacOSXBitmap.c | 2 +- macosx/tkMacOSXDialog.c | 5 +++-- macosx/tkMacOSXEvent.c | 1 + macosx/tkMacOSXInit.c | 24 +++++++++++++++++++++--- macosx/tkMacOSXKeyEvent.c | 1 + macosx/tkMacOSXKeyboard.c | 2 +- macosx/tkMacOSXMenu.c | 1 + macosx/tkMacOSXMenus.c | 1 + macosx/tkMacOSXMouseEvent.c | 1 + macosx/tkMacOSXNotify.c | 35 ++++++++++++++--------------------- macosx/tkMacOSXPrivate.h | 7 +++++-- macosx/tkMacOSXWindowEvent.c | 5 +++-- macosx/tkMacOSXWm.c | 42 +++++++++++++++++++++++++++++++++++------- macosx/tkMacOSXXStubs.c | 3 +-- 15 files changed, 91 insertions(+), 43 deletions(-) diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index 9c2f536..39a57eb 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -4120,8 +4120,8 @@ DisplayText( MacDrawable *macWin = winPtr->privatePtr; if (macWin && (macWin->flags & TK_DO_NOT_DRAW)){ dInfoPtr->flags &= ~REDRAW_PENDING; - return; - } + return; + } #endif if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) { diff --git a/macosx/tkMacOSXBitmap.c b/macosx/tkMacOSXBitmap.c index 52768c6..6e3c1a5 100644 --- a/macosx/tkMacOSXBitmap.c +++ b/macosx/tkMacOSXBitmap.c @@ -12,7 +12,7 @@ */ #include "tkMacOSXPrivate.h" - +#include "tkMacOSXConstants.h" /* * This structure holds information about native bitmaps. */ diff --git a/macosx/tkMacOSXDialog.c b/macosx/tkMacOSXDialog.c index 1383225..c1f1491 100644 --- a/macosx/tkMacOSXDialog.c +++ b/macosx/tkMacOSXDialog.c @@ -14,6 +14,7 @@ #include "tkMacOSXPrivate.h" #include "tkFileFilter.h" +#include "tkMacOSXConstants.h" #if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 #define modalOK NSOKButton @@ -190,7 +191,7 @@ static NSURL *getFileURL(NSString *directory, NSString *filename) { { FilePanelCallbackInfo *callbackInfo = contextInfo; - if (returnCode == NSFileHandlingPanelOKButton) { + if (returnCode == modalOK) { Tcl_Obj *resultObj; if (callbackInfo->multiple) { @@ -218,7 +219,7 @@ static NSURL *getFileURL(NSString *directory, NSString *filename) { } else { Tcl_SetObjResult(callbackInfo->interp, resultObj); } - } else if (returnCode == NSFileHandlingPanelCancelButton) { + } else if (returnCode == modalCancel) { Tcl_ResetResult(callbackInfo->interp); } if (panel == [NSApp modalWindow]) { diff --git a/macosx/tkMacOSXEvent.c b/macosx/tkMacOSXEvent.c index 7f3357f..de57008 100644 --- a/macosx/tkMacOSXEvent.c +++ b/macosx/tkMacOSXEvent.c @@ -14,6 +14,7 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXEvent.h" #include "tkMacOSXDebug.h" +#include "tkMacOSXConstants.h" #pragma mark TKApplication(TKEvent) diff --git a/macosx/tkMacOSXInit.c b/macosx/tkMacOSXInit.c index 33a60f2..a8b4e9d 100644 --- a/macosx/tkMacOSXInit.c +++ b/macosx/tkMacOSXInit.c @@ -57,17 +57,35 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt @end @implementation TKApplication -@synthesize poolProtected = _poolProtected; +@synthesize poolLock = _poolLock; @end +/* + * #define this to see a message on stderr whenever _resetAutoreleasePool is + * called while the pool is locked. + */ +#undef DEBUG_LOCK + @implementation TKApplication(TKInit) - (void) _resetAutoreleasePool { - if(![self poolProtected]) { + if([self poolLock] == 0) { [_mainPool drain]; _mainPool = [NSAutoreleasePool new]; + } else { +#ifdef DEBUG_LOCK + fprintf(stderr, "Pool is locked with count %d!!!!\n", [self poolLock]); +#endif } } +- (void) _lockAutoreleasePool +{ + [self setPoolLock:[self poolLock] + 1]; +} +- (void) _unlockAutoreleasePool +{ + [self setPoolLock:[self poolLock] - 1]; +} #ifdef TK_MAC_DEBUG_NOTIFICATIONS - (void) _postedNotification: (NSNotification *) notification { @@ -104,7 +122,7 @@ static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFSt { _eventInterp = interp; _mainPool = [NSAutoreleasePool new]; - [NSApp setPoolProtected:NO]; + [NSApp setPoolLock:0]; _defaultMainMenu = nil; [self _setupMenus]; [self setDelegate:self]; diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index 151b4f2..958f960 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -15,6 +15,7 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXEvent.h" +#include "tkMacOSXConstants.h" /* #ifdef TK_MAC_DEBUG diff --git a/macosx/tkMacOSXKeyboard.c b/macosx/tkMacOSXKeyboard.c index 7ac087d..bbbbf96 100644 --- a/macosx/tkMacOSXKeyboard.c +++ b/macosx/tkMacOSXKeyboard.c @@ -13,7 +13,7 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXEvent.h" - +#include "tkMacOSXConstants.h" /* * A couple of simple definitions to make code a bit more self-explaining. * diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index c7e3a78..d21cd11 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -19,6 +19,7 @@ #include "tkFont.h" #include "tkMacOSXWm.h" #include "tkMacOSXDebug.h" +#include "tkMacOSXConstants.h" /* #ifdef TK_MAC_DEBUG diff --git a/macosx/tkMacOSXMenus.c b/macosx/tkMacOSXMenus.c index 68b2c00..f8f00a6 100644 --- a/macosx/tkMacOSXMenus.c +++ b/macosx/tkMacOSXMenus.c @@ -13,6 +13,7 @@ #include "tkMacOSXPrivate.h" #include "tkMenu.h" +#include "tkMacOSXConstants.h" static void GenerateEditEvent(const char *name); static Tcl_Obj * GetWidgetDemoPath(Tcl_Interp *interp); diff --git a/macosx/tkMacOSXMouseEvent.c b/macosx/tkMacOSXMouseEvent.c index c4197f7..010023f 100644 --- a/macosx/tkMacOSXMouseEvent.c +++ b/macosx/tkMacOSXMouseEvent.c @@ -15,6 +15,7 @@ #include "tkMacOSXWm.h" #include "tkMacOSXEvent.h" #include "tkMacOSXDebug.h" +#include "tkMacOSXConstants.h" typedef struct { unsigned int state; diff --git a/macosx/tkMacOSXNotify.c b/macosx/tkMacOSXNotify.c index f14e1b8..7bb0b0d 100644 --- a/macosx/tkMacOSXNotify.c +++ b/macosx/tkMacOSXNotify.c @@ -15,8 +15,8 @@ #include "tkMacOSXPrivate.h" #include "tkMacOSXEvent.h" +#include "tkMacOSXConstants.h" #include -#include #import /* This is not used for anything at the moment. */ @@ -140,7 +140,7 @@ Tk_MacOSXSetupTkNotifier(void) */ if (CFRunLoopGetMain() == CFRunLoopGetCurrent()) { - if (!pthread_main_np()) { + if (![NSThread isMainThread]) { /* * Panic if main runloop is not on the main application thread. */ @@ -150,7 +150,7 @@ Tk_MacOSXSetupTkNotifier(void) } Tcl_CreateEventSource(TkMacOSXEventsSetupProc, TkMacOSXEventsCheckProc, - GetMainEventQueue()); + NULL); TkCreateExitHandler(TkMacOSXNotifyExitHandler, NULL); Tcl_SetServiceMode(TCL_SERVICE_ALL); TclMacOSXNotifierAddRunLoopMode(NSEventTrackingRunLoopMode); @@ -184,7 +184,7 @@ TkMacOSXNotifyExitHandler( Tcl_DeleteEventSource(TkMacOSXEventsSetupProc, TkMacOSXEventsCheckProc, - GetMainEventQueue()); + NULL); tsdPtr->initialized = 0; } @@ -216,9 +216,11 @@ TkMacOSXEventsSetupProc( int flags) { NSString *runloopMode = [[NSRunLoop currentRunLoop] currentMode]; + // fprintf(stderr, "SetupProc (%s)", [runloopMode UTF8String]); /* runloopMode will be nil if we are in the Tcl event loop. */ if (flags & TCL_WINDOW_EVENTS && !runloopMode) { static const Tcl_Time zeroBlockTime = { 0, 0 }; + [NSApp _resetAutoreleasePool]; /* Call this with dequeue=NO -- just checking if the queue is empty. */ NSEvent *currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] @@ -259,26 +261,15 @@ TkMacOSXEventsCheckProc( /* runloopMode will be nil if we are in the Tcl event loop. */ if (flags & TCL_WINDOW_EVENTS && !runloopMode) { NSEvent *currentEvent = nil; - NSEvent *testEvent = nil; NSModalSession modalSession; - + /* It is possible for the SetupProc to be called before this function + * returns. This happens, for example, when we process an event which + * opens a modal windows. To prevent premature release of our + * application-wide autorelease pool, we must lock it here. + */ + [NSApp _lockAutoreleasePool]; do { - [NSApp _resetAutoreleasePool]; modalSession = TkMacOSXGetModalSession(); - testEvent = [NSApp nextEventMatchingMask:NSAnyEventMask - untilDate:[NSDate distantPast] - inMode:GetRunLoopMode(modalSession) - dequeue:NO]; - /* We must not steal any events during LiveResize. */ -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 - if (testEvent && [[testEvent window] inLiveResize]) { - break; - } -#else - if (testEvent && [[[testEvent window] contentView] inLiveResize]) { - break; - } -#endif currentEvent = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:GetRunLoopMode(modalSession) @@ -303,6 +294,8 @@ TkMacOSXEventsCheckProc( break; } } while (1); + /* Now we can unlock the pool. */ + [NSApp _unlockAutoreleasePool]; } } diff --git a/macosx/tkMacOSXPrivate.h b/macosx/tkMacOSXPrivate.h index 65d60ce..6248c5a 100644 --- a/macosx/tkMacOSXPrivate.h +++ b/macosx/tkMacOSXPrivate.h @@ -278,14 +278,17 @@ VISIBILITY_HIDDEN NSAutoreleasePool *_mainPool; #ifdef __i386__ /* The Objective C runtime used on i386 requires this. */ - BOOL _poolProtected; + int _poolLock; #endif } -@property BOOL poolProtected; +@property BOOL poolLock; + @end @interface TKApplication(TKInit) - (NSString *)tkFrameworkImagePath:(NSString*)image; - (void)_resetAutoreleasePool; +- (void)_lockAutoreleasePool; +- (void)_unlockAutoreleasePool; @end @interface TKApplication(TKEvent) - (NSEvent *)tkProcessEvent:(NSEvent *)theEvent; diff --git a/macosx/tkMacOSXWindowEvent.c b/macosx/tkMacOSXWindowEvent.c index 4672586..df1b138 100644 --- a/macosx/tkMacOSXWindowEvent.c +++ b/macosx/tkMacOSXWindowEvent.c @@ -17,6 +17,7 @@ #include "tkMacOSXWm.h" #include "tkMacOSXEvent.h" #include "tkMacOSXDebug.h" +#include "tkMacOSXConstants.h" /* #ifdef TK_MAC_DEBUG @@ -858,7 +859,7 @@ ConfigureRestrictProc( * Since it calls Tcl_DoOneEvent, we need to make sure we * don't clobber the AutoreleasePool set up by the caller. */ - [NSApp setPoolProtected:YES]; + [NSApp _lockAutoreleasePool]; /* * Try to prevent flickers and flashes. @@ -889,7 +890,7 @@ ConfigureRestrictProc( [w enableFlushWindow]; [w flushWindowIfNeeded]; NSEnableScreenUpdates(); - [NSApp setPoolProtected:NO]; + [NSApp _unlockAutoreleasePool]; } } diff --git a/macosx/tkMacOSXWm.c b/macosx/tkMacOSXWm.c index 75473bf..63ec362 100644 --- a/macosx/tkMacOSXWm.c +++ b/macosx/tkMacOSXWm.c @@ -20,6 +20,7 @@ #include "tkMacOSXWm.h" #include "tkMacOSXEvent.h" #include "tkMacOSXDebug.h" +#include "tkMacOSXConstants.h" #define DEBUG_ZOMBIES 0 @@ -56,6 +57,8 @@ /*Objects for use in setting background color and opacity of window.*/ NSColor *colorName = NULL; BOOL opaqueTag = FALSE; +extern CGImageRef CreateCGImageWithXImage( + XImage *image); static const struct { const UInt64 validAttrs, defaultAttrs, forceOnAttrs, forceOffAttrs; @@ -232,9 +235,28 @@ static int windowHashInit = false; pointrect.size.height = 0; return [self convertRectFromScreen:pointrect].origin; } -@end #endif +/*Override automatic fullscreen button on 10.13 because system fullscreen API +confuses Tk window geometry. +*/ +#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_12 +- (void)toggleFullScreen:(id)sender +{ + TkWindow *winPtr = TkMacOSXGetTkWindow(self); + Tk_Window win_Ptr=(Tk_Window) winPtr; + Tcl_Interp *interp = Tk_Interp(win_Ptr); + if ([self isZoomed]) { + TkMacOSXZoomToplevel(self, inZoomIn); + } else { + TkMacOSXZoomToplevel(self, inZoomOut); + } +} + +#endif +@end + + #pragma mark - @@ -5092,8 +5114,8 @@ TkMacOSXGetTkWindow( * * TkMacOSXIsWindowZoomed -- * - * Ask Carbon if the given window is in the zoomed out state. Because - * dragging & growing a window can change the Carbon zoom state, we + * Ask Cocoa if the given window is in the zoomed out state. Because + * dragging & growing a window can change the Cocoa zoom state, we * cannot rely on wmInfoPtr->hints.initial_state for this information. * * Results: @@ -5111,6 +5133,7 @@ TkMacOSXIsWindowZoomed( { return [TkMacOSXDrawableWindow(winPtr->window) isZoomed]; } + /* *---------------------------------------------------------------------- @@ -5153,12 +5176,13 @@ TkMacOSXZoomToplevel( * Do nothing if already in desired zoom state. */ - if (![window isZoomed] == (zoomPart == inZoomIn)) { + if ((![window isZoomed] == (zoomPart == inZoomIn))) { return false; } - [window zoom:NSApp]; - wmPtr->hints.initial_state = - (zoomPart == inZoomIn ? NormalState : ZoomState); + [window zoom:NSApp]; + + wmPtr->hints.initial_state = + (zoomPart == inZoomIn ? NormalState : ZoomState); return true; } @@ -6501,6 +6525,7 @@ TkMacOSXMakeFullscreen( result = TCL_ERROR; wmPtr->flags &= ~WM_FULLSCREEN; } else { + Tk_UnmapWindow((Tk_Window) winPtr); NSRect bounds = [window contentRectForFrameRect:[window frame]]; NSRect screenBounds = NSMakeRect(0, 0, screenWidth, screenHeight); @@ -6532,6 +6557,7 @@ TkMacOSXMakeFullscreen( [window setStyleMask: NSBorderlessWindowMask]; [NSApp setPresentationOptions: NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar]; + Tk_MapWindow((Tk_Window) winPtr); #endif /*TK_GOT_AT_LEAST_SNOW_LEOPARD*/ } else { wmPtr->flags &= ~WM_FULLSCREEN; @@ -6543,6 +6569,7 @@ TkMacOSXMakeFullscreen( } if (wasFullscreen && !(wmPtr->flags & WM_FULLSCREEN)) { + Tk_UnmapWindow((Tk_Window) winPtr); UInt64 oldAttributes = wmPtr->attributes; NSRect bounds = NSMakeRect(wmPtr->configX, tkMacOSXZeroScreenHeight - (wmPtr->configY + wmPtr->yInParent + wmPtr->configHeight), @@ -6556,6 +6583,7 @@ TkMacOSXMakeFullscreen( wmPtr->flags |= WM_SYNC_PENDING; [window setFrame:[window frameRectForContentRect:bounds] display:YES]; wmPtr->flags &= ~WM_SYNC_PENDING; + Tk_MapWindow((Tk_Window) winPtr); } return result; } diff --git a/macosx/tkMacOSXXStubs.c b/macosx/tkMacOSXXStubs.c index 1c2d908..00940b5 100644 --- a/macosx/tkMacOSXXStubs.c +++ b/macosx/tkMacOSXXStubs.c @@ -890,7 +890,6 @@ XGetImage( int bitmap_pad = 0; int bytes_per_row = 4*width; int size; - MacDrawable *macDraw = (MacDrawable *) d; // Where is this variable used? May it be removed? int scalefactor = 1; #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 NSWindow *win = TkMacOSXDrawableWindow(d); @@ -1369,7 +1368,7 @@ void Tk_ResetUserInactiveTime( Display *dpy) { - IOGPoint loc; + IOGPoint loc = {0, 0}; kern_return_t kr; NXEvent nullEvent = {NX_NULLEVENT, {0, 0}, 0, -1, 0}; enum { kNULLEventPostThrottle = 10 }; -- cgit v0.12