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