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