/* * 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 * * 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 #include #include #include 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] = ""; long tkMacOSXMacOSXVersion = 0; #pragma mark TKApplication(TKInit) #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 #define NSTextInputContextKeyboardSelectionDidChangeNotification @"NSTextInputContextKeyboardSelectionDidChangeNotification" static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { [[NSNotificationCenter defaultCenter] postNotificationName:NSTextInputContextKeyboardSelectionDidChangeNotification object:nil userInfo:nil]; } #endif @interface TKApplication(TKKeyboard) - (void) keyboardChanged: (NSNotification *) notification; @end #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 #define TKApplication_NSApplicationDelegate #else #define TKApplication_NSApplicationDelegate #endif @interface TKApplication(TKWindowEvent) TKApplication_NSApplicationDelegate - (void) _setupWindowNotifications; @end @interface TKApplication(TKMenus) - (void) _setupMenus; @end @implementation TKApplication @synthesize poolProtected = _poolProtected; @end @implementation TKApplication(TKInit) - (void) _resetAutoreleasePool { if(![self poolProtected]) { [_mainPool drain]; _mainPool = [NSAutoreleasePool new]; } } #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 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), NULL, &keyboardChanged, kTISNotifySelectedKeyboardInputSourceChanged, NULL, CFNotificationSuspensionBehaviorCoalesce); #endif } - (void) _setupEventLoop { NSAutoreleasePool *pool = [NSAutoreleasePool new]; [self finishLaunching]; [self setWindowsNeedUpdate:YES]; [pool drain]; } - (void) _setup: (Tcl_Interp *) interp { _eventInterp = interp; _mainPool = [NSAutoreleasePool new]; [NSApp setPoolProtected:NO]; _defaultMainMenu = nil; [self _setupMenus]; [self setDelegate:self]; #ifdef TK_MAC_DEBUG_NOTIFICATIONS [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_postedNotification:) name:nil object:nil]; #endif [self _setupWindowNotifications]; [self _setupApplicationNotifications]; } - (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 - /* *---------------------------------------------------------------------- * * DoWindowActivate -- * * Idle handler that sets the application icon to the generic Tk icon. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void SetApplicationIcon( ClientData clientData) { NSAutoreleasePool *pool = [NSAutoreleasePool new]; NSString *path = [NSApp tkFrameworkImagePath:@"Tk.icns"]; if (path) { NSImage *image = [[NSImage alloc] initWithContentsOfFile:path]; if (image) { [NSApp setApplicationIconImage:image]; [image release]; } } [pool drain]; } /* *---------------------------------------------------------------------- * * 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) { int bundledExecutable = 0; CFBundleRef bundleRef; CFURLRef bundleUrl = NULL; struct utsname name; struct stat st; initialized = 1; /* * Initialize/check OS version variable for runtime checks. */ #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 # error Mac OS X 10.5 required #endif if (!uname(&name)) { tkMacOSXMacOSXVersion = (strtod(name.release, NULL) + 96) * 10; } /*Check for new versioning scheme on Yosemite (10.10) and later.*/ if (MAC_OS_X_VERSION_MIN_REQUIRED > 100000) { tkMacOSXMacOSXVersion = MAC_OS_X_VERSION_MIN_REQUIRED/100; } if (tkMacOSXMacOSXVersion && MAC_OS_X_VERSION_MIN_REQUIRED < 100000 && tkMacOSXMacOSXVersion/10 < MAC_OS_X_VERSION_MIN_REQUIRED/10) { Tcl_Panic("Mac OS X 10.%d or later required !", (MAC_OS_X_VERSION_MIN_REQUIRED/10)-100); } #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) { TkMacOSXDbgMsg("Tcl_MacOSXOpenVersionedBundleResources failed"); } #endif { 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]; } /* Check whether we are a bundled executable: */ bundleRef = CFBundleGetMainBundle(); if (bundleRef) { bundleUrl = CFBundleCopyBundleURL(bundleRef); } if (bundleUrl) { /* * A bundled executable is two levels down from its main bundle * directory (e.g. Wish.app/Contents/MacOS/Wish), whereas an * unbundled executable's main bundle directory is just the * directory containing the executable. So to check whether we are * bundled, we delete the last three path components of the * executable's url and compare the resulting url with the main * bundle url. */ int j = 3; CFURLRef url = CFBundleCopyExecutableURL(bundleRef); while (url && j--) { CFURLRef parent = CFURLCreateCopyDeletingLastPathComponent(NULL, url); CFRelease(url); url = parent; } if (url) { bundledExecutable = CFEqual(bundleUrl, url); CFRelease(url); } CFRelease(bundleUrl); } if (!bundledExecutable) { /* * If we are loaded into an executable that is not a bundled * application, the window server does not let us come to the * foreground. For such an executable, notify the window server * that we are now a full GUI application. */ OSStatus err = procNotFound; ProcessSerialNumber psn = { 0, kCurrentProcess }; err = ChkErr(TransformProcessType, &psn, kProcessTransformToForegroundApplication); /* * Set application icon to generic Tk icon, do it at idle time * instead of now to ensure tk_library is setup. */ Tcl_DoWhenIdle(SetApplicationIcon, NULL); } { NSAutoreleasePool *pool = [NSAutoreleasePool new]; [NSApp _setupEventLoop]; TkMacOSXInitAppleEvents(interp); TkMacOSXUseAntialiasedText(interp, -1); TkMacOSXInitCGDrawing(interp, TRUE, 0); [pool drain]; } /* * FIXME: Close stdin & stdout for remote debugging otherwise we will * fight with gdb for stdin & stdout */ if (getenv("XCNOSTDIN") != NULL) { close(0); close(1); } /* * 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; } } } Tk_MacOSXSetupTkNotifier(); 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::iconBitmap", TkMacOSXIconBitmapObjCmd, NULL, NULL); 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); } /* *---------------------------------------------------------------------- * * 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: */