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