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