1/* 2 * tkMacOSXInit.c -- 3 * 4 * This file contains Mac OS X -specific interpreter initialization 5 * functions. 6 * 7 * Copyright (c) 1995-1997 Sun Microsystems, Inc. 8 * Copyright 2001-2009, Apple Inc. 9 * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net> 10 * 11 * See the file "license.terms" for information on usage and redistribution of 12 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 13 * 14 * RCS: @(#) $Id$ 15 */ 16 17#include "tkMacOSXPrivate.h" 18 19#include "tclInt.h" /* for Tcl_GetStartupScript() & Tcl_SetStartupScript() */ 20 21#include <sys/stat.h> 22#include <sys/utsname.h> 23#include <dlfcn.h> 24#include <objc/objc-auto.h> 25 26static char tkLibPath[PATH_MAX + 1] = ""; 27 28/* 29 * If the App is in an App package, then we want to add the Scripts directory 30 * to the auto_path. 31 */ 32 33static char scriptPath[PATH_MAX + 1] = ""; 34 35int tkMacOSXGCEnabled = 0; 36long tkMacOSXMacOSXVersion = 0; 37 38#pragma mark TKApplication(TKInit) 39 40#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 41#define NSTextInputContextKeyboardSelectionDidChangeNotification @"NSTextInputContextKeyboardSelectionDidChangeNotification" 42static void keyboardChanged(CFNotificationCenterRef center, void *observer, CFStringRef name, const void *object, CFDictionaryRef userInfo) { 43 [[NSNotificationCenter defaultCenter] postNotificationName:NSTextInputContextKeyboardSelectionDidChangeNotification object:nil userInfo:nil]; 44} 45#endif 46 47@interface TKApplication(TKKeyboard) 48- (void)keyboardChanged:(NSNotification *)notification; 49@end 50 51#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 52#define TKApplication_NSApplicationDelegate <NSApplicationDelegate> 53#else 54#define TKApplication_NSApplicationDelegate 55#endif 56@interface TKApplication(TKWindowEvent) TKApplication_NSApplicationDelegate 57- (void)_setupWindowNotifications; 58@end 59 60@interface TKApplication(TKScrlbr) 61- (void)_setupScrollBarNotifications; 62@end 63 64@interface TKApplication(TKMenus) 65- (void)_setupMenus; 66@end 67 68@implementation TKApplication 69@end 70 71@implementation TKApplication(TKInit) 72#ifdef TK_MAC_DEBUG_NOTIFICATIONS 73- (void)_postedNotification:(NSNotification *)notification { 74 TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); 75} 76#endif 77- (void)_setupApplicationNotifications { 78 NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 79#define observe(n, s) [nc addObserver:self selector:@selector(s) name:(n) object:nil] 80 observe(NSApplicationDidBecomeActiveNotification, applicationActivate:); 81 observe(NSApplicationDidResignActiveNotification, applicationDeactivate:); 82 observe(NSApplicationDidUnhideNotification, applicationShowHide:); 83 observe(NSApplicationDidHideNotification, applicationShowHide:); 84 observe(NSApplicationDidChangeScreenParametersNotification, displayChanged:); 85 observe(NSTextInputContextKeyboardSelectionDidChangeNotification, keyboardChanged:); 86#undef observe 87#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 88 CFNotificationCenterAddObserver(CFNotificationCenterGetDistributedCenter(), NULL, &keyboardChanged, kTISNotifySelectedKeyboardInputSourceChanged, NULL, CFNotificationSuspensionBehaviorCoalesce); 89#endif 90} 91- (void)_setupEventLoop { 92 _running = 1; 93 if (!_appFlags._hasBeenRun) { 94 _appFlags._hasBeenRun = YES; 95 [self finishLaunching]; 96 } 97 [self setWindowsNeedUpdate:YES]; 98} 99- (void)_setup:(Tcl_Interp *)interp { 100 _eventInterp = interp; 101 _defaultMainMenu = nil; 102 [self _setupMenus]; 103 [self setDelegate:self]; 104#ifdef TK_MAC_DEBUG_NOTIFICATIONS 105 [[NSNotificationCenter defaultCenter] addObserver:self 106 selector:@selector(_postedNotification:) name:nil object:nil]; 107#endif 108 [self _setupWindowNotifications]; 109 [self _setupScrollBarNotifications]; 110 [self _setupApplicationNotifications]; 111} 112- (NSString *)tkFrameworkImagePath:(NSString*)image { 113 NSString *path = nil; 114 if (tkLibPath[0] != '\0') { 115 path = [[NSBundle bundleWithPath:[[NSString stringWithUTF8String: 116 tkLibPath] stringByAppendingString:@"/../.."]] 117 pathForImageResource:image]; 118 } 119 if (!path) { 120 const char *tk_library = Tcl_GetVar2(_eventInterp, "tk_library", NULL, 121 TCL_GLOBAL_ONLY); 122 if (tk_library) { 123 NSFileManager *fm = [NSFileManager defaultManager]; 124 path = [[NSString stringWithUTF8String:tk_library] 125 stringByAppendingFormat:@"/%@", image]; 126 if (![fm isReadableFileAtPath:path]) { 127 path = [[NSString stringWithUTF8String:tk_library] 128 stringByAppendingFormat:@"/../macosx/%@", image]; 129 if (![fm isReadableFileAtPath:path]) { 130 path = nil; 131 } 132 } 133 } 134 } 135#ifdef TK_MAC_DEBUG 136 if (!path && getenv("TK_SRCROOT")) { 137 path = [[NSString stringWithUTF8String:getenv("TK_SRCROOT")] 138 stringByAppendingFormat:@"/macosx/%@", image]; 139 if (![[NSFileManager defaultManager] isReadableFileAtPath:path]) { 140 path = nil; 141 } 142 } 143#endif 144 return path; 145} 146@end 147 148#pragma mark - 149 150/* 151 *---------------------------------------------------------------------- 152 * 153 * DoWindowActivate -- 154 * 155 * Idle handler that sets the application icon to the generic Tk icon. 156 * 157 * Results: 158 * None. 159 * 160 * Side effects: 161 * None. 162 * 163 *---------------------------------------------------------------------- 164 */ 165 166static void 167SetApplicationIcon( 168 ClientData clientData) 169{ 170 NSString *path = [NSApp tkFrameworkImagePath:@"Tk.icns"]; 171 if (path) { 172 NSImage *image = [[NSImage alloc] initWithContentsOfFile:path]; 173 if (image) { 174 [NSApp setApplicationIconImage:image]; 175 [image release]; 176 } 177 } 178} 179 180/* 181 *---------------------------------------------------------------------- 182 * 183 * TkpInit -- 184 * 185 * Performs Mac-specific interpreter initialization related to the 186 * tk_library variable. 187 * 188 * Results: 189 * Returns a standard Tcl result. Leaves an error message or result in 190 * the interp's result. 191 * 192 * Side effects: 193 * Sets "tk_library" Tcl variable, runs "tk.tcl" script. 194 * 195 *---------------------------------------------------------------------- 196 */ 197 198int 199TkpInit( 200 Tcl_Interp *interp) 201{ 202 static int initialized = 0; 203 204 /* 205 * Since it is possible for TkInit to be called multiple times and we 206 * don't want to do the following initialization multiple times we protect 207 * against doing it more than once. 208 */ 209 210 if (!initialized) { 211 int bundledExecutable = 0; 212 CFBundleRef bundleRef; 213 CFURLRef bundleUrl = NULL; 214 struct utsname name; 215 struct stat st; 216 217 initialized = 1; 218 219 /* 220 * Initialize/check OS version variable for runtime checks. 221 */ 222 223#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 224# error Mac OS X 10.5 required 225#endif 226 227 if (!uname(&name)) { 228 tkMacOSXMacOSXVersion = (strtod(name.release, NULL) + 96) * 10; 229 } 230 if (tkMacOSXMacOSXVersion && 231 tkMacOSXMacOSXVersion/10 < MAC_OS_X_VERSION_MIN_REQUIRED/10) { 232 Tcl_Panic("Mac OS X 10.%d or later required !", 233 (MAC_OS_X_VERSION_MIN_REQUIRED/10)-100); 234 } 235 236#ifdef TK_FRAMEWORK 237 /* 238 * When Tk is in a framework, force tcl_findLibrary to look in the 239 * framework scripts directory. 240 * FIXME: Should we come up with a more generic way of doing this? 241 */ 242 243 if (Tcl_MacOSXOpenVersionedBundleResources(interp, 244 "com.tcltk.tklibrary", TK_FRAMEWORK_VERSION, 0, PATH_MAX, 245 tkLibPath) != TCL_OK) { 246 TkMacOSXDbgMsg("Tcl_MacOSXOpenVersionedBundleResources failed"); 247 } 248#endif 249 250 static NSAutoreleasePool *pool = nil; 251 if (!pool) { 252 pool = [NSAutoreleasePool new]; 253 } 254 tkMacOSXGCEnabled = ([NSGarbageCollector defaultCollector] != nil); 255 [[NSUserDefaults standardUserDefaults] registerDefaults: 256 [NSDictionary dictionaryWithObjectsAndKeys: 257 [NSNumber numberWithBool:YES], 258 @"_NSCanWrapButtonTitles", 259 [NSNumber numberWithInt:-1], 260 @"NSStringDrawingTypesetterBehavior", 261 nil]]; 262 [TKApplication sharedApplication]; 263 [NSApp _setup:interp]; 264 265 /* Check whether we are a bundled executable: */ 266 bundleRef = CFBundleGetMainBundle(); 267 if (bundleRef) { 268 bundleUrl = CFBundleCopyBundleURL(bundleRef); 269 } 270 if (bundleUrl) { 271 /* 272 * A bundled executable is two levels down from its main bundle 273 * directory (e.g. Wish.app/Contents/MacOS/Wish), whereas an 274 * unbundled executable's main bundle directory is just the 275 * directory containing the executable. So to check whether we are 276 * bundled, we delete the last three path components of the 277 * executable's url and compare the resulting url with the main 278 * bundle url. 279 */ 280 281 int j = 3; 282 CFURLRef url = CFBundleCopyExecutableURL(bundleRef); 283 284 while (url && j--) { 285 CFURLRef parent = 286 CFURLCreateCopyDeletingLastPathComponent(NULL, url); 287 288 CFRelease(url); 289 url = parent; 290 } 291 if (url) { 292 bundledExecutable = CFEqual(bundleUrl, url); 293 CFRelease(url); 294 } 295 CFRelease(bundleUrl); 296 } 297 298 if (!bundledExecutable) { 299 /* 300 * If we are loaded into an executable that is not a bundled 301 * application, the window server does not let us come to the 302 * foreground. For such an executable, notify the window server 303 * that we are now a full GUI application. 304 */ 305 306 OSStatus err = procNotFound; 307 ProcessSerialNumber psn = { 0, kCurrentProcess }; 308 309 err = ChkErr(TransformProcessType, &psn, 310 kProcessTransformToForegroundApplication); 311 312 /* 313 * Set application icon to generic Tk icon, do it at idle time 314 * instead of now to ensure tk_library is setup. 315 */ 316 317 Tcl_DoWhenIdle(SetApplicationIcon, NULL); 318 } 319 320 [NSApp _setupEventLoop]; 321 TkMacOSXInitAppleEvents(interp); 322 TkMacOSXUseAntialiasedText(interp, -1); 323 TkMacOSXInitCGDrawing(interp, TRUE, 0); 324 [pool drain]; 325 pool = [NSAutoreleasePool new]; 326 327 /* 328 * FIXME: Close stdin & stdout for remote debugging otherwise we will 329 * fight with gdb for stdin & stdout 330 */ 331 332 if (getenv("XCNOSTDIN") != NULL) { 333 close(0); 334 close(1); 335 } 336 337 /* 338 * If we don't have a TTY and stdin is a special character file of 339 * length 0, (e.g. /dev/null, which is what Finder sets when double 340 * clicking Wish) then use the Tk based console interpreter. 341 */ 342 343 if (getenv("TK_CONSOLE") || 344 (!isatty(0) && (fstat(0, &st) || 345 (S_ISCHR(st.st_mode) && st.st_blocks == 0)))) { 346 Tk_InitConsoleChannels(interp); 347 Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDIN)); 348 Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDOUT)); 349 Tcl_RegisterChannel(interp, Tcl_GetStdChannel(TCL_STDERR)); 350 351 /* 352 * Only show the console if we don't have a startup script 353 * and tcl_interactive hasn't been set already. 354 */ 355 356 if (Tcl_GetStartupScript(NULL) == NULL) { 357 const char *intvar = Tcl_GetVar(interp, 358 "tcl_interactive", TCL_GLOBAL_ONLY); 359 360 if (intvar == NULL) { 361 Tcl_SetVar(interp, "tcl_interactive", "1", 362 TCL_GLOBAL_ONLY); 363 } 364 } 365 if (Tk_CreateConsoleWindow(interp) == TCL_ERROR) { 366 return TCL_ERROR; 367 } 368 } 369 } 370 371 Tk_MacOSXSetupTkNotifier(); 372 373 if (tkLibPath[0] != '\0') { 374 Tcl_SetVar(interp, "tk_library", tkLibPath, TCL_GLOBAL_ONLY); 375 } 376 377 if (scriptPath[0] != '\0') { 378 Tcl_SetVar(interp, "auto_path", scriptPath, 379 TCL_GLOBAL_ONLY|TCL_LIST_ELEMENT|TCL_APPEND_VALUE); 380 } 381 382 Tcl_CreateObjCommand(interp, "::tk::mac::standardAboutPanel", 383 TkMacOSXStandardAboutPanelObjCmd, NULL, NULL); 384 Tcl_CreateObjCommand(interp, "::tk::mac::iconBitmap", 385 TkMacOSXIconBitmapObjCmd, NULL, NULL); 386 387 return TCL_OK; 388} 389 390/* 391 *---------------------------------------------------------------------- 392 * 393 * TkpGetAppName -- 394 * 395 * Retrieves the name of the current application from a platform specific 396 * location. For Unix, the application name is the tail of the path 397 * contained in the tcl variable argv0. 398 * 399 * Results: 400 * Returns the application name in the given Tcl_DString. 401 * 402 * Side effects: 403 * None. 404 * 405 *---------------------------------------------------------------------- 406 */ 407 408void 409TkpGetAppName( 410 Tcl_Interp *interp, 411 Tcl_DString *namePtr) /* A previously initialized Tcl_DString. */ 412{ 413 const char *p, *name; 414 415 name = Tcl_GetVar(interp, "argv0", TCL_GLOBAL_ONLY); 416 if ((name == NULL) || (*name == 0)) { 417 name = "tk"; 418 } else { 419 p = strrchr(name, '/'); 420 if (p != NULL) { 421 name = p+1; 422 } 423 } 424 Tcl_DStringAppend(namePtr, name, -1); 425} 426 427/* 428 *---------------------------------------------------------------------- 429 * 430 * TkpDisplayWarning -- 431 * 432 * This routines is called from Tk_Main to display warning messages that 433 * occur during startup. 434 * 435 * Results: 436 * None. 437 * 438 * Side effects: 439 * Generates messages on stdout. 440 * 441 *---------------------------------------------------------------------- 442 */ 443 444void 445TkpDisplayWarning( 446 const char *msg, /* Message to be displayed. */ 447 const char *title) /* Title of warning. */ 448{ 449 Tcl_Channel errChannel = Tcl_GetStdChannel(TCL_STDERR); 450 451 if (errChannel) { 452 Tcl_WriteChars(errChannel, title, -1); 453 Tcl_WriteChars(errChannel, ": ", 2); 454 Tcl_WriteChars(errChannel, msg, -1); 455 Tcl_WriteChars(errChannel, "\n", 1); 456 } 457} 458 459/* 460 *---------------------------------------------------------------------- 461 * 462 * TkMacOSXDefaultStartupScript -- 463 * 464 * On MacOS X, we look for a file in the Resources/Scripts directory 465 * called AppMain.tcl and if found, we set argv[1] to that, so that the 466 * rest of the code will find it, and add the Scripts folder to the 467 * auto_path. If we don't find the startup script, we just bag it, 468 * assuming the user is starting up some other way. 469 * 470 * Results: 471 * None. 472 * 473 * Side effects: 474 * Tcl_SetStartupScript() called when AppMain.tcl found. 475 * 476 *---------------------------------------------------------------------- 477 */ 478 479MODULE_SCOPE void 480TkMacOSXDefaultStartupScript(void) 481{ 482 CFBundleRef bundleRef; 483 484 bundleRef = CFBundleGetMainBundle(); 485 486 if (bundleRef != NULL) { 487 CFURLRef appMainURL = CFBundleCopyResourceURL(bundleRef, 488 CFSTR("AppMain"), CFSTR("tcl"), CFSTR("Scripts")); 489 490 if (appMainURL != NULL) { 491 CFURLRef scriptFldrURL; 492 char startupScript[PATH_MAX + 1]; 493 494 if (CFURLGetFileSystemRepresentation (appMainURL, true, 495 (unsigned char *) startupScript, PATH_MAX)) { 496 Tcl_SetStartupScript(Tcl_NewStringObj(startupScript,-1), NULL); 497 scriptFldrURL = CFURLCreateCopyDeletingLastPathComponent(NULL, 498 appMainURL); 499 if (scriptFldrURL != NULL) { 500 CFURLGetFileSystemRepresentation(scriptFldrURL, true, 501 (unsigned char *) scriptPath, PATH_MAX); 502 CFRelease(scriptFldrURL); 503 } 504 } 505 CFRelease(appMainURL); 506 } 507 } 508} 509 510/* 511 *---------------------------------------------------------------------- 512 * 513 * TkMacOSXGetNamedSymbol -- 514 * 515 * Dynamically acquire address of a named symbol from a loaded dynamic 516 * library, so that we can use API that may not be available on all OS 517 * versions. 518 * 519 * Results: 520 * Address of given symbol or NULL if unavailable. 521 * 522 * Side effects: 523 * None. 524 * 525 *---------------------------------------------------------------------- 526 */ 527 528MODULE_SCOPE void* 529TkMacOSXGetNamedSymbol( 530 const char* module, 531 const char* symbol) 532{ 533 void *addr = dlsym(RTLD_NEXT, symbol); 534 if (!addr) { 535 (void) dlerror(); /* Clear dlfcn error state */ 536 } 537 return addr; 538} 539 540/* 541 *---------------------------------------------------------------------- 542 * 543 * TkMacOSXGetStringObjFromCFString -- 544 * 545 * Get a string object from a CFString as efficiently as possible. 546 * 547 * Results: 548 * New string object or NULL if conversion failed. 549 * 550 * Side effects: 551 * None. 552 * 553 *---------------------------------------------------------------------- 554 */ 555 556MODULE_SCOPE Tcl_Obj* 557TkMacOSXGetStringObjFromCFString( 558 CFStringRef str) 559{ 560 Tcl_Obj *obj = NULL; 561 const char *c = CFStringGetCStringPtr(str, kCFStringEncodingUTF8); 562 563 if (c) { 564 obj = Tcl_NewStringObj(c, -1); 565 } else { 566 CFRange all = CFRangeMake(0, CFStringGetLength(str)); 567 CFIndex len; 568 569 if (CFStringGetBytes(str, all, kCFStringEncodingUTF8, 0, false, NULL, 570 0, &len) > 0 && len < INT_MAX) { 571 obj = Tcl_NewObj(); 572 Tcl_SetObjLength(obj, len); 573 CFStringGetBytes(str, all, kCFStringEncodingUTF8, 0, false, 574 (UInt8*) obj->bytes, len, NULL); 575 } 576 } 577 return obj; 578} 579 580/* 581 * Local Variables: 582 * mode: c 583 * c-basic-offset: 4 584 * fill-column: 79 585 * coding: utf-8 586 * End: 587 */ 588