1 /* 2 * Copyright (c) 2011, 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 fUseDefaultIcon = NO; 57 58 // NSApplication will call _RegisterApplication with the application's bundle, but there may not be one. 59 // So, we need to call it ourselves to ensure the app is set up properly. 60 [self registerWithProcessManager]; 61 62 return [super init]; 63 } 64 65 - (void)dealloc 66 { 67 [fApplicationName release]; 68 fApplicationName = nil; 69 70 [super dealloc]; 71 } 72 //- (void)finalize { [super finalize]; } 73 74 - (void)finishLaunching 75 { 76 AWT_ASSERT_APPKIT_THREAD; 77 78 JNIEnv *env = [ThreadUtilities getJNIEnv]; 79 80 // Get default nib file location 81 // NOTE: This should learn about the current java.version. Probably best thru 82 // the Makefile system's -DFRAMEWORK_VERSION define. Need to be able to pass this 83 // thru to PB from the Makefile system and for local builds. 84 NSString *defaultNibFile = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.nib" withEnv:env]; 85 if (!defaultNibFile) { 86 NSBundle *javaBundle = [NSBundle bundleWithPath:SHARED_FRAMEWORK_BUNDLE]; 87 defaultNibFile = [javaBundle pathForResource:@"DefaultApp" ofType:@"nib"]; 88 } else { 89 sUsingDefaultNIB = NO; 90 } 91 92 [NSBundle loadNibFile:defaultNibFile externalNameTable: [NSDictionary dictionaryWithObject:self forKey:@"NSOwner"] withZone:nil]; 93 94 // Set user defaults to not try to parse application arguments. 95 NSUserDefaults * defs = [NSUserDefaults standardUserDefaults]; 96 NSDictionary * noOpenDict = [NSDictionary dictionaryWithObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"]; 97 [defs registerDefaults:noOpenDict]; 98 99 // Fix up the dock icon now that we are registered with CAS and the Dock. 100 [self setDockIconWithEnv:env]; 101 102 // If we are using our nib (the default application NIB) we need to put the app name into 103 // the application menu, which has placeholders for the name. 104 if (sUsingDefaultNIB) { 105 NSUInteger i, itemCount; 106 NSMenu *theMainMenu = [NSApp mainMenu]; 107 108 // First submenu off the main menu is the application menu. 109 NSMenuItem *appMenuItem = [theMainMenu itemAtIndex:0]; 110 NSMenu *appMenu = [appMenuItem submenu]; 111 itemCount = [appMenu numberOfItems]; 112 113 for (i = 0; i < itemCount; i++) { 114 NSMenuItem *anItem = [appMenu itemAtIndex:i]; 115 NSString *oldTitle = [anItem title]; 116 [anItem setTitle:[NSString stringWithFormat:oldTitle, fApplicationName]]; 117 } 118 } 119 120 if (applicationDelegate) { 121 [self setDelegate:applicationDelegate]; 122 } else { 123 qad = [QueuingApplicationDelegate sharedDelegate]; 124 [self setDelegate:qad]; 125 } 126 127 [super finishLaunching]; 128 129 // inform any interested parties that the AWT has arrived and is pumping 130 [[NSNotificationCenter defaultCenter] postNotificationName:JNFRunLoopDidStartNotification object:self]; 131 } 132 133 - (void) registerWithProcessManager 134 { 135 // Headless: NO 136 // Embedded: NO 137 // Multiple Calls: NO 138 // Caller: -[NSApplicationAWT init] 139 140 AWT_ASSERT_APPKIT_THREAD; 141 JNIEnv *env = [ThreadUtilities getJNIEnv]; 142 143 char envVar[80]; 144 145 // The following environment variable is set from the -Xdock:name param. It should be UTF8. 146 snprintf(envVar, sizeof(envVar), "APP_NAME_%d", getpid()); 147 char *appName = getenv(envVar); 148 if (appName != NULL) { 149 fApplicationName = [NSString stringWithUTF8String:appName]; 150 unsetenv(envVar); 151 152 // If this environment variable was set we were launched from the command line, so we 153 // should use a generic app icon if one wasn't set. 154 fUseDefaultIcon = YES; 155 } 156 157 // If it wasn't specified as an argument, see if it was specified as a system property. 158 if (fApplicationName == nil) { 159 fApplicationName = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.name" withEnv:env]; 160 } 161 162 // If we STILL don't have it, the app name is retrieved from an environment variable (set in java.c) It should be UTF8. 163 if (fApplicationName == nil) { 164 char mainClassEnvVar[80]; 165 snprintf(mainClassEnvVar, sizeof(mainClassEnvVar), "JAVA_MAIN_CLASS_%d", getpid()); 166 char *mainClass = getenv(mainClassEnvVar); 167 if (mainClass != NULL) { 168 fApplicationName = [NSString stringWithUTF8String:mainClass]; 169 unsetenv(mainClassEnvVar); 170 171 NSRange lastPeriod = [fApplicationName rangeOfString:@"." options:NSBackwardsSearch]; 172 if (lastPeriod.location != NSNotFound) { 173 fApplicationName = [fApplicationName substringFromIndex:lastPeriod.location + 1]; 174 } 175 // If this environment variable was set we were launched from the command line, so we 176 // should use a generic app icon if one wasn't set. 177 fUseDefaultIcon = YES; 178 } 179 } 180 181 // The dock name is nil for double-clickable Java apps (bundled and Web Start apps) 182 // When that happens get the display name, and if that's not available fall back to 183 // CFBundleName. 184 NSBundle *mainBundle = [NSBundle mainBundle]; 185 if (fApplicationName == nil) { 186 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"]; 187 188 if (fApplicationName == nil) { 189 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]; 190 191 if (fApplicationName == nil) { 192 fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey: (NSString *)kCFBundleExecutableKey]; 193 194 if (fApplicationName == nil) { 195 // Name of last resort is the last part of the applicatoin name without the .app (consistent with CopyProcessName) 196 fApplicationName = [[mainBundle bundlePath] lastPathComponent]; 197 198 if ([fApplicationName hasSuffix:@".app"]) { 199 fApplicationName = [fApplicationName stringByDeletingPathExtension]; 200 } 201 } 202 } 203 } 204 } 205 206 // We're all done trying to determine the app name. Hold on to it. 207 [fApplicationName retain]; 208 209 NSDictionary *registrationOptions = [NSMutableDictionary dictionaryWithObject:fApplicationName forKey:@"JRSAppNameKey"]; 210 211 NSString *launcherType = [PropertiesUtilities javaSystemPropertyForKey:@"sun.java.launcher" withEnv:env]; 212 if ([@"SUN_STANDARD" isEqualToString:launcherType]) { 213 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsCommandLineKey"]; 214 } 215 216 NSString *uiElementProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.UIElement" withEnv:env]; 217 if ([@"true" isCaseInsensitiveLike:uiElementProp]) { 218 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsUIElementKey"]; 219 } 220 221 NSString *backgroundOnlyProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.BackgroundOnly" withEnv:env]; 222 if ([@"true" isCaseInsensitiveLike:backgroundOnlyProp]) { 223 [registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsBackgroundOnlyKey"]; 224 } 225 226 // TODO replace with direct call 227 // [JRSAppKitAWT registerAWTAppWithOptions:registrationOptions]; 228 // and remove below transform/activate/run hack 229 230 id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT"); 231 SEL registerSel = @selector(registerAWTAppWithOptions:); 232 if ([jrsAppKitAWTClass respondsToSelector:registerSel]) { 233 [jrsAppKitAWTClass performSelector:registerSel withObject:registrationOptions]; 234 return; 235 } 236 237 // HACK BEGIN 238 // The following is necessary to make the java process behave like a 239 // proper foreground application... 240 [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^(){ 241 ProcessSerialNumber psn; 242 GetCurrentProcess(&psn); 243 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 244 245 [NSApp activateIgnoringOtherApps:YES]; 246 [NSApp run]; 247 }]; 248 // HACK END 249 } 250 251 - (void) setDockIconWithEnv:(JNIEnv *)env { 252 NSString *theIconPath = nil; 253 254 // The following environment variable is set in java.c. It is probably UTF8. 255 char envVar[80]; 256 snprintf(envVar, sizeof(envVar), "APP_ICON_%d", getpid()); 257 char *appIcon = getenv(envVar); 258 if (appIcon != NULL) { 259 theIconPath = [NSString stringWithUTF8String:appIcon]; 260 unsetenv(envVar); 261 } 262 263 if (theIconPath == nil) { 264 theIconPath = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.icon" withEnv:env]; 265 } 266 267 NSImage *iconImage = nil; 268 269 // Got the icon path, try to get the icon image from it 270 if (theIconPath != nil) { 271 iconImage = [[NSImage alloc] initWithContentsOfFile:theIconPath]; 272 } 273 274 // No icon path was specified or we failed to get the icon image on previous step, 275 // in either case, use the default Java icon 276 if (iconImage == nil) { 277 NSData* iconData; 278 279 iconData = [[NSData alloc] initWithBytesNoCopy: sAWTIconData length: sizeof(sAWTIconData) freeWhenDone: NO]; 280 iconImage = [[NSImage alloc] initWithData: iconData]; 281 [iconData release]; 282 } 283 284 [NSApp setApplicationIconImage:iconImage]; 285 [iconImage release]; 286 } 287 288 + (void) runAWTLoopWithApp:(NSApplication*)app { 289 NSAutoreleasePool *pool = [NSAutoreleasePool new]; 290 291 // Make sure that when we run in AWTRunLoopMode we don't exit randomly 292 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[JNFRunLoop javaRunLoopMode]]; 293 294 do { 295 @try { 296 [app run]; 297 } @catch (NSException* e) { 298 NSLog(@"Apple AWT Startup Exception: %@", [e description]); 299 NSLog(@"Apple AWT Restarting Native Event Thread"); 300 301 [app stop:app]; 302 } 303 } while (YES); 304 305 [pool drain]; 306 } 307 308 - (BOOL)usingDefaultNib { 309 return sUsingDefaultNIB; 310 } 311 312 - (void)orderFrontStandardAboutPanelWithOptions:(NSDictionary *)optionsDictionary { 313 if (!optionsDictionary) { 314 optionsDictionary = [NSMutableDictionary dictionaryWithCapacity:2]; 315 [optionsDictionary setValue:[[[[[NSApp mainMenu] itemAtIndex:0] submenu] itemAtIndex:0] title] forKey:@"ApplicationName"]; 316 if (![NSImage imageNamed:@"NSApplicationIcon"]) { 317 [optionsDictionary setValue:[NSApp applicationIconImage] forKey:@"ApplicationIcon"]; 318 } 319 } 320 321 [super orderFrontStandardAboutPanelWithOptions:optionsDictionary]; 322 } 323 324 #define DRAGMASK (NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseDraggedMask | NSLeftMouseUpMask | NSRightMouseUpMask | NSFlagsChangedMask | NSKeyDownMask) 325 326 - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag { 327 if (mask == DRAGMASK && [((NSString *)kCFRunLoopDefaultMode) isEqual:mode]) { 328 postEventDuringEventSynthesis = YES; 329 } 330 331 NSEvent *event = [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue: deqFlag]; 332 postEventDuringEventSynthesis = NO; 333 334 return event; 335 } 336 337 @end 338 339 340 void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> delegate) 341 { 342 AWT_ASSERT_APPKIT_THREAD; 343 applicationDelegate = delegate; 344 345 if (NSApp != nil) { 346 [NSApp setDelegate: applicationDelegate]; 347 348 if (applicationDelegate && qad) { 349 [qad processQueuedEventsWithTargetDelegate: applicationDelegate]; 350 qad = nil; 351 } 352 } 353 } 354