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/awt/util/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 } |
|