Path: blob/master/src/java.desktop/macosx/native/libosxapp/NSApplicationAWT.m
41149 views
/*1* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425#import "NSApplicationAWT.h"2627#import <objc/runtime.h>28#import <JavaRuntimeSupport/JavaRuntimeSupport.h>2930#import "PropertiesUtilities.h"31#import "ThreadUtilities.h"32#import "QueuingApplicationDelegate.h"33#import "AWTIconData.h"3435/*36* Declare library specific JNI_Onload entry if static build37*/38DEF_STATIC_JNI_OnLoad3940static BOOL sUsingDefaultNIB = YES;41static NSString *SHARED_FRAMEWORK_BUNDLE = @"/System/Library/Frameworks/JavaVM.framework";42static id <NSApplicationDelegate> applicationDelegate = nil;43static QueuingApplicationDelegate * qad = nil;4445// Flag used to indicate to the Plugin2 event synthesis code to do a postEvent instead of sendEvent46BOOL postEventDuringEventSynthesis = NO;4748/**49* Subtypes of NSApplicationDefined, which are used for custom events.50*/51enum {52ExecuteBlockEvent = 777, NativeSyncQueueEvent53};5455@implementation NSApplicationAWT5657- (id) init58{59// Headless: NO60// Embedded: NO61// Multiple Calls: NO62// Caller: +[NSApplication sharedApplication]6364AWT_ASSERT_APPKIT_THREAD;65fApplicationName = nil;66dummyEventTimestamp = 0.0;67seenDummyEventLock = nil;686970// NSApplication will call _RegisterApplication with the application's bundle, but there may not be one.71// So, we need to call it ourselves to ensure the app is set up properly.72[self registerWithProcessManager];7374return [super init];75}7677- (void)dealloc78{79[fApplicationName release];80fApplicationName = nil;8182[super dealloc];83}8485- (void)finishLaunching86{87AWT_ASSERT_APPKIT_THREAD;8889JNIEnv *env = [ThreadUtilities getJNIEnv];9091SEL appearanceSel = @selector(setAppearance:); // macOS 10.14+92if ([self respondsToSelector:appearanceSel]) {93NSString *appearanceProp = [PropertiesUtilities94javaSystemPropertyForKey:@"apple.awt.application.appearance"95withEnv:env];96if (![@"system" isEqual:appearanceProp]) {97// by default use light mode, because dark mode is not supported yet98NSAppearance *appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua];99if (appearanceProp != nil) {100NSAppearance *requested = [NSAppearance appearanceNamed:appearanceProp];101if (requested != nil) {102appearance = requested;103}104}105// [self setAppearance:appearance];106[self performSelector:appearanceSel withObject:appearance];107}108}109110// Get default nib file location111// NOTE: This should learn about the current java.version. Probably best thru112// the Makefile system's -DFRAMEWORK_VERSION define. Need to be able to pass this113// thru to PB from the Makefile system and for local builds.114NSString *defaultNibFile = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.nib" withEnv:env];115if (!defaultNibFile) {116NSBundle *javaBundle = [NSBundle bundleWithPath:SHARED_FRAMEWORK_BUNDLE];117defaultNibFile = [javaBundle pathForResource:@"DefaultApp" ofType:@"nib"];118} else {119sUsingDefaultNIB = NO;120}121122[NSBundle loadNibFile:defaultNibFile externalNameTable: [NSDictionary dictionaryWithObject:self forKey:@"NSOwner"] withZone:nil];123124// Set user defaults to not try to parse application arguments.125NSUserDefaults * defs = [NSUserDefaults standardUserDefaults];126NSDictionary * noOpenDict = [NSDictionary dictionaryWithObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];127[defs registerDefaults:noOpenDict];128129// Fix up the dock icon now that we are registered with CAS and the Dock.130[self setDockIconWithEnv:env];131132// If we are using our nib (the default application NIB) we need to put the app name into133// the application menu, which has placeholders for the name.134if (sUsingDefaultNIB) {135NSUInteger i, itemCount;136NSMenu *theMainMenu = [NSApp mainMenu];137138// First submenu off the main menu is the application menu.139NSMenuItem *appMenuItem = [theMainMenu itemAtIndex:0];140NSMenu *appMenu = [appMenuItem submenu];141itemCount = [appMenu numberOfItems];142143for (i = 0; i < itemCount; i++) {144NSMenuItem *anItem = [appMenu itemAtIndex:i];145NSString *oldTitle = [anItem title];146[anItem setTitle:[NSString stringWithFormat:oldTitle, fApplicationName]];147}148}149150if (applicationDelegate) {151[self setDelegate:applicationDelegate];152} else {153qad = [QueuingApplicationDelegate sharedDelegate];154[self setDelegate:qad];155}156157[super finishLaunching];158}159160161- (void) registerWithProcessManager162{163// Headless: NO164// Embedded: NO165// Multiple Calls: NO166// Caller: -[NSApplicationAWT init]167168AWT_ASSERT_APPKIT_THREAD;169JNIEnv *env = [ThreadUtilities getJNIEnv];170171char envVar[80];172173// The following environment variable is set from the -Xdock:name param. It should be UTF8.174snprintf(envVar, sizeof(envVar), "APP_NAME_%d", getpid());175char *appName = getenv(envVar);176if (appName != NULL) {177fApplicationName = [NSString stringWithUTF8String:appName];178unsetenv(envVar);179}180181// If it wasn't specified as an argument, see if it was specified as a system property.182if (fApplicationName == nil) {183fApplicationName = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.name" withEnv:env];184}185186// If we STILL don't have it, the app name is retrieved from an environment variable (set in java.c) It should be UTF8.187if (fApplicationName == nil) {188char mainClassEnvVar[80];189snprintf(mainClassEnvVar, sizeof(mainClassEnvVar), "JAVA_MAIN_CLASS_%d", getpid());190char *mainClass = getenv(mainClassEnvVar);191if (mainClass != NULL) {192fApplicationName = [NSString stringWithUTF8String:mainClass];193unsetenv(mainClassEnvVar);194195NSRange lastPeriod = [fApplicationName rangeOfString:@"." options:NSBackwardsSearch];196if (lastPeriod.location != NSNotFound) {197fApplicationName = [fApplicationName substringFromIndex:lastPeriod.location + 1];198}199}200}201202// The dock name is nil for double-clickable Java apps (bundled and Web Start apps)203// When that happens get the display name, and if that's not available fall back to204// CFBundleName.205NSBundle *mainBundle = [NSBundle mainBundle];206if (fApplicationName == nil) {207fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];208209if (fApplicationName == nil) {210fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey];211212if (fApplicationName == nil) {213fApplicationName = (NSString *)[mainBundle objectForInfoDictionaryKey: (NSString *)kCFBundleExecutableKey];214215if (fApplicationName == nil) {216// Name of last resort is the last part of the applicatoin name without the .app (consistent with CopyProcessName)217fApplicationName = [[mainBundle bundlePath] lastPathComponent];218219if ([fApplicationName hasSuffix:@".app"]) {220fApplicationName = [fApplicationName stringByDeletingPathExtension];221}222}223}224}225}226227// We're all done trying to determine the app name. Hold on to it.228[fApplicationName retain];229230NSDictionary *registrationOptions = [NSMutableDictionary dictionaryWithObject:fApplicationName forKey:@"JRSAppNameKey"];231232NSString *launcherType = [PropertiesUtilities javaSystemPropertyForKey:@"sun.java.launcher" withEnv:env];233if ([@"SUN_STANDARD" isEqualToString:launcherType]) {234[registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsCommandLineKey"];235}236237NSString *uiElementProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.UIElement" withEnv:env];238if ([@"true" isCaseInsensitiveLike:uiElementProp]) {239[registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsUIElementKey"];240}241242NSString *backgroundOnlyProp = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.BackgroundOnly" withEnv:env];243if ([@"true" isCaseInsensitiveLike:backgroundOnlyProp]) {244[registrationOptions setValue:[NSNumber numberWithBool:YES] forKey:@"JRSAppIsBackgroundOnlyKey"];245}246247// TODO replace with direct call248// [JRSAppKitAWT registerAWTAppWithOptions:registrationOptions];249// and remove below transform/activate/run hack250251id jrsAppKitAWTClass = objc_getClass("JRSAppKitAWT");252SEL registerSel = @selector(registerAWTAppWithOptions:);253if ([jrsAppKitAWTClass respondsToSelector:registerSel]) {254[jrsAppKitAWTClass performSelector:registerSel withObject:registrationOptions];255return;256}257258// HACK BEGIN259// The following is necessary to make the java process behave like a260// proper foreground application...261[ThreadUtilities performOnMainThreadWaiting:NO block:^(){262ProcessSerialNumber psn;263GetCurrentProcess(&psn);264TransformProcessType(&psn, kProcessTransformToForegroundApplication);265266[NSApp activateIgnoringOtherApps:YES];267[NSApp run];268}];269// HACK END270}271272- (void) setDockIconWithEnv:(JNIEnv *)env {273NSString *theIconPath = nil;274275// The following environment variable is set in java.c. It is probably UTF8.276char envVar[80];277snprintf(envVar, sizeof(envVar), "APP_ICON_%d", getpid());278char *appIcon = getenv(envVar);279if (appIcon != NULL) {280theIconPath = [NSString stringWithUTF8String:appIcon];281unsetenv(envVar);282}283284if (theIconPath == nil) {285theIconPath = [PropertiesUtilities javaSystemPropertyForKey:@"apple.awt.application.icon" withEnv:env];286}287288// Use the path specified to get the icon image289NSImage* iconImage = nil;290if (theIconPath != nil) {291iconImage = [[NSImage alloc] initWithContentsOfFile:theIconPath];292}293294// If no icon file was specified or we failed to get the icon image295// and there is no bundle's icon, then use the default icon296if (iconImage == nil) {297NSString* bundleIcon = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"];298if (bundleIcon == nil) {299NSData* iconData;300iconData = [[NSData alloc] initWithBytesNoCopy: sAWTIconData length: sizeof(sAWTIconData) freeWhenDone: NO];301iconImage = [[NSImage alloc] initWithData: iconData];302[iconData release];303}304}305306// Set up the dock icon if we have an icon image.307if (iconImage != nil) {308[NSApp setApplicationIconImage:iconImage];309[iconImage release];310}311}312313+ (void) runAWTLoopWithApp:(NSApplication*)app {314NSAutoreleasePool *pool = [NSAutoreleasePool new];315316// Make sure that when we run in javaRunLoopMode we don't exit randomly317[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:[ThreadUtilities javaRunLoopMode]];318319do {320@try {321[app run];322} @catch (NSException* e) {323NSLog(@"Apple AWT Startup Exception: %@", [e description]);324NSLog(@"Apple AWT Startup Exception callstack: %@", [e callStackSymbols]);325NSLog(@"Apple AWT Restarting Native Event Thread");326327[app stop:app];328}329} while (YES);330331[pool drain];332}333334- (BOOL)usingDefaultNib {335return sUsingDefaultNIB;336}337338- (void)orderFrontStandardAboutPanelWithOptions:(NSDictionary *)optionsDictionary {339if (!optionsDictionary) {340optionsDictionary = [NSMutableDictionary dictionaryWithCapacity:2];341[optionsDictionary setValue:[[[[[NSApp mainMenu] itemAtIndex:0] submenu] itemAtIndex:0] title] forKey:@"ApplicationName"];342if (![NSImage imageNamed:@"NSApplicationIcon"]) {343[optionsDictionary setValue:[NSApp applicationIconImage] forKey:@"ApplicationIcon"];344}345}346347[super orderFrontStandardAboutPanelWithOptions:optionsDictionary];348}349350#define DRAGMASK (NSMouseMovedMask | NSLeftMouseDraggedMask | NSRightMouseDownMask | NSRightMouseDraggedMask | NSLeftMouseUpMask | NSRightMouseUpMask | NSFlagsChangedMask | NSKeyDownMask)351352#if defined(MAC_OS_X_VERSION_10_12) && __LP64__353// 10.12 changed `mask` to NSEventMask (unsigned long long) for x86_64 builds.354- (NSEvent *)nextEventMatchingMask:(NSEventMask)mask355#else356- (NSEvent *)nextEventMatchingMask:(NSUInteger)mask357#endif358untilDate:(NSDate *)expiration inMode:(NSString *)mode dequeue:(BOOL)deqFlag {359if (mask == DRAGMASK && [((NSString *)kCFRunLoopDefaultMode) isEqual:mode]) {360postEventDuringEventSynthesis = YES;361}362363NSEvent *event = [super nextEventMatchingMask:mask untilDate:expiration inMode:mode dequeue: deqFlag];364postEventDuringEventSynthesis = NO;365366return event;367}368369// NSTimeInterval has microseconds precision370#define TS_EQUAL(ts1, ts2) (fabs((ts1) - (ts2)) < 1e-6)371372- (void)sendEvent:(NSEvent *)event373{374if ([event type] == NSApplicationDefined375&& TS_EQUAL([event timestamp], dummyEventTimestamp)376&& (short)[event subtype] == NativeSyncQueueEvent377&& [event data1] == NativeSyncQueueEvent378&& [event data2] == NativeSyncQueueEvent) {379[seenDummyEventLock lockWhenCondition:NO];380[seenDummyEventLock unlockWithCondition:YES];381} else if ([event type] == NSApplicationDefined382&& (short)[event subtype] == ExecuteBlockEvent383&& [event data1] != 0 && [event data2] == ExecuteBlockEvent) {384void (^block)() = (void (^)()) [event data1];385block();386[block release];387} else if ([event type] == NSKeyUp && ([event modifierFlags] & NSCommandKeyMask)) {388// Cocoa won't send us key up event when releasing a key while Cmd is down,389// so we have to do it ourselves.390[[self keyWindow] sendEvent:event];391} else {392[super sendEvent:event];393}394}395396/*397* Posts the block to the AppKit event queue which will be executed398* on the main AppKit loop.399* While running nested loops this event will be ignored.400*/401- (void)postRunnableEvent:(void (^)())block402{403void (^copy)() = [block copy];404NSInteger encode = (NSInteger) copy;405NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];406NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined407location: NSMakePoint(0,0)408modifierFlags: 0409timestamp: 0410windowNumber: 0411context: nil412subtype: ExecuteBlockEvent413data1: encode414data2: ExecuteBlockEvent];415416[NSApp postEvent: event atStart: NO];417[pool drain];418}419420- (void)postDummyEvent:(bool)useCocoa {421seenDummyEventLock = [[NSConditionLock alloc] initWithCondition:NO];422dummyEventTimestamp = [NSProcessInfo processInfo].systemUptime;423424NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];425NSEvent* event = [NSEvent otherEventWithType: NSApplicationDefined426location: NSMakePoint(0,0)427modifierFlags: 0428timestamp: dummyEventTimestamp429windowNumber: 0430context: nil431subtype: NativeSyncQueueEvent432data1: NativeSyncQueueEvent433data2: NativeSyncQueueEvent];434if (useCocoa) {435[NSApp postEvent:event atStart:NO];436} else {437ProcessSerialNumber psn;438GetCurrentProcess(&psn);439CGEventPostToPSN(&psn, [event CGEvent]);440}441[pool drain];442}443444- (void)waitForDummyEvent:(double)timeout {445bool unlock = true;446if (timeout >= 0) {447double sec = timeout / 1000;448unlock = [seenDummyEventLock lockWhenCondition:YES449beforeDate:[NSDate dateWithTimeIntervalSinceNow:sec]];450} else {451[seenDummyEventLock lockWhenCondition:YES];452}453if (unlock) {454[seenDummyEventLock unlock];455}456[seenDummyEventLock release];457458seenDummyEventLock = nil;459}460461@end462463464void OSXAPP_SetApplicationDelegate(id <NSApplicationDelegate> newdelegate)465{466AWT_ASSERT_APPKIT_THREAD;467applicationDelegate = newdelegate;468469if (NSApp != nil) {470[NSApp setDelegate: applicationDelegate];471472if (applicationDelegate && qad) {473[qad processQueuedEventsWithTargetDelegate: applicationDelegate];474qad = nil;475}476}477}478479480