1 /* 2 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #import <pthread.h> 27 #import <objc/runtime.h> 28 #import <Cocoa/Cocoa.h> 29 #import <Security/AuthSession.h> 30 #import <JavaNativeFoundation/JavaNativeFoundation.h> 31 #import <JavaRuntimeSupport/JavaRuntimeSupport.h> 32 33 #import "NSApplicationAWT.h" 34 #import "PropertiesUtilities.h" 35 #import "ThreadUtilities.h" 36 #import "AWT_debug.h" 37 #import "ApplicationDelegate.h" 38 39 #define DEBUG 0 40 41 42 // The symbol is defined in libosxapp.dylib (ThreadUtilities.m) 43 extern JavaVM *jvm; 44 45 static bool ShouldPrintVerboseDebugging() { 46 static int debug = -1; 47 if (debug == -1) { 48 debug = (int)(getenv("JAVA_AWT_VERBOSE") != NULL) || (DEBUG != 0); 49 } 50 return (bool)debug; 51 } 52 53 // This is the data necessary to have JNI_OnLoad wait for AppKit to start. 54 static BOOL sAppKitStarted = NO; 55 static pthread_mutex_t sAppKitStarted_mutex = PTHREAD_MUTEX_INITIALIZER; 56 static pthread_cond_t sAppKitStarted_cv = PTHREAD_COND_INITIALIZER; 57 58 void setBusy(BOOL isBusy); 59 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 60 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); 61 static void AWT_NSUncaughtExceptionHandler(NSException *exception); 62 63 static CFRunLoopObserverRef busyObserver = NULL; 64 static CFRunLoopObserverRef notBusyObserver = NULL; 65 66 static void setUpAWTAppKit(BOOL swt_mode, BOOL headless) { 67 AWT_ASSERT_APPKIT_THREAD; 68 BOOL verbose = ShouldPrintVerboseDebugging(); 69 if (verbose) AWT_DEBUG_LOG(@"setting up busy observers"); 70 71 JNIEnv *env = [ThreadUtilities getJNIEnv]; 72 73 // Add CFRunLoopObservers to call into AWT so that AWT knows that the 74 // AWT thread (which is the AppKit main thread) is alive. This way AWT 75 // will not automatically shutdown. 76 busyObserver = CFRunLoopObserverCreate( 77 NULL, // CFAllocator 78 kCFRunLoopAfterWaiting, // CFOptionFlags 79 true, // repeats 80 NSIntegerMax, // order 81 &BusyObserver, // CFRunLoopObserverCallBack 82 NULL); // CFRunLoopObserverContext 83 84 notBusyObserver = CFRunLoopObserverCreate( 85 NULL, // CFAllocator 86 kCFRunLoopBeforeWaiting, // CFOptionFlags 87 true, // repeats 88 NSIntegerMin, // order 89 &NotBusyObserver, // CFRunLoopObserverCallBack 90 NULL); // CFRunLoopObserverContext 91 92 CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; 93 CFRunLoopAddObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); 94 CFRunLoopAddObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); 95 96 CFRelease(busyObserver); 97 CFRelease(notBusyObserver); 98 99 if (!headless) setBusy(YES); 100 101 // Set the java name of the AppKit main thread appropriately. 102 jclass threadClass = NULL; 103 jstring name = NULL; 104 jobject curThread = NULL; 105 106 if (!swt_mode) { 107 threadClass = (*env)->FindClass(env, "java/lang/Thread"); 108 if (threadClass == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 109 jmethodID currentThreadID = (*env)->GetStaticMethodID(env, threadClass, "currentThread", "()Ljava/lang/Thread;"); 110 if (currentThreadID == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 111 jmethodID setName = (*env)->GetMethodID(env, threadClass, "setName", "(Ljava/lang/String;)V"); 112 if (setName == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 113 114 curThread = (*env)->CallStaticObjectMethod(env, threadClass, currentThreadID); // AWT_THREADING Safe (known object) 115 if (curThread == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 116 name = (*env)->NewStringUTF(env, "AWT-AppKit"); 117 if (name == NULL || (*env)->ExceptionCheck(env)) goto cleanup; 118 (*env)->CallVoidMethod(env, curThread, setName, name); // AWT_THREADING Safe (known object) 119 if ((*env)->ExceptionCheck(env)) goto cleanup; 120 } 121 122 cleanup: 123 if (threadClass != NULL) { 124 (*env)->DeleteLocalRef(env, threadClass); 125 } 126 if (name != NULL) { 127 (*env)->DeleteLocalRef(env, name); 128 } 129 if (curThread != NULL) { 130 (*env)->DeleteLocalRef(env, curThread); 131 } 132 if ((*env)->ExceptionCheck(env)) { 133 (*env)->ExceptionDescribe(env); 134 (*env)->ExceptionClear(env); 135 } 136 137 // Add the exception handler of last resort 138 NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler); 139 140 if (verbose) AWT_DEBUG_LOG(@"finished setting thread name"); 141 } 142 143 144 145 // Returns true if java believes it is running headless 146 BOOL isHeadless(JNIEnv *env) { 147 // Just access the property directly, instead of using GraphicsEnvironment.isHeadless. 148 // This is because this may be called while AWT is being loaded, and calling AWT 149 // while it is being loaded will deadlock. 150 static JNF_CLASS_CACHE(jc_Toolkit, "java/awt/GraphicsEnvironment"); 151 static JNF_STATIC_MEMBER_CACHE(jm_isHeadless, jc_Toolkit, "isHeadless", "()Z"); 152 return JNFCallStaticBooleanMethod(env, jm_isHeadless); 153 } 154 155 BOOL isSWTInWebStart(JNIEnv* env) { 156 NSString *swtWebStart = [PropertiesUtilities javaSystemPropertyForKey:@"com.apple.javaws.usingSWT" withEnv:env]; 157 return [@"true" isCaseInsensitiveLike:swtWebStart]; 158 } 159 160 void setBusy(BOOL busy) { 161 AWT_ASSERT_APPKIT_THREAD; 162 163 JNIEnv *env = [ThreadUtilities getJNIEnv]; 164 static JNF_CLASS_CACHE(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown"); 165 166 if (busy) { 167 static JNF_STATIC_MEMBER_CACHE(jm_notifyBusyMethod, jc_AWTAutoShutdown, "notifyToolkitThreadBusy", "()V"); 168 JNFCallStaticVoidMethod(env, jm_notifyBusyMethod); 169 } else { 170 static JNF_STATIC_MEMBER_CACHE(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V"); 171 JNFCallStaticVoidMethod(env, jm_notifyFreeMethod); 172 } 173 } 174 175 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 176 AWT_ASSERT_APPKIT_THREAD; 177 178 // This is only called with the selector kCFRunLoopAfterWaiting. 179 #ifndef PRODUCT_BUILD 180 assert(what == kCFRunLoopAfterWaiting); 181 #endif /* PRODUCT_BUILD */ 182 183 setBusy(YES); 184 } 185 186 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { 187 AWT_ASSERT_APPKIT_THREAD; 188 189 // This is only called with the selector kCFRunLoopBeforeWaiting. 190 #ifndef PRODUCT_BUILD 191 assert(what == kCFRunLoopBeforeWaiting); 192 #endif /* PRODUCT_BUILD */ 193 194 setBusy(NO); 195 } 196 197 static void AWT_NSUncaughtExceptionHandler(NSException *exception) { 198 NSLog(@"Apple AWT Internal Exception: %@", [exception description]); 199 } 200 201 // This is an empty Obj-C object just so that -peformSelectorOnMainThread can be used. 202 @interface AWTStarter : NSObject { } 203 + (void)start:(BOOL)headless swtMode:(BOOL)swtMode swtModeForWebStart:(BOOL)swtModeForWebStart; 204 - (void)starter:(NSArray*)args; 205 + (void)appKitIsRunning:(id)arg; 206 @end 207 208 @implementation AWTStarter 209 210 + (BOOL) isConnectedToWindowServer { 211 SecuritySessionId session_id; 212 SessionAttributeBits session_info; 213 OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info); 214 if (status != noErr) return NO; 215 if (!(session_info & sessionHasGraphicAccess)) return NO; 216 return YES; 217 } 218 219 + (BOOL) markAppAsDaemon { 220 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); 221 SEL markAppSel = @selector(markAppIsDaemon); 222 if (![jrsAppKitAWTClass respondsToSelector:markAppSel]) return NO; 223 return (BOOL)[jrsAppKitAWTClass performSelector:markAppSel]; 224 } 225 226 + (void)appKitIsRunning:(id)arg { 227 // Headless: NO 228 // Embedded: BOTH 229 // Multiple Calls: NO 230 // Callers: AppKit's NSApplicationDidFinishLaunchingNotification or +[AWTStarter startAWT:] 231 AWT_ASSERT_APPKIT_THREAD; 232 233 BOOL verbose = ShouldPrintVerboseDebugging(); 234 if (verbose) AWT_DEBUG_LOG(@"about to message AppKit started"); 235 236 // Signal that AppKit has started (or is already running). 237 pthread_mutex_lock(&sAppKitStarted_mutex); 238 sAppKitStarted = YES; 239 pthread_cond_signal(&sAppKitStarted_cv); 240 pthread_mutex_unlock(&sAppKitStarted_mutex); 241 242 if (verbose) AWT_DEBUG_LOG(@"finished messaging AppKit started"); 243 } 244 245 + (void)start:(BOOL)headless swtMode:(BOOL)swtMode swtModeForWebStart:(BOOL)swtModeForWebStart 246 { 247 BOOL verbose = ShouldPrintVerboseDebugging(); 248 249 // Headless: BOTH 250 // Embedded: BOTH 251 // Multiple Calls: NO 252 // Caller: JNI_OnLoad 253 254 // onMainThread is NOT the same at SWT mode! 255 // If the JVM was started on the first thread for SWT, but the SWT loads the AWT on a secondary thread, 256 // onMainThread here will be false but SWT mode will be true. If we are currently on the main thread, we don't 257 // need to throw AWT startup over to another thread. 258 BOOL onMainThread = (pthread_main_np() != 0); 259 260 if (verbose) { 261 NSString *msg = [NSString stringWithFormat:@"+[AWTStarter start headless:%d swtMode:%d swtModeForWebStart:%d] { onMainThread:%d }", headless, swtMode, swtModeForWebStart, onMainThread]; 262 AWT_DEBUG_LOG(msg); 263 } 264 265 if (!headless) 266 { 267 // Listen for the NSApp to start. This indicates that JNI_OnLoad can proceed. 268 // It must wait because there is a chance that another java thread will grab 269 // the AppKit lock before the +[NSApplication sharedApplication] returns. 270 // See <rdar://problem/3492666> for an example. 271 [[NSNotificationCenter defaultCenter] addObserver:[AWTStarter class] 272 selector:@selector(appKitIsRunning:) 273 name:NSApplicationDidFinishLaunchingNotification 274 object:nil]; 275 276 if (verbose) NSLog(@"+[AWTStarter start:::]: registered NSApplicationDidFinishLaunchingNotification"); 277 } 278 279 id st = [[AWTStarter alloc] init]; 280 281 NSArray * args = [NSArray arrayWithObjects: 282 [NSNumber numberWithBool: onMainThread], 283 [NSNumber numberWithBool: swtMode], 284 [NSNumber numberWithBool: headless], 285 [NSNumber numberWithBool: swtModeForWebStart], 286 [NSNumber numberWithBool: verbose], 287 nil]; 288 289 if (onMainThread) { 290 [st starter:args]; 291 } else { 292 [st performSelectorOnMainThread: @selector(starter:) withObject:args waitUntilDone:NO]; 293 } 294 295 if (!headless && !onMainThread) { 296 if (verbose) AWT_DEBUG_LOG(@"about to wait on AppKit startup mutex"); 297 298 // Wait here for AppKit to have started (or for AWT to have been loaded into 299 // an already running NSApplication). 300 pthread_mutex_lock(&sAppKitStarted_mutex); 301 while (sAppKitStarted == NO) { 302 pthread_cond_wait(&sAppKitStarted_cv, &sAppKitStarted_mutex); 303 } 304 pthread_mutex_unlock(&sAppKitStarted_mutex); 305 306 // AWT gets here AFTER +[AWTStarter appKitIsRunning:] is called. 307 if (verbose) AWT_DEBUG_LOG(@"got out of the AppKit startup mutex"); 308 } 309 310 // Don't set the delegate until the NSApplication has been created and 311 // its finishLaunching has initialized it. 312 // ApplicationDelegate is the support code for com.apple.eawt. 313 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){ 314 OSXAPP_SetApplicationDelegate([ApplicationDelegate sharedDelegate]); 315 }]; 316 } 317 318 - (void)starter:(NSArray*)args { 319 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 320 321 BOOL onMainThread = [[args objectAtIndex:0] boolValue]; 322 BOOL swtMode = [[args objectAtIndex:1] boolValue]; 323 BOOL headless = [[args objectAtIndex:2] boolValue]; 324 BOOL swtModeForWebStart = [[args objectAtIndex:3] boolValue]; 325 BOOL verbose = [[args objectAtIndex:4] boolValue]; 326 327 BOOL wasOnMainThread = onMainThread; 328 329 setUpAWTAppKit(swtMode, headless); 330 331 // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return. 332 if (headless) { 333 BOOL didBecomeDaemon = [AWTStarter markAppAsDaemon]; 334 return; 335 } 336 337 if (swtMode || swtModeForWebStart) { 338 if (verbose) NSLog(@"in SWT or SWT/WebStart mode"); 339 340 // The SWT should call NSApplicationLoad, but they don't know a priori that they will be using the AWT, so they don't. 341 NSApplicationLoad(); 342 } 343 344 // This will create a NSApplicationAWT for standalone AWT programs, unless there is 345 // already a NSApplication instance. If there is already a NSApplication instance, 346 // and -[NSApplication isRunning] returns YES, AWT is embedded inside another 347 // AppKit Application. 348 NSApplication *app = [NSApplicationAWT sharedApplication]; 349 350 // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent. 351 if (![app isRunning]) { 352 if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]"); 353 354 // This is where the AWT AppKit thread parks itself to process events. 355 [NSApplicationAWT runAWTLoopWithApp: app]; 356 } else { 357 // We're either embedded, or showing a splash screen 358 if (![NSApp isKindOfClass:[NSApplicationAWT class]]) { 359 if (verbose) AWT_DEBUG_LOG(@"running embedded"); 360 361 // Since we're embedded, no need to be swamping the runloop with the observers. 362 CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; 363 CFRunLoopRemoveObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); 364 CFRunLoopRemoveObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); 365 // We don't track if the runloop is busy, so set it free to let AWT finish when it needs 366 setBusy(NO); 367 busyObserver = NULL; 368 notBusyObserver = NULL; 369 } else { 370 if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen"); 371 } 372 373 // Signal so that JNI_OnLoad can proceed. 374 if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil]; 375 376 // Proceed to exit this call as there is no reason to run the NSApplication event loop. 377 } 378 379 [pool drain]; 380 } 381 382 @end 383 384 385 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 386 BOOL verbose = ShouldPrintVerboseDebugging(); 387 if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad"); 388 389 // Headless: BOTH 390 // Embedded: BOTH 391 // Multiple Calls: NO 392 // Caller: JavaVM classloader 393 394 // Keep a static reference for other archives. 395 OSXAPP_SetJavaVM(vm); 396 397 JNIEnv *env = NULL; 398 399 // Need JNIEnv for JNF_COCOA_ENTER(env); macro below 400 jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4); 401 if (status != JNI_OK || env == NULL) { 402 AWT_DEBUG_LOG(@"Can't get JNIEnv"); 403 return JNI_VERSION_1_4; 404 } 405 406 // The following is true when AWT is attempting to connect to the window server 407 // when it isn't set up properly to do so. 408 // BOOL AWTLoadFailure = YES; For now we are skipping this check so i'm commenting out this variable as unused 409 JNF_COCOA_ENTER(env); 410 411 // If -XstartOnFirstThread was used at invocation time, an environment variable will be set. 412 // (See java_md.c for the matching setenv call.) When that happens, we assume the SWT will be in use. 413 BOOL swt_compatible_mode = NO; 414 415 char envVar[80]; 416 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); 417 if (getenv(envVar) != NULL) { 418 swt_compatible_mode = YES; 419 unsetenv(envVar); 420 } 421 422 BOOL swt_in_webstart = isSWTInWebStart(env); 423 BOOL headless = isHeadless(env); 424 425 // Make sure we're on the right thread. If not, we still need the JNIEnv to throw an exception. 426 if (pthread_main_np() != 0 && !swt_compatible_mode && !headless) { 427 AWT_DEBUG_LOG(@"Apple AWT Java VM was loaded on first thread -- can't start AWT."); 428 [JNFException raise:env as:kInternalError reason:"Can't start the AWT because Java was started on the first thread. Make sure StartOnFirstThread is " 429 "not specified in your application's Info.plist or on the command line"]; 430 return JNI_VERSION_1_4; 431 } 432 433 // We need to let Foundation know that this is a multithreaded application, if it isn't already. 434 if (![NSThread isMultiThreaded]) { 435 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]; 436 } 437 438 // if (swt_compatible_mode || headless || [AWTStarter isConnectedToWindowServer] || [AWTStarter isRemoteSession]) { 439 // No need in this check - we will try to launch AWTStarter anyways - to be able to run GUI application remotely 440 // AWTLoadFailure = NO; 441 442 [AWTStarter start:headless swtMode:swt_compatible_mode swtModeForWebStart:swt_in_webstart]; 443 444 // } 445 446 /* if (AWTLoadFailure) { // We will not reach this code anyways 447 [JNFException raise:env as:kInternalError reason:"Can't connect to window server - not enough permissions."]; 448 } */ 449 450 JNF_COCOA_EXIT(env); 451 452 if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad"); 453 454 return JNI_VERSION_1_4; 455 }