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