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