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 "NSApplicationAWT.h" 27 28 #import <objc/runtime.h> 29 #import <JavaRuntimeSupport/JavaRuntimeSupport.h> 30 31 #import "PropertiesUtilities.h" 32 #import "ThreadUtilities.h" 33 #import "QueuingApplicationDelegate.h" 34 35 36 static BOOL sUsingDefaultNIB = YES; 37 static NSString *SHARED_FRAMEWORK_BUNDLE = @"/System/Library/Frameworks/JavaVM.framework"; 38 static id <NSApplicationDelegate> applicationDelegate = nil; 39 static QueuingApplicationDelegate * qad = nil; 40 41 // Flag used to indicate to the Plugin2 event synthesis code to do a postEvent instead of sendEvent 42 BOOL postEventDuringEventSynthesis = NO; 43 44 @implementation NSApplicationAWT 45 46 - (id) init 47 { 48 // Headless: NO 49 // Embedded: NO 50 // Multiple Calls: NO 51 // Caller: +[NSApplication sharedApplication] 52 53 AWT_ASSERT_APPKIT_THREAD; 54 fApplicationName = nil; 55 dummyEventTimestamp = 0.0; 56 seenDummyEventLock = nil; 57 58 59 // NSApplication will call _RegisterApplication with the application's bundle, but there may not be one. 60 // So, we need to call it ourselves to ensure the app is set up properly. 61 [self registerWithProcessManager]; 62 63 return [super init]; 64 } 65 66 - (void)dealloc 67 { 68 [fApplicationName release]; 69 fApplicationName = nil; 70 71 [super dealloc]; 72 } 73 //- (void)finalize { [super finalize]; } 74 75 - (void)finishLaunching 76 { 77 AWT_ASSERT_APPKIT_THREAD; 78 79 JNIEnv *env = [ThreadUtilities getJNIEnv]; 80 81 // Get default nib file location 82 // NOTE: This should learn about the current java.version. Probably best thru 83 // the Makefile system's -DFRAMEWORK_VERSION define. Need to be able to pass this 84 // thru to PB from the Makefile system and for local builds. 85 NSString *defaultNibFile = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.nib" withEnv:env]; 86 if (!defaultNibFile) { 87 NSBundle *javaBundle = [NSBundle bundleWithPath:SHARED_FRAMEWORK_BUNDLE]; 88 defaultNibFile = [javaBundle pathForResource:@"DefaultApp" ofType:@"nib"]; 89 } else { 90 sUsingDefaultNIB = NO; 91 } 92 93 [NSBundle loadNibFile:defaultNibFile externalNameTable: [NSDictionary dictionaryWithObject:self forKey:@"NSOwner"] withZone:nil]; 94 95 // Set user defaults to not try to parse application arguments. 96 NSUserDefaults * defs = [NSUserDefaults standardUserDefaults]; 97 NSDictionary * noOpenDict = [NSDictionary dictionaryWithObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"]; 98 [defs registerDefaults:noOpenDict]; 99 100 // Fix up the dock icon now that we are registered with CAS and the Dock. 101 [self setDockIconWithEnv:env]; 102 103 // If we are using our nib (the default application NIB) we need to put the app name into 104 // the application menu, which has placeholders for the name. 105 if (sUsingDefaultNIB) { 106 NSUInteger i, itemCount; 107 NSMenu *theMainMenu = [NSApp mainMenu]; 108 109 // First submenu off the main menu is the application menu. 110 NSMenuItem *appMenuItem = [theMainMenu itemAtIndex:0]; 111 NSMenu *appMenu = [appMenuItem submenu]; 112 itemCount = [appMenu numberOfItems]; 113 114 for (i = 0; i < itemCount; i++) { 115 NSMenuItem *anItem = [appMenu itemAtIndex:i]; 116 NSString *oldTitle = [anItem title]; 117 [anItem setTitle:[NSString stringWithFormat:oldTitle, fApplicationName]]; 118 } 119 } 120 121 if (applicationDelegate) { 122 [self setDelegate:applicationDelegate]; 123 } else { 124 qad = [QueuingApplicationDelegate sharedDelegate]; 125 [self setDelegate:qad]; 126 } 127 128 [super finishLaunching]; 129 130 // inform any interested parties that the AWT has arrived and is pumping 131 [[NSNotificationCenter defaultCenter] postNotificationName:JNFRunLoopDidStartNotification object:self]; 132 } 133 134 - (void) registerWithProcessManager 135 { 136 // Headless: NO 137 // Embedded: NO 138 // Multiple Calls: NO 139 // Caller: -[NSApplicationAWT init] 140 141 AWT_ASSERT_APPKIT_THREAD; 142 JNIEnv *env = [ThreadUtilities getJNIEnv]; 143 144 char envVar[80]; 145 146 // The following environment variable is set from the -Xdock:name param. It should be UTF8. 147 snprintf(envVar, sizeof(envVar), "APP_NAME_%d", getpid()); 148 char *appName = getenv(envVar); 149 if (appName != NULL) { 150 fApplicationName = [NSString stringWithUTF8String:appName]; 151 unsetenv(envVar); 152 } 153 154 // If it wasn't specified as an argument, see if it was specified as a system property. 155 if (fApplicationName == nil) { 156 fApplicationName = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.name" withEnv:env]; 157 } 158 159 // If we STILL don't have it, the app name is retrieved from an environment variable (set in java.c) It should be UTF8. 160 if (fApplicationName == nil) { 161 char mainClassEnvVar[80]; 162 snprintf(mainClassEnvVar, sizeof(mainClassEnvVar), "JAVA_MAIN_CLASS_%d", getpid()); 163 char *mainClass = getenv(mainClassEnvVar); 164 if (mainClass != NULL) { 165 fApplicationName = [NSString stringWithUTF8String:mainClass]; 166 unsetenv(mainClassEnvVar); 167 168 NSRange lastPeriod = [fApplicationName rangeOfString:@"." options:NSBackwardsSearch]; 169 if (lastPeriod.location != NSNotFound) { 170 fApplicationName = [fApplicationName substringFromIndex:lastPeriod.location + 1]; 171 } 172 } 173 } 174 175 // The dock name is nil for double-clickable Java apps (bundled and Web Start apps) 176 // When that happens get the display name, and if that's not available fall back to 177 // CFBundleName. 178 NSBundle *mainBundle = [NSBundle mainBundle]; 179 if (fApplicationName == nil) { 180 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; 181 182 if (fApplicationName == nil) { 183 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]; 184 185 if (fApplicationName == nil) { 186 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey: (NSString *)kCFBundleExecutableKey]; 187 188 if (fApplicationName == nil) { 189 // Name of last resort is the last part of the applicatoin name without the .app (consistent with CopyProcessName) 190 fApplicationName = [[mainBundle bundlePath] lastPathComponent]; 191 192 if ([fApplicationName hasSuffix:@".app"]) { 193 fApplicationName = [fApplicationName stringByDeletingPathExtension]; 194 } 195 } 196 } 197 } 198 } 199 200 // We're all done trying to determine the app name. Hold on to it. 201 [fApplicationName retain]; 202 203 NSDictionary *registrationOptions = [NSMutableDictionary dictionaryWithObject:fApplicationName forKey:@"JRSAppNameKey"]; 204 205 NSString *launcherType = [PropertiesUtilities javaSystemPropertyForKey:@"sun.java.launcher" withEnv:env]; 206 if ([@"SUN_STANDARD" isEqualToString:launcherType]) { 207 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsCommandLineKey"]; 208 } 209 210 NSString *uiElementProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.UIElement" withEnv:env]; 211 if ([@"true" isCaseInsensitiveLike:uiElementProp]) { 212 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsUIElementKey"]; 213 } 214 215 NSString *backgroundOnlyProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.BackgroundOnly" withEnv:env]; 216 if ([@"true" isCaseInsensitiveLike:backgroundOnlyProp]) { 217 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsBackgroundOnlyKey"]; 218 } 219 220 // TODO replace with direct call 221 // [JRSAppKitAWT registerAWTAppWithOptions:registrationOptions]; 222 // and remove below transform/activate/run hack 223 224 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); 225 SEL registerSel = @selector(registerAWTAppWithOptions:); 226 if ([jrsAppKitAWTClass respondsToSelector:registerSel]) { 227 [jrsAppKitAWTClass performSelector:registerSel withObject:registrationOptions]; 228 return; 229 } 230 231 // HACK BEGIN 232 // The following is necessary to make the java process behave like a 233 // proper foreground application... 234 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 235 ProcessSerialNumber psn; 236 GetCurrentProcess(&psn); 237 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 238 239 [NSApp activateIgnoringOtherApps:YES]; 240 [NSApp run]; 241 }]; 242 // HACK END 243 } 244 245 - (void) setDockIconWithEnv:(JNIEnv *)env { 246 NSString *theIconPath = nil; 247 248 // The following environment variable is set in java.c. It is probably UTF8. 249 char envVar[80]; 250 snprintf(envVar, sizeof(envVar), "APP_ICON_%d", getpid()); 251 char *appIcon = getenv(envVar); 252 if (appIcon != NULL) { 253 theIconPath = [NSString stringWithUTF8String:appIcon]; 254 unsetenv(envVar); 255 } 256 257 if (theIconPath == nil) { 258 theIconPath = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.icon" withEnv:env]; 259 } 260 261 // If the icon file wasn't specified as an argument and we need to get an icon 262 // we'll use the generic java app icon. 263 NSString *defaultIconPath = [NSString stringWithFormat:@"%@%@", SHARED_FRAMEWORK_BUNDLE, @"/Resources/GenericApp.icns"]; 264 if (theIconPath == nil) { 265 NSString* bundleIcon = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; 266 if (bundleIcon == nil) { 267 theIconPath = defaultIconPath; 268 } 269 } 270 271 // Set up the dock icon if we have an icon name. 272 if (theIconPath != nil) { 273 NSImage *iconImage = [[NSImage alloc] initWithContentsOfFile:theIconPath]; 274 275 // If we failed for some reason fall back to the default icon. 276 if (iconImage == nil) { 277 iconImage = [[NSImage alloc] initWithContentsOfFile:defaultIconPath]; 278 } 279 280 [NSApp setApplicationIconImage:iconImage]; 281 [iconImage release]; 282 } 283 } 284 285 + (void) runAWTLoopWithApp:(NSApplication*)app { 286 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 287 288 // Make sure that when we run in AWTRunLoopMode we don't exit randomly 289 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[JNFRunLoop javaRunLoopMode]]; 290 291 do { 292 @try { 293 [app run]; 294 } @catch (NSException* e) { 295 NSLog(@"Apple AWT Startup Exception: %@", [e description]); 296 NSLog(@"Apple AWT Restarting Native Event Thread"); 297 298 [app stop:app]; 299 } 300 } while (YES); 301 302 [pool drain]; 303 } 304 305 - (BOOL)usingDefaultNib { 306 return sUsingDefaultNIB; 307 } 308 309 - (void)orderFrontStandardAboutPanelWithOptions:(NSDictionary *)optionsDictionary { 310 if (!optionsDictionary) { 311 optionsDictionary = [NSMutableDictionary dictionaryWithCapacity:2]; 312 [optionsDictionary setValue:[[[[[NSApp mainMenu] itemAtIndex:0] submenu] itemAtIndex:0] title] forKey:@"ApplicationName"]; 313 if (![NSImage imageNamed:@"NSApplicationIcon"]) { 314 [optionsDictionary setValue:[NSApp applicationIconImage] forKey:@"ApplicationIcon"]; 315 } 316 } 317 318 [super orderFrontStandardAboutPanelWithOptions:optionsDictionary]; 319 } 320 321 #define DRAGMASK (NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseDraggedMask | NSLeftMouseUpMask | NSRightMouseUpMask | NSFlagsChangedMask | NSKeyDownMask) 322 323 - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag { 324 if (mask == DRAGMASK && [((NSString *)kCFRunLoopDefaultMode) isEqual:mode]) { 325 postEventDuringEventSynthesis = YES; 326 } 327 328 NSEvent *event = [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue: deqFlag]; 329 postEventDuringEventSynthesis = NO; 330 331 return event; 332 } 333 334 // NSTimeInterval has microseconds precision 335 #define TS_EQUAL(ts1, ts2) (fabs((ts1) - (ts2)) < 1e-6) 336 337 - (void)sendEvent:(NSEvent *)event 338 { 339 if ([event type] == NSApplicationDefined && TS_EQUAL([event timestamp], dummyEventTimestamp)) { 340 [seenDummyEventLock lockWhenCondition:NO]; 341 [seenDummyEventLock unlockWithCondition:YES]; 342 } else { 343 [super sendEvent:event]; 344 } 345 } 346 347 - (void)postDummyEvent { 348 seenDummyEventLock = [[NSConditionLock alloc] initWithCondition:NO]; 349 dummyEventTimestamp = [NSProcessInfo processInfo].systemUptime; 350 351 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 352 NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined 353 location: NSMakePoint(0,0) 354 modifierFlags: 0 355 timestamp: dummyEventTimestamp 356 windowNumber: 0 357 context: nil 358 subtype: 0 359 data1: 0 360 data2: 0]; 361 [NSApp postEvent: event atStart: NO]; 362 [pool drain]; 363 } 364 365 - (void)waitForDummyEvent { 366 [seenDummyEventLock lockWhenCondition:YES]; 367 [seenDummyEventLock unlock]; 368 [seenDummyEventLock release]; 369 370 seenDummyEventLock = nil; 371 } 372 373 @end 374 375 376 void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> delegate) 377 { 378 AWT_ASSERT_APPKIT_THREAD; 379 applicationDelegate = delegate; 380 381 if (NSApp != nil) { 382 [NSApp setDelegate: applicationDelegate]; 383 384 if (applicationDelegate && qad) { 385 [qad processQueuedEventsWithTargetDelegate: applicationDelegate]; 386 qad = nil; 387 } 388 } 389 } 390