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 "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 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 // Use the path specified to get the icon image 262 NSImage* iconImage = nil; 263 if (theIconPath != nil) { 264 iconImage = [[NSImage alloc] initWithContentsOfFile:theIconPath]; 265 } 266 267 // If no icon file was specified or we failed to get the icon image 268 // and there is no bundle's icon, then use the default icon 269 if (iconImage == nil) { 270 NSString* bundleIcon = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"]; 271 if (bundleIcon == nil) { 272 NSData* iconData; 273 iconData = [[NSData alloc] initWithBytesNoCopy: sAWTIconData length: sizeof(sAWTIconData) freeWhenDone: NO]; 274 iconImage = [[NSImage alloc] initWithData: iconData]; 275 [iconData release]; 276 } 277 } 278 279 // Set up the dock icon if we have an icon image. 280 if (iconImage != nil) { 281 [NSApp setApplicationIconImage:iconImage]; 282 [iconImage release]; 283 } 284 } 285 286 + (void) runAWTLoopWithApp:(NSApplication*)app { 287 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 288 289 // Make sure that when we run in AWTRunLoopMode we don't exit randomly 290 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[JNFRunLoop javaRunLoopMode]]; 291 292 do { 293 @try { 294 [app run]; 295 } @catch (NSException* e) { 296 NSLog(@"Apple AWT Startup Exception: %@", [e description]); 297 NSLog(@"Apple AWT Restarting Native Event Thread"); 298 299 [app stop:app]; 300 } 301 } while (YES); 302 303 [pool drain]; 304 } 305 306 - (BOOL)usingDefaultNib { 307 return sUsingDefaultNIB; 308 } 309 310 - (void)orderFrontStandardAboutPanelWithOptions:(NSDictionary *)optionsDictionary { 311 if (!optionsDictionary) { 312 optionsDictionary = [NSMutableDictionary dictionaryWithCapacity:2]; 313 [optionsDictionary setValue:[[[[[NSApp mainMenu] itemAtIndex:0] submenu] itemAtIndex:0] title] forKey:@"ApplicationName"]; 314 if (![NSImage imageNamed:@"NSApplicationIcon"]) { 315 [optionsDictionary setValue:[NSApp applicationIconImage] forKey:@"ApplicationIcon"]; 316 } 317 } 318 319 [super orderFrontStandardAboutPanelWithOptions:optionsDictionary]; 320 } 321 322 #define DRAGMASK (NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseDraggedMask | NSLeftMouseUpMask | NSRightMouseUpMask | NSFlagsChangedMask | NSKeyDownMask) 323 324 - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag { 325 if (mask == DRAGMASK && [((NSString *)kCFRunLoopDefaultMode) isEqual:mode]) { 326 postEventDuringEventSynthesis = YES; 327 } 328 329 NSEvent *event = [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue: deqFlag]; 330 postEventDuringEventSynthesis = NO; 331 332 return event; 333 } 334 335 // NSTimeInterval has microseconds precision 336 #define TS_EQUAL(ts1, ts2) (fabs((ts1) - (ts2)) < 1e-6) 337 338 - (void)sendEvent:(NSEvent *)event 339 { 340 if ([event type] == NSApplicationDefined && TS_EQUAL([event timestamp], dummyEventTimestamp)) { 341 [seenDummyEventLock lockWhenCondition:NO]; 342 [seenDummyEventLock unlockWithCondition:YES]; 343 } else if ([event type] == NSKeyUp && ([event modifierFlags] & NSCommandKeyMask)) { 344 // Cocoa won't send us key up event when releasing a key while Cmd is down, 345 // so we have to do it ourselves. 346 [[self keyWindow] sendEvent:event]; 347 } else { 348 [super sendEvent:event]; 349 } 350 } 351 352 - (void)postDummyEvent { 353 seenDummyEventLock = [[NSConditionLock alloc] initWithCondition:NO]; 354 dummyEventTimestamp = [NSProcessInfo processInfo].systemUptime; 355 356 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 357 NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined 358 location: NSMakePoint(0,0) 359 modifierFlags: 0 360 timestamp: dummyEventTimestamp 361 windowNumber: 0 362 context: nil 363 subtype: 0 364 data1: 0 365 data2: 0]; 366 [NSApp postEvent: event atStart: NO]; 367 [pool drain]; 368 } 369 370 - (void)waitForDummyEvent { 371 [seenDummyEventLock lockWhenCondition:YES]; 372 [seenDummyEventLock unlock]; 373 [seenDummyEventLock release]; 374 375 seenDummyEventLock = nil; 376 } 377 378 @end 379 380 381 void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> delegate) 382 { 383 AWT_ASSERT_APPKIT_THREAD; 384 applicationDelegate = delegate; 385 386 if (NSApp != nil) { 387 [NSApp setDelegate: applicationDelegate]; 388 389 if (applicationDelegate && qad) { 390 [qad processQueuedEventsWithTargetDelegate: applicationDelegate]; 391 qad = nil; 392 } 393 } 394 } 395