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 (theIconPath == nil) {
 262         NSString* bundleIcon = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"];
 263         if (bundleIcon == nil) {
 264             theIconPath = defaultIconPath;
 265         }
 266     }
 267 
 268     // Set up the dock icon if we have an icon name.
 269     if (theIconPath != nil) {
 270         NSImage *iconImage = [[NSImage alloc] initWithContentsOfFile:theIconPath];
 271 
 272         // If we failed for some reason fall back to the default icon.
 273         if (iconImage == nil) {
 274             iconImage = [[NSImage alloc] initWithContentsOfFile:defaultIconPath];
 275         }
 276 
 277         [NSApp setApplicationIconImage:iconImage];
 278         [iconImage release];
 279     }
 280 }
 281 
 282 + (void) runAWTLoopWithApp:(NSApplication*)app {
 283     NSAutoreleasePool *pool = [NSAutoreleasePool new];
 284 
 285     // Make sure that when we run in AWTRunLoopMode we don't exit randomly
 286     [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[JNFRunLoop javaRunLoopMode]];
 287 
 288     do {
 289         @try {
 290             [app run];
 291         } @catch (NSException* e) {
 292             NSLog(@"Apple AWT Startup Exception: %@", [e description]);
 293             NSLog(@"Apple AWT Restarting Native Event Thread");
 294 
 295             [app stop:app];
 296         }
 297     } while (YES);
 298 
 299     [pool drain];
 300 }
 301 
 302 - (BOOL)usingDefaultNib {
 303     return sUsingDefaultNIB;
 304 }
 305 
 306 - (void)orderFrontStandardAboutPanelWithOptions:(NSDictionary *)optionsDictionary {
 307     if (!optionsDictionary) {
 308         optionsDictionary = [NSMutableDictionary dictionaryWithCapacity:2];
 309         [optionsDictionary setValue:[[[[[NSApp mainMenu] itemAtIndex:0] submenu] itemAtIndex:0] title] forKey:@"ApplicationName"];
 310         if (![NSImage imageNamed:@"NSApplicationIcon"]) {
 311             [optionsDictionary setValue:[NSApp applicationIconImage] forKey:@"ApplicationIcon"];
 312         }
 313     }
 314 
 315     [super orderFrontStandardAboutPanelWithOptions:optionsDictionary];
 316 }
 317 
 318 #define DRAGMASK (NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseDraggedMask | NSLeftMouseUpMask | NSRightMouseUpMask | NSFlagsChangedMask | NSKeyDownMask)
 319 
 320 - (NSEvent *)nextEventMatchingMask:(NSUInteger)mask untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag {
 321     if (mask == DRAGMASK && [((NSString *)kCFRunLoopDefaultMode) isEqual:mode]) {
 322         postEventDuringEventSynthesis = YES;
 323     }
 324 
 325     NSEvent *event = [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue: deqFlag];
 326     postEventDuringEventSynthesis = NO;
 327 
 328     return event;
 329 }
 330 
 331 @end
 332 
 333 
 334 void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> delegate)
 335 {
 336 AWT_ASSERT_APPKIT_THREAD;
 337     applicationDelegate = delegate;
 338 
 339     if (NSApp != nil) {
 340         [NSApp setDelegate: applicationDelegate];
 341 
 342         if (applicationDelegate && qad) {
 343             [qad processQueuedEventsWithTargetDelegate: applicationDelegate];
 344             qad = nil;
 345         }
 346     }
 347 }
 348