/* * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #import #import #import #import #import #import #import "NSApplicationAWT.h" #import "PropertiesUtilities.h" #import "ThreadUtilities.h" #import "AWT_debug.h" #import "ApplicationDelegate.h" #define DEBUG 0 // The symbol is defined in libosxapp.dylib (ThreadUtilities.m) extern JavaVM *jvm; static bool ShouldPrintVerboseDebugging() { static int debug = -1; if (debug == -1) { debug = (int)(getenv("JAVA_AWT_VERBOSE") != NULL) || (DEBUG != 0); } return (bool)debug; } // This is the data necessary to have JNI_OnLoad wait for AppKit to start. static BOOL sAppKitStarted = NO; static pthread_mutex_t sAppKitStarted_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t sAppKitStarted_cv = PTHREAD_COND_INITIALIZER; void setBusy(BOOL isBusy); static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg); static void AWT_NSUncaughtExceptionHandler(NSException *exception); static CFRunLoopObserverRef busyObserver = NULL; static CFRunLoopObserverRef notBusyObserver = NULL; static void setUpAWTAppKit(BOOL swt_mode, BOOL headless) { AWT_ASSERT_APPKIT_THREAD; BOOL verbose = ShouldPrintVerboseDebugging(); if (verbose) AWT_DEBUG_LOG(@"setting up busy observers"); JNIEnv *env = [ThreadUtilities getJNIEnv]; // Add CFRunLoopObservers to call into AWT so that AWT knows that the // AWT thread (which is the AppKit main thread) is alive. This way AWT // will not automatically shutdown. busyObserver = CFRunLoopObserverCreate( NULL, // CFAllocator kCFRunLoopAfterWaiting, // CFOptionFlags true, // repeats NSIntegerMax, // order &BusyObserver, // CFRunLoopObserverCallBack NULL); // CFRunLoopObserverContext notBusyObserver = CFRunLoopObserverCreate( NULL, // CFAllocator kCFRunLoopBeforeWaiting, // CFOptionFlags true, // repeats NSIntegerMin, // order &NotBusyObserver, // CFRunLoopObserverCallBack NULL); // CFRunLoopObserverContext CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; CFRunLoopAddObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); CFRunLoopAddObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); CFRelease(busyObserver); CFRelease(notBusyObserver); if (!headless) setBusy(YES); // Set the java name of the AppKit main thread appropriately. jclass threadClass = NULL; jstring name = NULL; jobject curThread = NULL; if (!swt_mode) { threadClass = (*env)->FindClass(env, "java/lang/Thread"); if (threadClass == NULL || (*env)->ExceptionCheck(env)) goto cleanup; jmethodID currentThreadID = (*env)->GetStaticMethodID(env, threadClass, "currentThread", "()Ljava/lang/Thread;"); if (currentThreadID == NULL || (*env)->ExceptionCheck(env)) goto cleanup; jmethodID setName = (*env)->GetMethodID(env, threadClass, "setName", "(Ljava/lang/String;)V"); if (setName == NULL || (*env)->ExceptionCheck(env)) goto cleanup; curThread = (*env)->CallStaticObjectMethod(env, threadClass, currentThreadID); // AWT_THREADING Safe (known object) if (curThread == NULL || (*env)->ExceptionCheck(env)) goto cleanup; name = (*env)->NewStringUTF(env, "AWT-AppKit"); if (name == NULL || (*env)->ExceptionCheck(env)) goto cleanup; (*env)->CallVoidMethod(env, curThread, setName, name); // AWT_THREADING Safe (known object) if ((*env)->ExceptionCheck(env)) goto cleanup; } cleanup: if (threadClass != NULL) { (*env)->DeleteLocalRef(env, threadClass); } if (name != NULL) { (*env)->DeleteLocalRef(env, name); } if (curThread != NULL) { (*env)->DeleteLocalRef(env, curThread); } if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); } // Add the exception handler of last resort NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler); if (verbose) AWT_DEBUG_LOG(@"finished setting thread name"); } // Returns true if java believes it is running headless BOOL isHeadless(JNIEnv *env) { // Just access the property directly, instead of using GraphicsEnvironment.isHeadless. // This is because this may be called while AWT is being loaded, and calling AWT // while it is being loaded will deadlock. static JNF_CLASS_CACHE(jc_Toolkit, "java/awt/GraphicsEnvironment"); static JNF_STATIC_MEMBER_CACHE(jm_isHeadless, jc_Toolkit, "isHeadless", "()Z"); return JNFCallStaticBooleanMethod(env, jm_isHeadless); } BOOL isSWTInWebStart(JNIEnv* env) { NSString *swtWebStart = [PropertiesUtilities javaSystemPropertyForKey:@"com.apple.javaws.usingSWT" withEnv:env]; return [@"true" isCaseInsensitiveLike:swtWebStart]; } void setBusy(BOOL busy) { AWT_ASSERT_APPKIT_THREAD; JNIEnv *env = [ThreadUtilities getJNIEnv]; static JNF_CLASS_CACHE(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown"); if (busy) { static JNF_STATIC_MEMBER_CACHE(jm_notifyBusyMethod, jc_AWTAutoShutdown, "notifyToolkitThreadBusy", "()V"); JNFCallStaticVoidMethod(env, jm_notifyBusyMethod); } else { static JNF_STATIC_MEMBER_CACHE(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V"); JNFCallStaticVoidMethod(env, jm_notifyFreeMethod); } } static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { AWT_ASSERT_APPKIT_THREAD; // This is only called with the selector kCFRunLoopAfterWaiting. #ifndef PRODUCT_BUILD assert(what == kCFRunLoopAfterWaiting); #endif /* PRODUCT_BUILD */ setBusy(YES); } static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) { AWT_ASSERT_APPKIT_THREAD; // This is only called with the selector kCFRunLoopBeforeWaiting. #ifndef PRODUCT_BUILD assert(what == kCFRunLoopBeforeWaiting); #endif /* PRODUCT_BUILD */ setBusy(NO); } static void AWT_NSUncaughtExceptionHandler(NSException *exception) { NSLog(@"Apple AWT Internal Exception: %@", [exception description]); } // This is an empty Obj-C object just so that -peformSelectorOnMainThread can be used. @interface AWTStarter : NSObject { } + (void)start:(BOOL)headless swtMode:(BOOL)swtMode swtModeForWebStart:(BOOL)swtModeForWebStart; - (void)starter:(NSArray*)args; + (void)appKitIsRunning:(id)arg; @end @implementation AWTStarter + (BOOL) isConnectedToWindowServer { SecuritySessionId session_id; SessionAttributeBits session_info; OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info); if (status != noErr) return NO; if (!(session_info & sessionHasGraphicAccess)) return NO; return YES; } + (BOOL) markAppAsDaemon { id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); SEL markAppSel = @selector(markAppIsDaemon); if (![jrsAppKitAWTClass respondsToSelector:markAppSel]) return NO; return (BOOL)[jrsAppKitAWTClass performSelector:markAppSel]; } + (void)appKitIsRunning:(id)arg { // Headless: NO // Embedded: BOTH // Multiple Calls: NO // Callers: AppKit's NSApplicationDidFinishLaunchingNotification or +[AWTStarter startAWT:] AWT_ASSERT_APPKIT_THREAD; BOOL verbose = ShouldPrintVerboseDebugging(); if (verbose) AWT_DEBUG_LOG(@"about to message AppKit started"); // Signal that AppKit has started (or is already running). pthread_mutex_lock(&sAppKitStarted_mutex); sAppKitStarted = YES; pthread_cond_signal(&sAppKitStarted_cv); pthread_mutex_unlock(&sAppKitStarted_mutex); if (verbose) AWT_DEBUG_LOG(@"finished messaging AppKit started"); } + (void)start:(BOOL)headless swtMode:(BOOL)swtMode swtModeForWebStart:(BOOL)swtModeForWebStart { BOOL verbose = ShouldPrintVerboseDebugging(); // Headless: BOTH // Embedded: BOTH // Multiple Calls: NO // Caller: JNI_OnLoad // onMainThread is NOT the same at SWT mode! // If the JVM was started on the first thread for SWT, but the SWT loads the AWT on a secondary thread, // onMainThread here will be false but SWT mode will be true. If we are currently on the main thread, we don't // need to throw AWT startup over to another thread. BOOL onMainThread = (pthread_main_np() != 0); if (verbose) { NSString *msg = [NSString stringWithFormat:@"+[AWTStarter start headless:%d swtMode:%d swtModeForWebStart:%d] { onMainThread:%d }", headless, swtMode, swtModeForWebStart, onMainThread]; AWT_DEBUG_LOG(msg); } if (!headless) { // Listen for the NSApp to start. This indicates that JNI_OnLoad can proceed. // It must wait because there is a chance that another java thread will grab // the AppKit lock before the +[NSApplication sharedApplication] returns. // See for an example. [[NSNotificationCenter defaultCenter] addObserver:[AWTStarter class] selector:@selector(appKitIsRunning:) name:NSApplicationDidFinishLaunchingNotification object:nil]; if (verbose) NSLog(@"+[AWTStarter start:::]: registered NSApplicationDidFinishLaunchingNotification"); } id st = [[AWTStarter alloc] init]; NSArray * args = [NSArray arrayWithObjects: [NSNumber numberWithBool: onMainThread], [NSNumber numberWithBool: swtMode], [NSNumber numberWithBool: headless], [NSNumber numberWithBool: swtModeForWebStart], [NSNumber numberWithBool: verbose], nil]; if (onMainThread) { [st starter:args]; } else { [st performSelectorOnMainThread: @selector(starter:) withObject:args waitUntilDone:NO]; } if (!headless && !onMainThread) { if (verbose) AWT_DEBUG_LOG(@"about to wait on AppKit startup mutex"); // Wait here for AppKit to have started (or for AWT to have been loaded into // an already running NSApplication). pthread_mutex_lock(&sAppKitStarted_mutex); while (sAppKitStarted == NO) { pthread_cond_wait(&sAppKitStarted_cv, &sAppKitStarted_mutex); } pthread_mutex_unlock(&sAppKitStarted_mutex); // AWT gets here AFTER +[AWTStarter appKitIsRunning:] is called. if (verbose) AWT_DEBUG_LOG(@"got out of the AppKit startup mutex"); } // Don't set the delegate until the NSApplication has been created and // its finishLaunching has initialized it. // ApplicationDelegate is the support code for com.apple.eawt. void (^setDelegateBlock)() = ^(){ OSXAPP_SetApplicationDelegate([ApplicationDelegate sharedDelegate]); }; if (onMainThread) { setDelegateBlock(); } else { [JNFRunLoop performOnMainThreadWaiting:YES withBlock:setDelegateBlock]; } } - (void)starter:(NSArray*)args { NSAutoreleasePool *pool = [NSAutoreleasePool new]; BOOL onMainThread = [[args objectAtIndex:0] boolValue]; BOOL swtMode = [[args objectAtIndex:1] boolValue]; BOOL headless = [[args objectAtIndex:2] boolValue]; BOOL swtModeForWebStart = [[args objectAtIndex:3] boolValue]; BOOL verbose = [[args objectAtIndex:4] boolValue]; BOOL wasOnMainThread = onMainThread; setUpAWTAppKit(swtMode, headless); // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return. if (headless) { BOOL didBecomeDaemon = [AWTStarter markAppAsDaemon]; return; } if (swtMode || swtModeForWebStart) { if (verbose) NSLog(@"in SWT or SWT/WebStart mode"); // The SWT should call NSApplicationLoad, but they don't know a priori that they will be using the AWT, so they don't. NSApplicationLoad(); } // This will create a NSApplicationAWT for standalone AWT programs, unless there is // already a NSApplication instance. If there is already a NSApplication instance, // and -[NSApplication isRunning] returns YES, AWT is embedded inside another // AppKit Application. NSApplication *app = [NSApplicationAWT sharedApplication]; // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent. if (![app isRunning]) { if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]"); // This is where the AWT AppKit thread parks itself to process events. [NSApplicationAWT runAWTLoopWithApp: app]; } else { // We're either embedded, or showing a splash screen if (![NSApp isKindOfClass:[NSApplicationAWT class]]) { if (verbose) AWT_DEBUG_LOG(@"running embedded"); // Since we're embedded, no need to be swamping the runloop with the observers. CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop]; CFRunLoopRemoveObserver(runLoop, busyObserver, kCFRunLoopDefaultMode); CFRunLoopRemoveObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode); // We don't track if the runloop is busy, so set it free to let AWT finish when it needs setBusy(NO); busyObserver = NULL; notBusyObserver = NULL; } else { if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen"); } // Signal so that JNI_OnLoad can proceed. if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil]; // Proceed to exit this call as there is no reason to run the NSApplication event loop. } [pool drain]; } @end JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { BOOL verbose = ShouldPrintVerboseDebugging(); if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad"); // Headless: BOTH // Embedded: BOTH // Multiple Calls: NO // Caller: JavaVM classloader // Keep a static reference for other archives. OSXAPP_SetJavaVM(vm); JNIEnv *env = NULL; // Need JNIEnv for JNF_COCOA_ENTER(env); macro below jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4); if (status != JNI_OK || env == NULL) { AWT_DEBUG_LOG(@"Can't get JNIEnv"); return JNI_VERSION_1_4; } // The following is true when AWT is attempting to connect to the window server // when it isn't set up properly to do so. // BOOL AWTLoadFailure = YES; For now we are skipping this check so i'm commenting out this variable as unused JNF_COCOA_ENTER(env); // If -XstartOnFirstThread was used at invocation time, an environment variable will be set. // (See java_md.c for the matching setenv call.) When that happens, we assume the SWT will be in use. BOOL swt_compatible_mode = NO; char envVar[80]; snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid()); if (getenv(envVar) != NULL) { swt_compatible_mode = YES; unsetenv(envVar); } BOOL swt_in_webstart = isSWTInWebStart(env); BOOL headless = isHeadless(env); // Make sure we're on the right thread. If not, we still need the JNIEnv to throw an exception. if (pthread_main_np() != 0 && !swt_compatible_mode && !headless) { AWT_DEBUG_LOG(@"Apple AWT Java VM was loaded on first thread -- can't start AWT."); [JNFException raise:env as:kInternalError reason:"Can't start the AWT because Java was started on the first thread. Make sure StartOnFirstThread is " "not specified in your application's Info.plist or on the command line"]; return JNI_VERSION_1_4; } // We need to let Foundation know that this is a multithreaded application, if it isn't already. if (![NSThread isMultiThreaded]) { [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]; } // if (swt_compatible_mode || headless || [AWTStarter isConnectedToWindowServer] || [AWTStarter isRemoteSession]) { // No need in this check - we will try to launch AWTStarter anyways - to be able to run GUI application remotely // AWTLoadFailure = NO; [AWTStarter start:headless swtMode:swt_compatible_mode swtModeForWebStart:swt_in_webstart]; // } /* if (AWTLoadFailure) { // We will not reach this code anyways [JNFException raise:env as:kInternalError reason:"Can't connect to window server - not enough permissions."]; } */ JNF_COCOA_EXIT(env); if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad"); return JNI_VERSION_1_4; }