diff options
Diffstat (limited to 'tk8.6/macosx/tkMacOSXInit.c')
-rw-r--r-- | tk8.6/macosx/tkMacOSXInit.c | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/tk8.6/macosx/tkMacOSXInit.c b/tk8.6/macosx/tkMacOSXInit.c new file mode 100644 index 0000000..bf0b9f2 --- /dev/null +++ b/tk8.6/macosx/tkMacOSXInit.c @@ -0,0 +1,652 @@ +/* + * tkMacOSXInit.c -- + * + * This file contains Mac OS X -specific interpreter initialization + * functions. + * + * Copyright (c) 1995-1997 Sun Microsystems, Inc. + * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright (c) 2017 Marc Culler + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tkMacOSXPrivate.h" + +#include <sys/stat.h> +#include <dlfcn.h> +#include <objc/objc-auto.h> + +static char tkLibPath[PATH_MAX + 1] = ""; + +/* + * If the App is in an App package, then we want to add the Scripts directory + * to the auto_path. + */ + +static char scriptPath[PATH_MAX + 1] = ""; + +#pragma mark TKApplication(TKInit) + +@implementation TKApplication +@synthesize poolLock = _poolLock; +@synthesize macMinorVersion = _macMinorVersion; +@synthesize isDrawing = _isDrawing; +@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 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 +{ + TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); +} +#endif + +- (void) _setupApplicationNotifications +{ + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; +#define observe(n, s) \ + [nc addObserver:self selector:@selector(s) name:(n) object:nil] + observe(NSApplicationDidBecomeActiveNotification, applicationActivate:); + observe(NSApplicationDidResignActiveNotification, applicationDeactivate:); + observe(NSApplicationDidUnhideNotification, applicationShowHide:); + observe(NSApplicationDidHideNotification, applicationShowHide:); + observe(NSApplicationDidChangeScreenParametersNotification, displayChanged:); + observe(NSTextInputContextKeyboardSelectionDidChangeNotification, keyboardChanged:); +#undef observe +} + +-(void)applicationWillFinishLaunching:(NSNotification *)aNotification +{ + + /* + * Initialize notifications. + */ +#ifdef TK_MAC_DEBUG_NOTIFICATIONS + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(_postedNotification:) name:nil object:nil]; +#endif + [self _setupWindowNotifications]; + [self _setupApplicationNotifications]; + + /* + * Construct the menu bar. + */ + _defaultMainMenu = nil; + [self _setupMenus]; + + /* + * Initialize event processing. + */ + TkMacOSXInitAppleEvents(_eventInterp); + + /* + * Initialize the graphics context. + */ + TkMacOSXUseAntialiasedText(_eventInterp, -1); + TkMacOSXInitCGDrawing(_eventInterp, TRUE, 0); +} + +-(void)applicationDidFinishLaunching:(NSNotification *)notification +{ + + /* + * It is not safe to force activation of the NSApp until this method is + * called. Activating too early can cause the menu bar to be unresponsive. + */ + + [NSApp activateIgnoringOtherApps: YES]; + + /* + * Process events to ensure that the root window is fully initialized. See + * ticket 56a1823c73. + */ + + [NSApp _lockAutoreleasePool]; + while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS| TCL_DONT_WAIT)) {} + [NSApp _unlockAutoreleasePool]; +} + +- (void) _setup: (Tcl_Interp *) interp +{ + /* + * Remember our interpreter. + */ + _eventInterp = interp; + + /* + * Install the global autoreleasePool. + */ + _mainPool = [NSAutoreleasePool new]; + [NSApp setPoolLock:0]; + + /* + * Record the OS version we are running on. + */ + int minorVersion; +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 + Gestalt(gestaltSystemVersionMinor, (SInt32*)&minorVersion); +#else + NSOperatingSystemVersion systemVersion; + systemVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; + minorVersion = systemVersion.minorVersion; +#endif + [NSApp setMacMinorVersion: minorVersion]; + + /* + * We are not drawing right now. + */ + + [NSApp setIsDrawing:NO]; + + /* + * Be our own delegate. + */ + + [self setDelegate:self]; + + /* + * Make sure we are allowed to open windows. + */ + + [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; + + /* + * If no icon has been set from an Info.plist file, use the Wish icon from + * the Tk framework. + */ + NSString *iconFile = [[NSBundle mainBundle] objectForInfoDictionaryKey: + @"CFBundleIconFile"]; + if (!iconFile) { + NSString *path = [NSApp tkFrameworkImagePath:@"Tk.icns"]; + if (path) { + NSImage *image = [[NSImage alloc] initWithContentsOfFile:path]; + if (image) { + [NSApp setApplicationIconImage:image]; + [image release]; + } + } + } +} + +- (NSString *) tkFrameworkImagePath: (NSString *) image +{ + NSString *path = nil; + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + if (tkLibPath[0] != '\0') { + path = [[NSBundle bundleWithPath:[[NSString stringWithUTF8String: + tkLibPath] stringByAppendingString:@"/../.."]] + pathForImageResource:image]; + } + if (!path) { + const char *tk_library = Tcl_GetVar2(_eventInterp, "tk_library", NULL, + TCL_GLOBAL_ONLY); + + if (tk_library) { + NSFileManager *fm = [NSFileManager defaultManager]; + + path = [[NSString stringWithUTF8String:tk_library] + stringByAppendingFormat:@"/%@", image]; + if (![fm isReadableFileAtPath:path]) { + path = [[NSString stringWithUTF8String:tk_library] + stringByAppendingFormat:@"/../macosx/%@", image]; + if (![fm isReadableFileAtPath:path]) { + path = nil; + } + } + } + } +#ifdef TK_MAC_DEBUG + if (!path && getenv("TK_SRCROOT")) { + path = [[NSString stringWithUTF8String:getenv("TK_SRCROOT")] + stringByAppendingFormat:@"/macosx/%@", image]; + if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) { + path = nil; + } + } +#endif + [path retain]; + [pool drain]; + return path; +} +@end + +#pragma mark - + +/* + *---------------------------------------------------------------------- + * + * TkpInit -- + * + * Performs Mac-specific interpreter initialization related to the + * tk_library variable. + * + * Results: + * Returns a standard Tcl result. Leaves an error message or result in + * the interp's result. + * + * Side effects: + * Sets "tk_library" Tcl variable, runs "tk.tcl" script. + * + *---------------------------------------------------------------------- + */ + +int +TkpInit( + Tcl_Interp *interp) +{ + static int initialized = 0; + + /* + * Since it is possible for TkInit to be called multiple times and we + * don't want to do the following initialization multiple times we protect + * against doing it more than once. + */ + + if (!initialized) { + struct stat st; + + initialized = 1; + + /* + * Initialize/check OS version variable for runtime checks. + */ + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 +# error Mac OS X 10.6 required +#endif + +#ifdef TK_FRAMEWORK + /* + * When Tk is in a framework, force tcl_findLibrary to look in the + * framework scripts directory. + * FIXME: Should we come up with a more generic way of doing this? + */ + + if (Tcl_MacOSXOpenVersionedBundleResources(interp, + "com.tcltk.tklibrary", TK_FRAMEWORK_VERSION, 0, PATH_MAX, + tkLibPath) != TCL_OK) { + # if 0 /* This is not really an error. Wish still runs fine. */ + TkMacOSXDbgMsg("Tcl_MacOSXOpenVersionedBundleResources failed"); + # endif + } +#endif + + /* + * FIXME: Close stdin & stdout for remote debugging otherwise we will + * fight with gdb for stdin & stdout + */ + + if (getenv("XCNOSTDIN") != NULL) { + close(0); + close(1); + } + + /* + * Instantiate our NSApplication object. This needs to be done before + * we check whether to open a console window. + */ + + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + [[NSUserDefaults standardUserDefaults] registerDefaults: + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:YES], + @"_NSCanWrapButtonTitles", + [NSNumber numberWithInt:-1], + @"NSStringDrawingTypesetterBehavior", + nil]]; + [TKApplication sharedApplication]; + [pool drain]; + [NSApp _setup:interp]; + [NSApp finishLaunching]; + Tk_MacOSXSetupTkNotifier(); + + /* + * If the root window is mapped before the App has finished launching + * it will open off screen (see ticket 56a1823c73). To avoid this we + * ask Tk to process an event with no wait. We expect Tcl_DoOneEvent + * to wait until the Mac event loop has been created and then return + * immediately since the queue is empty. + */ + + Tcl_DoOneEvent(TCL_WINDOW_EVENTS | TCL_DONT_WAIT); + + /* + * If we don't have a TTY and stdin is a special character file of + * length 0, (e.g. /dev/null, which is what Finder sets when double + * clicking Wish) then use the Tk based console interpreter. + */ + + if (getenv("TK_CONSOLE") || + (!isatty(0) && (fstat(0, &st) || + (S_ISCHR(st.st_mode) && st.st_blocks == 0)))) { + Tk_InitConsoleChannels(interp); + Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDIN)); + Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDOUT)); + Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDERR)); + + /* + * Only show the console if we don't have a startup script and + * tcl_interactive hasn't been set already. + */ + + if (Tcl_GetStartupScript(NULL) == NULL) { + const char *intvar = Tcl_GetVar2(interp, + "tcl_interactive", NULL, TCL_GLOBAL_ONLY); + + if (intvar == NULL) { + Tcl_SetVar2(interp, "tcl_interactive", NULL, "1", + TCL_GLOBAL_ONLY); + } + } + if (Tk_CreateConsoleWindow(interp) == TCL_ERROR) { + return TCL_ERROR; + } + } + + } + + if (tkLibPath[0] != '\0') { + Tcl_SetVar2(interp, "tk_library", NULL, tkLibPath, TCL_GLOBAL_ONLY); + } + + if (scriptPath[0] != '\0') { + Tcl_SetVar2(interp, "auto_path", NULL, scriptPath, + TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT|TCL_APPEND_VALUE); + } + + Tcl_CreateObjCommand(interp, "::tk::mac::standardAboutPanel", + TkMacOSXStandardAboutPanelObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::mac::registerServiceWidget", + TkMacOSXRegisterServiceWidgetObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::mac::iconBitmap", + TkMacOSXIconBitmapObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tk::mac::GetAppPath", TkMacOSXGetAppPath, NULL, NULL); + + /* + * Initialize the NSServices object here. Apple's docs say to do this + * in applicationDidFinishLaunching, but the Tcl interpreter is not + * initialized until this function call. + */ + + TkMacOSXServices_Init(interp); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TkpGetAppName -- + * + * Retrieves the name of the current application from a platform specific + * location. For Unix, the application name is the tail of the path + * contained in the tcl variable argv0. + * + * Results: + * Returns the application name in the given Tcl_DString. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkpGetAppName( + Tcl_Interp *interp, + Tcl_DString *namePtr) /* A previously initialized Tcl_DString. */ +{ + const char *p, *name; + + name = Tcl_GetVar2(interp, "argv0", NULL, TCL_GLOBAL_ONLY); + if ((name == NULL) || (*name == 0)) { + name = "tk"; + } else { + p = strrchr(name, '/'); + if (p != NULL) { + name = p+1; + } + } + Tcl_DStringAppend(namePtr, name, -1); +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXGetAppPath -- + * + * Returns the path of the Wish application bundle. + * + * Results: + * Returns the application path. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +int TkMacOSXGetAppPath( + ClientData cd, + Tcl_Interp *ip, + int objc, + Tcl_Obj *const objv[]) +{ + + CFURLRef mainBundleURL = CFBundleCopyBundleURL(CFBundleGetMainBundle()); + + + /* + * Convert the URL reference into a string reference. + */ + + CFStringRef appPath = CFURLCopyFileSystemPath(mainBundleURL, kCFURLPOSIXPathStyle); + + /* + * Get the system encoding method. + */ + + CFStringEncoding encodingMethod = CFStringGetSystemEncoding(); + + /* + * Convert the string reference into a C string. + */ + + char *path = (char *) CFStringGetCStringPtr(appPath, encodingMethod); + + Tcl_SetResult(ip, path, NULL); + + CFRelease(mainBundleURL); + CFRelease(appPath); + return TCL_OK; + +} + +/* + *---------------------------------------------------------------------- + * + * TkpDisplayWarning -- + * + * This routines is called from Tk_Main to display warning messages that + * occur during startup. + * + * Results: + * None. + * + * Side effects: + * Generates messages on stdout. + * + *---------------------------------------------------------------------- + */ + +void +TkpDisplayWarning( + const char *msg, /* Message to be displayed. */ + const char *title) /* Title of warning. */ +{ + Tcl_Channel errChannel = Tcl_GetStdChannel(TCL_STDERR); + + if (errChannel) { + Tcl_WriteChars(errChannel, title, -1); + Tcl_WriteChars(errChannel, ": ", 2); + Tcl_WriteChars(errChannel, msg, -1); + Tcl_WriteChars(errChannel, "\n", 1); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXDefaultStartupScript -- + * + * On MacOS X, we look for a file in the Resources/Scripts directory + * called AppMain.tcl and if found, we set argv[1] to that, so that the + * rest of the code will find it, and add the Scripts folder to the + * auto_path. If we don't find the startup script, we just bag it, + * assuming the user is starting up some other way. + * + * Results: + * None. + * + * Side effects: + * Tcl_SetStartupScript() called when AppMain.tcl found. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE void +TkMacOSXDefaultStartupScript(void) +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + CFBundleRef bundleRef = CFBundleGetMainBundle(); + + if (bundleRef != NULL) { + CFURLRef appMainURL = CFBundleCopyResourceURL(bundleRef, + CFSTR("AppMain"), CFSTR("tcl"), CFSTR("Scripts")); + + if (appMainURL != NULL) { + CFURLRef scriptFldrURL; + char startupScript[PATH_MAX + 1]; + + if (CFURLGetFileSystemRepresentation (appMainURL, true, + (unsigned char *) startupScript, PATH_MAX)) { + Tcl_SetStartupScript(Tcl_NewStringObj(startupScript,-1), NULL); + scriptFldrURL = CFURLCreateCopyDeletingLastPathComponent(NULL, + appMainURL); + if (scriptFldrURL != NULL) { + CFURLGetFileSystemRepresentation(scriptFldrURL, true, + (unsigned char *) scriptPath, PATH_MAX); + CFRelease(scriptFldrURL); + } + } + CFRelease(appMainURL); + } + } + [pool drain]; +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXGetNamedSymbol -- + * + * Dynamically acquire address of a named symbol from a loaded dynamic + * library, so that we can use API that may not be available on all OS + * versions. + * + * Results: + * Address of given symbol or NULL if unavailable. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE void* +TkMacOSXGetNamedSymbol( + const char* module, + const char* symbol) +{ + void *addr = dlsym(RTLD_NEXT, symbol); + if (!addr) { + (void) dlerror(); /* Clear dlfcn error state */ + } + return addr; +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXGetStringObjFromCFString -- + * + * Get a string object from a CFString as efficiently as possible. + * + * Results: + * New string object or NULL if conversion failed. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +MODULE_SCOPE Tcl_Obj* +TkMacOSXGetStringObjFromCFString( + CFStringRef str) +{ + Tcl_Obj *obj = NULL; + const char *c = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); + + if (c) { + obj = Tcl_NewStringObj(c, -1); + } else { + CFRange all = CFRangeMake(0, CFStringGetLength(str)); + CFIndex len; + + if (CFStringGetBytes(str, all, kCFStringEncodingUTF8, 0, false, NULL, + 0, &len) > 0 && len < INT_MAX) { + obj = Tcl_NewObj(); + Tcl_SetObjLength(obj, len); + CFStringGetBytes(str, all, kCFStringEncodingUTF8, 0, false, + (UInt8*) obj->bytes, len, NULL); + } + } + return obj; +} + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ |