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