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