1 /*
2 * Copyright (c) 2011, 2013, 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 // Indicates if AWT is running embedded (in SWT, FX, elsewhere)
46 static BOOL isEmbedded = NO;
47
48 // Indicates that the app has been started with -XstartOnFirstThread
49 // (directly or via WebStart settings), and AWT should not run its
50 // own event loop in this mode. Even if a loop isn't running yet,
51 // we expect an embedder (e.g. SWT) to start it some time later.
52 static BOOL forceEmbeddedMode = NO;
53
54 static bool ShouldPrintVerboseDebugging() {
55 static int debug = -1;
56 if (debug == -1) {
57 debug = (int)(getenv("JAVA_AWT_VERBOSE") != NULL) || (DEBUG != 0);
58 }
59 return (bool)debug;
60 }
61
62 // This is the data necessary to have JNI_OnLoad wait for AppKit to start.
63 static BOOL sAppKitStarted = NO;
64 static pthread_mutex_t sAppKitStarted_mutex = PTHREAD_MUTEX_INITIALIZER;
65 static pthread_cond_t sAppKitStarted_cv = PTHREAD_COND_INITIALIZER;
66
67 void setBusy(BOOL isBusy);
68 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg);
69 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg);
70 static void AWT_NSUncaughtExceptionHandler(NSException *exception);
71
72 static CFRunLoopObserverRef busyObserver = NULL;
73 static CFRunLoopObserverRef notBusyObserver = NULL;
74
75 static void setUpAWTAppKit()
76 {
77 BOOL verbose = ShouldPrintVerboseDebugging();
78 if (verbose) AWT_DEBUG_LOG(@"setting up busy observers");
79
80 // Add CFRunLoopObservers to call into AWT so that AWT knows that the
81 // AWT thread (which is the AppKit main thread) is alive. This way AWT
82 // will not automatically shutdown.
83 busyObserver = CFRunLoopObserverCreate(
84 NULL, // CFAllocator
85 kCFRunLoopAfterWaiting, // CFOptionFlags
86 true, // repeats
87 NSIntegerMax, // order
88 &BusyObserver, // CFRunLoopObserverCallBack
89 NULL); // CFRunLoopObserverContext
90
91 notBusyObserver = CFRunLoopObserverCreate(
92 NULL, // CFAllocator
93 kCFRunLoopBeforeWaiting, // CFOptionFlags
94 true, // repeats
95 NSIntegerMin, // order
96 &NotBusyObserver, // CFRunLoopObserverCallBack
97 NULL); // CFRunLoopObserverContext
98
99 CFRunLoopRef runLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
100 CFRunLoopAddObserver(runLoop, busyObserver, kCFRunLoopDefaultMode);
101 CFRunLoopAddObserver(runLoop, notBusyObserver, kCFRunLoopDefaultMode);
102
103 CFRelease(busyObserver);
104 CFRelease(notBusyObserver);
105
106 setBusy(YES);
107 }
108
109 static void setUpAppKitThreadName()
110 {
111 BOOL verbose = ShouldPrintVerboseDebugging();
112 JNIEnv *env = [ThreadUtilities getJNIEnv];
113
114 // Set the java name of the AppKit main thread appropriately.
115 jclass threadClass = NULL;
116 jstring name = NULL;
117 jobject curThread = NULL;
118
119 threadClass = (*env)->FindClass(env, "java/lang/Thread");
120 if (threadClass == NULL || (*env)->ExceptionCheck(env)) goto cleanup;
121 jmethodID currentThreadID = (*env)->GetStaticMethodID(env, threadClass, "currentThread", "()Ljava/lang/Thread;");
122 if (currentThreadID == NULL || (*env)->ExceptionCheck(env)) goto cleanup;
123 jmethodID setName = (*env)->GetMethodID(env, threadClass, "setName", "(Ljava/lang/String;)V");
124 if (setName == NULL || (*env)->ExceptionCheck(env)) goto cleanup;
125
126 curThread = (*env)->CallStaticObjectMethod(env, threadClass, currentThreadID); // AWT_THREADING Safe (known object)
127 if (curThread == NULL || (*env)->ExceptionCheck(env)) goto cleanup;
128 name = (*env)->NewStringUTF(env, "AWT-AppKit");
129 if (name == NULL || (*env)->ExceptionCheck(env)) goto cleanup;
130 (*env)->CallVoidMethod(env, curThread, setName, name); // AWT_THREADING Safe (known object)
131 if ((*env)->ExceptionCheck(env)) goto cleanup;
132
133 cleanup:
134 if (threadClass != NULL) {
135 (*env)->DeleteLocalRef(env, threadClass);
136 }
137 if (name != NULL) {
138 (*env)->DeleteLocalRef(env, name);
139 }
140 if (curThread != NULL) {
141 (*env)->DeleteLocalRef(env, curThread);
142 }
143 if ((*env)->ExceptionCheck(env)) {
144 (*env)->ExceptionDescribe(env);
145 (*env)->ExceptionClear(env);
146 }
147
148 if (verbose) AWT_DEBUG_LOG(@"finished setting thread name");
149 }
150
151
152 // Returns true if java believes it is running headless
153 BOOL isHeadless(JNIEnv *env) {
154 // Just access the property directly, instead of using GraphicsEnvironment.isHeadless.
155 // This is because this may be called while AWT is being loaded, and calling AWT
156 // while it is being loaded will deadlock.
157 static JNF_CLASS_CACHE(jc_Toolkit, "java/awt/GraphicsEnvironment");
158 static JNF_STATIC_MEMBER_CACHE(jm_isHeadless, jc_Toolkit, "isHeadless", "()Z");
159 return JNFCallStaticBooleanMethod(env, jm_isHeadless);
160 }
161
162 BOOL isSWTInWebStart(JNIEnv* env) {
163 NSString *swtWebStart = [PropertiesUtilities javaSystemPropertyForKey:@"com.apple.javaws.usingSWT" withEnv:env];
164 return [@"true" isCaseInsensitiveLike:swtWebStart];
165 }
166
167 void setBusy(BOOL busy) {
168 AWT_ASSERT_APPKIT_THREAD;
169
170 JNIEnv *env = [ThreadUtilities getJNIEnv];
171 static JNF_CLASS_CACHE(jc_AWTAutoShutdown, "sun/awt/AWTAutoShutdown");
172
173 if (busy) {
174 static JNF_STATIC_MEMBER_CACHE(jm_notifyBusyMethod, jc_AWTAutoShutdown, "notifyToolkitThreadBusy", "()V");
175 JNFCallStaticVoidMethod(env, jm_notifyBusyMethod);
176 } else {
177 static JNF_STATIC_MEMBER_CACHE(jm_notifyFreeMethod, jc_AWTAutoShutdown, "notifyToolkitThreadFree", "()V");
178 JNFCallStaticVoidMethod(env, jm_notifyFreeMethod);
179 }
180 }
181
182 static void BusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) {
183 AWT_ASSERT_APPKIT_THREAD;
184
185 // This is only called with the selector kCFRunLoopAfterWaiting.
186 #ifndef PRODUCT_BUILD
187 assert(what == kCFRunLoopAfterWaiting);
188 #endif /* PRODUCT_BUILD */
189
190 setBusy(YES);
191 }
192
193 static void NotBusyObserver(CFRunLoopObserverRef ref, CFRunLoopActivity what, void* arg) {
194 AWT_ASSERT_APPKIT_THREAD;
195
196 // This is only called with the selector kCFRunLoopBeforeWaiting.
197 #ifndef PRODUCT_BUILD
198 assert(what == kCFRunLoopBeforeWaiting);
199 #endif /* PRODUCT_BUILD */
200
201 setBusy(NO);
202 }
203
204 static void AWT_NSUncaughtExceptionHandler(NSException *exception) {
205 NSLog(@"Apple AWT Internal Exception: %@", [exception description]);
206 }
207
208 // This is an empty Obj-C object just so that -peformSelectorOnMainThread can be used.
209 @interface AWTStarter : NSObject { }
210 + (void)start:(BOOL)headless;
211 - (void)starter:(NSArray*)args;
212 + (void)appKitIsRunning:(id)arg;
213 @end
214
215 @implementation AWTStarter
216
217 + (BOOL) isConnectedToWindowServer {
218 SecuritySessionId session_id;
219 SessionAttributeBits session_info;
220 OSStatus status = SessionGetInfo(callerSecuritySession, &session_id, &session_info);
221 if (status != noErr) return NO;
222 if (!(session_info & sessionHasGraphicAccess)) return NO;
223 return YES;
224 }
225
226 + (BOOL) markAppAsDaemon {
227 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT");
228 SEL markAppSel = @selector(markAppIsDaemon);
229 if (![jrsAppKitAWTClass respondsToSelector:markAppSel]) return NO;
230 return [jrsAppKitAWTClass performSelector:markAppSel] ? YES : NO;
231 }
232
233 + (void)appKitIsRunning:(id)arg {
234 // Headless: NO
235 // Embedded: BOTH
236 // Multiple Calls: NO
237 // Callers: AppKit's NSApplicationDidFinishLaunchingNotification or +[AWTStarter startAWT:]
238 AWT_ASSERT_APPKIT_THREAD;
239
240 BOOL verbose = ShouldPrintVerboseDebugging();
241 if (verbose) AWT_DEBUG_LOG(@"about to message AppKit started");
242
243 // Signal that AppKit has started (or is already running).
244 pthread_mutex_lock(&sAppKitStarted_mutex);
245 sAppKitStarted = YES;
246 pthread_cond_signal(&sAppKitStarted_cv);
247 pthread_mutex_unlock(&sAppKitStarted_mutex);
248
249 if (verbose) AWT_DEBUG_LOG(@"finished messaging AppKit started");
250 }
251
252 + (void)start:(BOOL)headless
253 {
254 BOOL verbose = ShouldPrintVerboseDebugging();
255
256 // Headless: BOTH
257 // Embedded: BOTH
258 // Multiple Calls: NO
259 // Caller: JNI_OnLoad
260
261 // onMainThread is NOT the same at SWT mode!
262 // If the JVM was started on the first thread for SWT, but the SWT loads the AWT on a secondary thread,
263 // onMainThread here will be false but SWT mode will be true. If we are currently on the main thread, we don't
264 // need to throw AWT startup over to another thread.
265 BOOL onMainThread = (pthread_main_np() != 0);
266
267 if (verbose) {
268 NSString *msg = [NSString stringWithFormat:@"+[AWTStarter start headless:%d] { onMainThread:%d }", headless, onMainThread];
269 AWT_DEBUG_LOG(msg);
270 }
271
272 if (!headless)
273 {
274 // Listen for the NSApp to start. This indicates that JNI_OnLoad can proceed.
275 // It must wait because there is a chance that another java thread will grab
276 // the AppKit lock before the +[NSApplication sharedApplication] returns.
277 // See <rdar://problem/3492666> for an example.
278 [[NSNotificationCenter defaultCenter] addObserver:[AWTStarter class]
279 selector:@selector(appKitIsRunning:)
280 name:NSApplicationDidFinishLaunchingNotification
281 object:nil];
282
283 if (verbose) NSLog(@"+[AWTStarter start:::]: registered NSApplicationDidFinishLaunchingNotification");
284 }
285
286 id st = [[AWTStarter alloc] init];
287
288 NSArray * args = [NSArray arrayWithObjects:
289 [NSNumber numberWithBool: onMainThread],
290 [NSNumber numberWithBool: headless],
291 [NSNumber numberWithBool: verbose],
292 nil];
293
294 if (onMainThread) {
295 [st starter:args];
296 } else {
297 [st performSelectorOnMainThread: @selector(starter:) withObject:args waitUntilDone:NO];
298 }
299
300 if (!headless && !onMainThread) {
301 if (verbose) AWT_DEBUG_LOG(@"about to wait on AppKit startup mutex");
302
303 // Wait here for AppKit to have started (or for AWT to have been loaded into
304 // an already running NSApplication).
305 pthread_mutex_lock(&sAppKitStarted_mutex);
306 while (sAppKitStarted == NO) {
307 pthread_cond_wait(&sAppKitStarted_cv, &sAppKitStarted_mutex);
308 }
309 pthread_mutex_unlock(&sAppKitStarted_mutex);
310
311 // AWT gets here AFTER +[AWTStarter appKitIsRunning:] is called.
312 if (verbose) AWT_DEBUG_LOG(@"got out of the AppKit startup mutex");
313 }
314
315 if (!headless) {
316 // Don't set the delegate until the NSApplication has been created and
317 // its finishLaunching has initialized it.
318 // ApplicationDelegate is the support code for com.apple.eawt.
319 [ThreadUtilities performOnMainThreadWaiting:YES block:^(){
320 id<NSApplicationDelegate> delegate = [ApplicationDelegate sharedDelegate];
321 if (delegate != nil) {
322 OSXAPP_SetApplicationDelegate(delegate);
323 }
324 }];
325 }
326 }
327
328 - (void)starter:(NSArray*)args {
329 NSAutoreleasePool *pool = [NSAutoreleasePool new];
330
331 BOOL onMainThread = [[args objectAtIndex:0] boolValue];
332 BOOL headless = [[args objectAtIndex:1] boolValue];
333 BOOL verbose = [[args objectAtIndex:2] boolValue];
334
335 BOOL wasOnMainThread = onMainThread;
336
337 // Add the exception handler of last resort
338 NSSetUncaughtExceptionHandler(AWT_NSUncaughtExceptionHandler);
339
340 // Headless mode trumps either ordinary AWT or SWT-in-AWT mode. Declare us a daemon and return.
341 if (headless) {
342 // Note that we don't install run loop observers in headless mode
343 // because we don't need them (see 7174704)
344 if (!forceEmbeddedMode) {
345 setUpAppKitThreadName();
346 }
347 [AWTStarter markAppAsDaemon];
348 return;
349 }
350
351 if (forceEmbeddedMode) {
352 if (verbose) NSLog(@"in SWT or SWT/WebStart mode");
353
354 // Init a default NSApplication instance instead of the NSApplicationAWT.
355 // Note that [NSApp isRunning] will return YES after that, though
356 // this behavior isn't specified anywhere. We rely on that.
357 NSApplicationLoad();
358 }
359
360 // This will create a NSApplicationAWT for standalone AWT programs, unless there is
361 // already a NSApplication instance. If there is already a NSApplication instance,
362 // and -[NSApplication isRunning] returns YES, AWT is embedded inside another
363 // AppKit Application.
364 NSApplication *app = [NSApplicationAWT sharedApplication];
365 isEmbedded = ![NSApp isKindOfClass:[NSApplicationAWT class]];
366 [ThreadUtilities setAWTEmbedded:isEmbedded];
367
368 if (!isEmbedded) {
369 // Install run loop observers and set the AppKit Java thread name
370 setUpAWTAppKit();
371 setUpAppKitThreadName();
372 }
373
374 // AWT gets to this point BEFORE NSApplicationDidFinishLaunchingNotification is sent.
375 if (![app isRunning]) {
376 if (verbose) AWT_DEBUG_LOG(@"+[AWTStarter startAWT]: ![app isRunning]");
377
378 // This is where the AWT AppKit thread parks itself to process events.
379 [NSApplicationAWT runAWTLoopWithApp: app];
380 } else {
381 // We're either embedded, or showing a splash screen
382 if (isEmbedded) {
383 if (verbose) AWT_DEBUG_LOG(@"running embedded");
384
385 // We don't track if the runloop is busy, so set it free to let AWT finish when it needs
386 setBusy(NO);
387 } else {
388 if (verbose) AWT_DEBUG_LOG(@"running after showing a splash screen");
389 }
390
391 // Signal so that JNI_OnLoad can proceed.
392 if (!wasOnMainThread) [AWTStarter appKitIsRunning:nil];
393
394 // Proceed to exit this call as there is no reason to run the NSApplication event loop.
395 }
396
397 [pool drain];
398 }
399
400 @end
401
402
403 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
404 BOOL verbose = ShouldPrintVerboseDebugging();
405 if (verbose) AWT_DEBUG_LOG(@"entered JNI_OnLoad");
406
407 // Headless: BOTH
408 // Embedded: BOTH
409 // Multiple Calls: NO
410 // Caller: JavaVM classloader
411
412 // Keep a static reference for other archives.
413 OSXAPP_SetJavaVM(vm);
414
415 JNIEnv *env = NULL;
416
417 // Need JNIEnv for JNF_COCOA_ENTER(env); macro below
418 jint status = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4);
419 if (status != JNI_OK || env == NULL) {
420 AWT_DEBUG_LOG(@"Can't get JNIEnv");
421 return JNI_VERSION_1_4;
422 }
423
424 JNF_COCOA_ENTER(env);
425
426 // Launcher sets this env variable if -XstartOnFirstThread is specified
427 char envVar[80];
428 snprintf(envVar, sizeof(envVar), "JAVA_STARTED_ON_FIRST_THREAD_%d", getpid());
429 if (getenv(envVar) != NULL) {
430 forceEmbeddedMode = YES;
431 unsetenv(envVar);
432 }
433
434 if (isSWTInWebStart(env)) {
435 forceEmbeddedMode = YES;
436 }
437 JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
438 jclass jc_ThreadGroupUtils = (*env)->FindClass(env, "sun/misc/ThreadGroupUtils");
439 jmethodID sjm_getRootThreadGroup = (*env)->GetStaticMethodID(env, jc_ThreadGroupUtils, "getRootThreadGroup", "()Ljava/lang/ThreadGroup;");
440 jobject rootThreadGroup = (*env)->CallStaticObjectMethod(env, jc_ThreadGroupUtils, sjm_getRootThreadGroup);
441 [ThreadUtilities setAppkitThreadGroup:(*env)->NewGlobalRef(env, rootThreadGroup)];
442 // The current thread was attached in getJNIEnvUnchached.
443 // Detach it back. It will be reattached later if needed with a proper TG
444 [ThreadUtilities detachCurrentThread];
445
446 BOOL headless = isHeadless(env);
447
448 // We need to let Foundation know that this is a multithreaded application, if it isn't already.
449 if (![NSThread isMultiThreaded]) {
450 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil];
451 }
452
453 [AWTStarter start:headless];
454
455 JNF_COCOA_EXIT(env);
456
457 if (verbose) AWT_DEBUG_LOG(@"exiting JNI_OnLoad");
458
459 return JNI_VERSION_1_4;
460 }
--- EOF ---