All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nary.appbundler.appbundler.1.0.source-code.main.m Maven / Gradle / Ivy

There is a newer version: 1.3.1
Show newest version
/*
 * Copyright 2012, Oracle and/or its affiliates. All rights reserved.
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

#import 
#include 
#include 
#include 

#define JAVA_LAUNCH_ERROR "JavaLaunchError"

#define JVM_RUNTIME_KEY "JVMRuntime"
#define WORKING_DIR "WorkingDirectory"
#define JVM_MAIN_CLASS_NAME_KEY "JVMMainClassName"
#define JVM_OPTIONS_KEY "JVMOptions"
#define JVM_DEFAULT_OPTIONS_KEY "JVMDefaultOptions"
#define JVM_ARGUMENTS_KEY "JVMArguments"
#define JVM_CLASSPATH_KEY "JVMClassPath"
#define JVM_VERSION_KEY "JVMVersion"
#define JRE_PREFERRED_KEY "JREPreferred"
#define JDK_PREFERRED_KEY "JDKPreferred"
#define JVM_DEBUG_KEY "JVMDebug"
#define IGNORE_PSN_KEY "IgnorePSN"

#define JVM_RUN_PRIVILEGED "JVMRunPrivileged"
#define JVM_RUN_JNLP "JVMJNLPLauncher"
#define JVM_RUN_JAR "JVMJARLauncher"


#define UNSPECIFIED_ERROR "An unknown error occurred."

#define APP_ROOT_PREFIX "$APP_ROOT"
#define JVM_RUNTIME "$JVM_RUNTIME"

#define JAVA_RUNTIME  "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home"
#define LIBJLI_DY_LIB "lib/jli/libjli.dylib"
#define DEPLOY_LIB    "lib/deploy.jar"


#define DLog(...) NSLog(@"%s %@", __PRETTY_FUNCTION__, [NSString stringWithFormat:__VA_ARGS__])


typedef int (JNICALL *JLI_Launch_t)(int argc, char ** argv,
                                    int jargc, const char** jargv,
                                    int appclassc, const char** appclassv,
                                    const char* fullversion,
                                    const char* dotversion,
                                    const char* pname,
                                    const char* lname,
                                    jboolean javaargs,
                                    jboolean cpwildcard,
                                    jboolean javaw,
                                    jint ergo);

static char** progargv = NULL;
static int progargc = 0;
static int launchCount = 0;

const char * tmpFile();
int launch(char *, int, char **);

NSString * findJavaDylib (NSString *, bool, bool, bool, bool);
NSString * findJREDylib (int, bool, bool);
NSString * findJDKDylib (int, bool, bool);
int extractMajorVersion (NSString *);
NSString * convertRelativeFilePath(NSString *);
NSString * addDirectoryToSystemArguments(NSUInteger, NSSearchPathDomainMask, NSString *, NSMutableArray *);
void addModifierFlagToSystemArguments(NSEventModifierFlags, NSString *, NSEventModifierFlags, NSMutableArray *);

int main(int argc, char *argv[]) {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    int result;
    @try {
    	if ((argc > 1) && (launchCount == 0)) {
    		progargc = argc - 1;
    		progargv = &argv[1];
    	}

        launch(argv[0], progargc, progargv);
        result = 0;
    } @catch (NSException *exception) {
        NSAlert *alert = [[NSAlert alloc] init];
        [alert setAlertStyle:NSCriticalAlertStyle];
        [alert setMessageText:[exception reason]];
        [alert runModal];

        result = 1;
    }

    [pool drain];

    return result;
}

int launch(char *commandName, int progargc, char *progargv[]) {

    // Preparation for jnlp launcher arguments
    const char *const_jargs = NULL;
    const char *const_appclasspath = NULL;

    // Get the main bundle
    NSBundle *mainBundle = [NSBundle mainBundle];

    // Get the main bundle's info dictionary
    NSDictionary *infoDictionary = [mainBundle infoDictionary];
    
    // Test for debugging (but only on the second runthrough)
    bool isDebugging = [[infoDictionary objectForKey:@JVM_DEBUG_KEY] boolValue];
    
    if (isDebugging) {
        DLog(@"\n\n\n\nLoading Application '%@'", [infoDictionary objectForKey:@"CFBundleName"]);
    }
    
    // Set the working directory based on config, defaulting to the user's home directory
    NSString *workingDir = [infoDictionary objectForKey:@WORKING_DIR];
    if (workingDir != nil) {
        workingDir = [workingDir stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
    } else {
        workingDir = [[NSFileManager defaultManager] currentDirectoryPath];
        workingDir = NSHomeDirectory(); // REVIEW: Check which if these ones is realy the users home directory ...
    }
    if (isDebugging) {
    	DLog(@"Working Directory: '%@'", convertRelativeFilePath(workingDir));
    }
    
    chdir([workingDir UTF8String]);
           
    // execute privileged
    NSString *privileged = [infoDictionary objectForKey:@JVM_RUN_PRIVILEGED];
    if ( privileged != nil && getuid() != 0 ) {
        NSDictionary *error = [NSDictionary new];
        
        NSString *script =  [NSString stringWithFormat:@"do shell script \"\\\"%@\\\" > /dev/null 2>&1 &\" with administrator privileges", [NSString stringWithCString:commandName encoding:NSASCIIStringEncoding]];
        
        if (isDebugging) {
            DLog(@"script: %@", script);
        }

        NSAppleScript *appleScript = [[NSAppleScript new] initWithSource:script];
        if ([appleScript executeAndReturnError:&error]) {
            // This means we successfully elevated the application and can stop in here.
            return 0;
        }
    }
    
    // Locate the JLI_Launch() function
    NSString *runtime = [infoDictionary objectForKey:@JVM_RUNTIME_KEY];
    NSString *runtimePath = [[mainBundle builtInPlugInsPath] stringByAppendingPathComponent:runtime];

    NSString *jvmRequired = [infoDictionary objectForKey:@JVM_VERSION_KEY];
    bool exactVersionMatch = false;
    bool jrePreferred = [[infoDictionary objectForKey:@JRE_PREFERRED_KEY] boolValue];
    bool jdkPreferred = [[infoDictionary objectForKey:@JDK_PREFERRED_KEY] boolValue];
    
    if (jrePreferred && jdkPreferred) {
        DLog(@"Specifying both JRE- and JDK-preferred means neither is preferred");
        jrePreferred = false;
        jdkPreferred = false;
    }

    // check for jnlp launcher name
    // This basically circumvents the security problems introduced with 10.8.4 that JNLP Files must be signed to execute them without CTRL+CLick -> Open
    // See: How to sign (dynamic) JNLP files for OSX 10.8.4 and Gatekeeper http://stackoverflow.com/questions/16958130/how-to-sign-dynamic-jnlp-files-for-osx-10-8-4-and-gatekeeper
    // There is no solution to properly sign a dynamic jnlp file to date. Both Apple and Oracle have open rdars/tickets on this.
    // The following mechanism encapsulates a JNLP file/template. It makes a temporary copy when executing. This ensures that the JNLP file can be updates from the server at runtime.
    // YES, this may insert additional security threats, but it is still the only way to avoid permission problems.
    // It is highly recommended that the resulting .app container is being signed with a certificate from Apple - otherwise you will not need this mechanism.
    // Moved up here to check if we want to launch a JNLP. If so: make sure the version is below 9
    NSString *jnlplauncher = [infoDictionary objectForKey:@JVM_RUN_JNLP];
    if ( jnlplauncher != nil ) {
        int required = 8;
        if ( jvmRequired != nil ) {
            required = extractMajorVersion (jvmRequired);
            if (required > 8) { required = 8; }
        }
        
        exactVersionMatch = true;
        jvmRequired = [NSString stringWithFormat:@"1.%i", required];
        DLog(@"Will Require a JVM version '%i' due to JNLP restrictions", required);
    }
    
    NSString *javaDylib;

    // If a runtime is set, we really want it. If it is not there, we will fail later on.
    if (runtime != nil) {
        NSString *dylibRelPath = @"Contents/Home/jre";
        javaDylib = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB];
        BOOL isDir;
        NSFileManager *fm = [[NSFileManager alloc] init];
        BOOL javaDylibFileExists = [fm fileExistsAtPath:javaDylib isDirectory:&isDir];
        if (!javaDylibFileExists || isDir) {
            dylibRelPath = @"Contents/Home";
            javaDylib = [[runtimePath stringByAppendingPathComponent:dylibRelPath] stringByAppendingPathComponent:@LIBJLI_DY_LIB];
            javaDylibFileExists = [fm fileExistsAtPath:javaDylib isDirectory:&isDir];
                if (!javaDylibFileExists || isDir) {
                    javaDylib = NULL;
                }
        }
        if (isDebugging) {
            DLog(@"Java Runtime (%@) Relative Path: '%@' (dylib: %@)", runtime, runtimePath, javaDylib);
        }
    }
    else {
        // Search for the runtimePath, then make it a libjli.dylib path.
        runtimePath = findJavaDylib (jvmRequired, jrePreferred, jdkPreferred, isDebugging, exactVersionMatch);
        javaDylib = [runtimePath stringByAppendingPathComponent:@LIBJLI_DY_LIB];

        if (isDebugging) {
            DLog(@"Java Runtime Dylib Path: '%@'", convertRelativeFilePath(javaDylib));
        }
    }

    const char *libjliPath = NULL;
    if (javaDylib != nil)
    {
        libjliPath = [javaDylib fileSystemRepresentation];
    }
    
    DLog(@"Launchpath: %s", libjliPath);

    void *libJLI = dlopen(libjliPath, RTLD_LAZY);

    JLI_Launch_t jli_LaunchFxnPtr = NULL;
    if (libJLI != NULL) {
        jli_LaunchFxnPtr = dlsym(libJLI, "JLI_Launch");
    }

    if (jli_LaunchFxnPtr == NULL) {
        NSString *msg;

        if (runtime == nil && jvmRequired != nil) {
            int required = extractMajorVersion (jvmRequired);
            
            if (required < 7) { required = 7; }

            if (jdkPreferred) {
                NSString *msga = NSLocalizedString(@"JDKxLoadFullError", @UNSPECIFIED_ERROR);
                msg = [NSString stringWithFormat:msga, required];
            }
            else {
                NSString *msga = NSLocalizedString(@"JRExLoadFullError", @UNSPECIFIED_ERROR);
                msg = [NSString stringWithFormat:msga, required];
            }
        }
        else {
            msg = NSLocalizedString(@"JRELoadError", @UNSPECIFIED_ERROR);
        }
        
        DLog(@"Error launching JVM Runtime (%@) Relative Path: '%@' (dylib: %@)\n  error: %@",
              runtime, runtimePath, javaDylib, msg);

        [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
                reason:msg userInfo:nil] raise];
    }

    // Set the class path
    NSFileManager *defaultFileManager = [NSFileManager defaultManager];
    NSString *mainBundlePath = [mainBundle bundlePath];
    
    // make sure the bundle path does not contain a colon, as that messes up the java.class.path,
    // because colons are used a path separators and cannot be escaped.
    
    // funny enough, Finder does not let you create folder with colons in their names,
    // but when you create a folder with a slash, e.g. "audio/video", it is accepted
    // and turned into... you guessed it, a colon:
    // "audio:video"
    if ([mainBundlePath rangeOfString:@":"].location != NSNotFound) {
        [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
                                 reason:NSLocalizedString(@"BundlePathContainsColon", @UNSPECIFIED_ERROR)
                               userInfo:nil] raise];
    }
    if (isDebugging) {
        NSLog(@"Main Bundle Path: '%@'", mainBundlePath);
    }

    // Set the class path
    NSString *javaPath = [mainBundlePath stringByAppendingString:@"/Contents/Java"];
    NSMutableArray *systemArguments = [[NSMutableArray alloc] init];
    NSMutableString *classPath = [NSMutableString stringWithString:@"-Djava.class.path="];

    // Set the library path
    NSString *libraryPath = [NSString stringWithFormat:@"-Djava.library.path=%@/Contents/MacOS", mainBundlePath];
    [systemArguments addObject:libraryPath];
    
    // Get the VM options
    NSMutableArray *options = [[infoDictionary objectForKey:@JVM_OPTIONS_KEY] mutableCopy];
    if (options == nil) {
        options = [NSMutableArray array];
    }
    
    // Get the VM default options
    NSArray *defaultOptions = [NSArray array];
    NSDictionary *defaultOptionsDict = [infoDictionary objectForKey:@JVM_DEFAULT_OPTIONS_KEY];
    if (defaultOptionsDict != nil) {
        NSMutableDictionary *defaults = [NSMutableDictionary dictionaryWithDictionary: defaultOptionsDict];
        // Replace default options with user specific options, if available
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        // Create special key that should be used by Java's java.util.Preferences impl
        // Requires us to use "/" + bundleIdentifier.replace('.', '/') + "/JVMOptions/" as node on the Java side
        // Beware: bundleIdentifiers shorter than 3 segments are placed in a different file!
        // See java/util/prefs/MacOSXPreferences.java of OpenJDK for details
        NSString *bundleDictionaryKey = [mainBundle bundleIdentifier];
        bundleDictionaryKey = [bundleDictionaryKey stringByReplacingOccurrencesOfString:@"." withString:@"/"];
        bundleDictionaryKey = [NSString stringWithFormat: @"/%@/", bundleDictionaryKey];
        
        NSDictionary *bundleDictionary = [userDefaults dictionaryForKey: bundleDictionaryKey];
        if (bundleDictionary != nil) {
            NSDictionary *jvmOptionsDictionary = [bundleDictionary objectForKey: @"JVMOptions/"];
            for (NSString *key in jvmOptionsDictionary) {
                NSString *value = [jvmOptionsDictionary objectForKey:key];
                [defaults setObject: value forKey: key];
            }
        }
        defaultOptions = [defaults allValues];
    }
    
    // Get the application arguments
    NSMutableArray *arguments = [[infoDictionary objectForKey:@JVM_ARGUMENTS_KEY] mutableCopy];
    if (arguments == nil) {
        arguments = [NSMutableArray array];
    }

    // Check for a defined JAR File below the Contents/Java folder
    // If set, use this instead of a classpath setting
    NSString *jarlauncher = [infoDictionary objectForKey:@JVM_RUN_JAR];

    // Get the main class name
    NSString *mainClassName = [infoDictionary objectForKey:@JVM_MAIN_CLASS_NAME_KEY];

    if ( jnlplauncher != nil ) {
        
        const_appclasspath = [[runtimePath stringByAppendingPathComponent:@DEPLOY_LIB] fileSystemRepresentation];
        
        // JNLP Launcher found, need to modify quite a bit now
        [options addObject:@"-classpath"];
        [options addObject:[NSString stringWithFormat:@"%s", const_appclasspath]];
        
        // unset the original classpath
        classPath = nil;
        
        // Main Class is javaws
        mainClassName=@"com.sun.javaws.Main";
        
        // Optional stuff that javaws would do as well
        [options addObject:@"-Dsun.awt.warmup=true"];
        [options addObject:@"-Xverify:remote"];
        [options addObject:@"-Djnlpx.remove=true"];
        [options addObject:@"-DtrustProxy=true"];
        
        [options addObject:[NSString stringWithFormat:@"-Djava.security.policy=file:%@/lib/security/javaws.policy", runtimePath]];
        [options addObject:[NSString stringWithFormat:@"-Xbootclasspath/a:%@/lib/javaws.jar:%@/lib/deploy.jar:%@/lib/plugin.jar", runtimePath, runtimePath, runtimePath]];
        
        // Argument that javaws does also
        // [arguments addObject:@"-noWebStart"];
        
        // Copy the jnlp to a temporary location
        NSError *copyerror = nil;
        NSString *tempFileName = [NSString stringWithCString:tmpFile() encoding:NSASCIIStringEncoding];
        // File now exists.
        [defaultFileManager removeItemAtPath:tempFileName error:NULL];
        
        // Check if this is absolute or relative (else)
        NSString *jnlpPath = [mainBundlePath stringByAppendingPathComponent:jnlplauncher];
        if ( ![defaultFileManager fileExistsAtPath:jnlpPath] ) {
            jnlpPath = [javaPath stringByAppendingPathComponent:jnlplauncher];
        }
        
        [defaultFileManager copyItemAtURL:[NSURL fileURLWithPath:jnlpPath] toURL:[NSURL fileURLWithPath:tempFileName] error:©error];
        if ( copyerror != nil ) {
            NSLog(@"Error: %@", copyerror);
            [[NSException exceptionWithName:@"Error while copying JNLP File"
                                     reason:@"File copy error"
                                   userInfo:copyerror.userInfo] raise];
        }
        
        // Add the jnlp as argument so that javaws.Main can read and delete it
        [arguments addObject:tempFileName];
        
    } else
    // Either mainClassName or jarLauncher has to be set since this is not a jnlpLauncher
    if ( mainClassName == nil && jarlauncher == nil ) {
        [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
            reason:NSLocalizedString(@"MainClassNameRequired", @UNSPECIFIED_ERROR)
            userInfo:nil] raise];
    }
    
    if (isDebugging) {
        DLog(@"Main Class Name: '%@'", mainClassName);
    }

    // If a jar file is defined as launcher, disacard the javaPath
    if ( jarlauncher != nil ) {
        [classPath appendFormat:@":%@/%@", javaPath, jarlauncher];
    } else {
        
        NSArray *cp = [infoDictionary objectForKey:@JVM_CLASSPATH_KEY];
        if (cp == nil) {
            
            // Implicit classpath, so use the contents of the "Java" folder to build an explicit classpath
            
            [classPath appendFormat:@"%@/Classes", javaPath];
            NSFileManager *defaultFileManager = [NSFileManager defaultManager];
            NSArray *javaDirectoryContents = [defaultFileManager contentsOfDirectoryAtPath:javaPath error:nil];
            if (javaDirectoryContents == nil) {
                [[NSException exceptionWithName:@JAVA_LAUNCH_ERROR
                                         reason:NSLocalizedString(@"JavaDirectoryNotFound", @UNSPECIFIED_ERROR)
                                       userInfo:nil] raise];
            }
            
            for (NSString *file in javaDirectoryContents) {
                if ([file hasSuffix:@".jar"]) {
                    [classPath appendFormat:@":%@/%@", javaPath, file];
                }
            }
            
        } else {
            
            // Explicit ClassPath
            
            int k = 0;
            for (NSString *file in cp) {
                if (k++ > 0) [classPath appendString:@":"]; // add separator if needed
                file = [file stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
                [classPath appendString:file];
            }
        }
    }

    if ( classPath != nil ) {
        [systemArguments addObject:classPath];
    }

    // Set OSX special folders
    NSString * libraryDirectory = addDirectoryToSystemArguments(NSLibraryDirectory, NSUserDomainMask, @"LibraryDirectory", systemArguments);
    addDirectoryToSystemArguments(NSDocumentDirectory, NSUserDomainMask, @"DocumentsDirectory", systemArguments);
    addDirectoryToSystemArguments(NSApplicationSupportDirectory, NSUserDomainMask, @"ApplicationSupportDirectory", systemArguments);
    addDirectoryToSystemArguments(NSCachesDirectory, NSUserDomainMask, @"CachesDirectory", systemArguments);
    addDirectoryToSystemArguments(NSApplicationDirectory, NSUserDomainMask, @"ApplicationDirectory", systemArguments);
    addDirectoryToSystemArguments(NSAutosavedInformationDirectory, NSUserDomainMask, @"AutosavedInformationDirectory", systemArguments);
    addDirectoryToSystemArguments(NSDesktopDirectory, NSUserDomainMask, @"DesktopDirectory", systemArguments);
    addDirectoryToSystemArguments(NSDownloadsDirectory, NSUserDomainMask, @"DownloadsDirectory", systemArguments);
    addDirectoryToSystemArguments(NSMoviesDirectory, NSUserDomainMask, @"MoviesDirectory", systemArguments);
    addDirectoryToSystemArguments(NSMusicDirectory, NSUserDomainMask, @"MusicDirectory", systemArguments);
    addDirectoryToSystemArguments(NSPicturesDirectory, NSUserDomainMask, @"PicturesDirectory", systemArguments);
    addDirectoryToSystemArguments(NSSharedPublicDirectory, NSUserDomainMask, @"SharedPublicDirectory", systemArguments);
    
    addDirectoryToSystemArguments(NSLibraryDirectory, NSLocalDomainMask, @"SystemLibraryDirectory", systemArguments);
    addDirectoryToSystemArguments(NSApplicationSupportDirectory, NSLocalDomainMask, @"SystemApplicationSupportDirectory", systemArguments);
    addDirectoryToSystemArguments(NSCachesDirectory, NSLocalDomainMask, @"SystemCachesDirectory", systemArguments);
    addDirectoryToSystemArguments(NSApplicationDirectory, NSLocalDomainMask, @"SystemApplicationDirectory", systemArguments);
    addDirectoryToSystemArguments(NSUserDirectory, NSLocalDomainMask, @"SystemUserDirectory", systemArguments);
    
    // get the user's home directory, independent of the sandbox container
    int bufsize;
    if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1) {
        char buffer[bufsize];
        struct passwd pwd, *result = NULL;
        if (getpwuid_r(getuid(), &pwd, buffer, bufsize, &result) == 0 && result) {
            [systemArguments addObject:[NSString stringWithFormat:@"-DUserHome=%s", pwd.pw_dir]];
        }
    }
    
    //Sandbox
    NSString *containersDirectory = [libraryDirectory stringByAppendingPathComponent:@"Containers"];
    NSString *sandboxEnabled = @"false";
    BOOL isDir;
    NSFileManager *fm = [[NSFileManager alloc] init];
    BOOL containersDirExists = [fm fileExistsAtPath:containersDirectory isDirectory:&isDir];
    if (containersDirExists && isDir) {
        sandboxEnabled = @"true";
    }
    NSString *sandboxEnabledVar = [NSString stringWithFormat:@"-DSandboxEnabled=%@", sandboxEnabled];
    [systemArguments addObject:sandboxEnabledVar];
	

    // Mojave Dark Mode enabled?
    NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
    BOOL isDarkMode = (osxMode != nil && [osxMode isEqualToString:@"Dark"]);

    NSString *darkModeEnabledVar = [NSString stringWithFormat:@"-DDarkMode=%s",
                                                    (isDarkMode ? "true" : "false")];
    [systemArguments addObject:darkModeEnabledVar];

	// Check for modifier keys on app launch
	
	// Since [NSEvent modifierFlags] is only available since OS X 10.6., only add properties if supported.
	if ([NSEvent respondsToSelector:@selector(modifierFlags)]) {
		NSEventModifierFlags launchModifierFlags = [NSEvent modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
		
		[systemArguments addObject:[NSString stringWithFormat:@"-DLaunchModifierFlags=%lu", (unsigned long)launchModifierFlags]];
		
		addModifierFlagToSystemArguments(NSEventModifierFlagCapsLock, @"LaunchModifierFlagCapsLock", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagShift, @"LaunchModifierFlagShift", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagControl, @"LaunchModifierFlagControl", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagOption, @"LaunchModifierFlagOption", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagCommand, @"LaunchModifierFlagCommand", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagNumericPad, @"LaunchModifierFlagNumericPad", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagHelp, @"LaunchModifierFlagHelp", launchModifierFlags, systemArguments);
		addModifierFlagToSystemArguments(NSEventModifierFlagFunction, @"LaunchModifierFlagFunction", launchModifierFlags, systemArguments);
	}
	
	

    // Remove -psn argument
    int newProgargc = progargc;
    char *newProgargv[newProgargc];
    for (int i = 0; i < progargc; i++) {
        newProgargv[i] = progargv[i];
    }
    
    bool ignorePSN = [[infoDictionary objectForKey:@IGNORE_PSN_KEY] boolValue];
    if (ignorePSN) {
        NSString *psnRegexp = @"^-psn_\\d_\\d+$";
        NSPredicate *psnTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", psnRegexp];

        int shift = 0;
        int i = 0;
        while (i < newProgargc) {
            NSString *s = [NSString stringWithFormat:@"%s", newProgargv[i]];
            if ([psnTest evaluateWithObject: s]){
                shift++;
                newProgargc--;
            }
            newProgargv[i] = newProgargv[i+shift];
            i++;
        }
    }

    // replace $APP_ROOT in environment variables
    NSDictionary* environment = [[NSProcessInfo processInfo] environment];
    for (NSString* key in environment) {
        NSString* value = [environment objectForKey:key];
        NSString* newValue = [value stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        if (! [newValue isEqualToString:value]) {
            setenv([key UTF8String], [newValue UTF8String], 1);
        }
    }

    // Initialize the arguments to JLI_Launch()
    // +5 due to the special directories and the sandbox enabled property
    int argc = 1 + [systemArguments count] + [options count] + [defaultOptions count] + 1 + [arguments count] + newProgargc;
    char *argv[argc];

    int i = 0;
    argv[i++] = commandName;
    for (NSString *systemArgument in systemArguments) {
    	argv[i++] = strdup([systemArgument UTF8String]);
    }

    for (NSString *option in options) {
        option = [option stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        option = [option stringByReplacingOccurrencesOfString:@JVM_RUNTIME withString:runtimePath];
        argv[i++] = strdup([option UTF8String]);
        if (isDebugging) { DLog(@"Option: %@",option); }
    }

    for (NSString *defaultOption in defaultOptions) {
        defaultOption = [defaultOption stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        argv[i++] = strdup([defaultOption UTF8String]);
        if (isDebugging) { DLog(@"DefaultOption: %@",defaultOption); }
    }

    argv[i++] = strdup([mainClassName UTF8String]);

    for (NSString *argument in arguments) {
        argument = [argument stringByReplacingOccurrencesOfString:@APP_ROOT_PREFIX withString:[mainBundle bundlePath]];
        argv[i++] = strdup([argument UTF8String]);
    }

	int ctr = 0;
	for (ctr = 0; ctr < newProgargc; ctr++) {
		argv[i++] = newProgargv[ctr];
	}
    
    // Print the full command line for debugging purposes...
    if (isDebugging) {
        DLog(@"Command line passed to application:");
        int j=0;
        for(j=0; j= jvmRequired && !exactMatch) || (version == jvmRequired && exactMatch) ) {
                if (isDebugging) {
                    DLog (@"JRE version qualifies");
                }
                return @JAVA_RUNTIME;
            }
        }
    }
    @catch (NSException *exception)
    {
        DLog (@"JRE search exception: '%@'", [exception reason]);
    }

    return nil;
}

//  Having failed to find a JRE in the usual location, see if a JDK is installed
//  (probably in /Library/Java/JavaVirtualMachines). If so, return address of
//  dylib in the JRE within the JDK.
/**
 *  Searches for a JDK dylib of the specified version or later.
 */
NSString * findJDKDylib (
        int jvmRequired,
        bool isDebugging,
        bool exactMatch)
{
    @try
    {
        NSTask *task = [[NSTask alloc] init];
        [task setLaunchPath:@"/usr/libexec/java_home"];

        NSArray *args = [NSArray arrayWithObjects: @"-v", [NSString stringWithFormat:@"1.%i%@", jvmRequired, exactMatch?@"":@"+"], nil];
        [task setArguments:args];

        NSPipe *stdout = [NSPipe pipe];
        [task setStandardOutput:stdout];

        NSPipe *stderr = [NSPipe pipe];
        [task setStandardError:stderr];

        [task setStandardInput:[NSPipe pipe]];

        NSFileHandle *outHandle = [stdout fileHandleForReading];
        NSFileHandle *errHandle = [stderr fileHandleForReading];

        [task launch];
        [task waitUntilExit];
        [task release];

        NSData *data1 = [outHandle readDataToEndOfFile];
        NSData *data2 = [errHandle readDataToEndOfFile];

        NSString *outRead = [[NSString alloc] initWithData:data1
                                                    encoding:NSUTF8StringEncoding];
        NSString *errRead = [[NSString alloc] initWithData:data2
                                                    encoding:NSUTF8StringEncoding];

    //  If matching JDK not found, outRead will include something like
    //  "Unable to find any JVMs matching version "1.X"."
        if ( errRead != nil
                && [errRead rangeOfString:@"Unable"].location != NSNotFound )
        {
            if (isDebugging) {  DLog (@"No matching JDK found."); }
            return nil;
        }

        int version = 0;

        NSRange vrange = [outRead rangeOfString:@"jdk1."];
        if (vrange.location == NSNotFound) {
            // try the changed version layout from version 9
            vrange = [outRead rangeOfString:@"jdk-"];
            vrange.location += 4;
        } else {
            // otherwise remove the leading jdk
            vrange.location += 3;
        }

        if (vrange.location != NSNotFound) {
            NSString *vstring = [outRead substringFromIndex:(vrange.location)];

            vrange  = [vstring rangeOfString:@"/"];
            vstring = [vstring substringToIndex:vrange.location];

            version = extractMajorVersion(vstring);

            if (isDebugging) {
                DLog (@"Found a Java %@ JDK", vstring);
                DLog (@"Looks like major version %d", extractMajorVersion(vstring));
            }
        }

        if ( (version >= jvmRequired && !exactMatch) || (version == jvmRequired && exactMatch) ) {
            if (isDebugging) {
                DLog (@"JDK version qualifies");
            }

            NSString *outread2 = [outRead stringByTrimmingCharactersInSet:[NSCharacterSet
                                                            whitespaceAndNewlineCharacterSet]];

        //  Return location where LIBJLI_DY_LIB is located. Note that the path was
        //  shortemed between JDK 8 and JDK 9.
            if (version > 8) {
                return outread2;
            }
            else {
                return [outread2 stringByAppendingPathComponent:@"jre"];
            }
        }
    }
    @catch (NSException *exception)
    {
        DLog (@"JDK search exception: '%@'", [exception reason]);
    }

    return nil;
}


/**
 *  Extract the Java major version number from a string. We expect the input
 *  to look like either either "1.X", "1.X.Y_ZZ" or "X.Y.ZZ", and the
 *  returned result will be the integral value of X. Any failure to parse the
 *  string will return 0.
 */
int extractMajorVersion (NSString *vstring) {
  if (vstring == nil) { return 0; }
  
  //  Expecting either a java version of form 1.X, 1.X.Y_ZZ or jdk1.X.Y_ZZ.
  //  Strip everything from start and ending that aren't part of the version number
  NSCharacterSet* nonDigits = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet];
  vstring = [vstring stringByTrimmingCharactersInSet:nonDigits];
  
  if([vstring hasPrefix:@"1."]) {  // this is the version < 9 layout. Remove the leading "1."
    vstring = [vstring substringFromIndex:2];
  }
  
  // the next integer token should be the major version, so read everything up to the first decimal point, if any
  NSUInteger versionEndLoc = [vstring rangeOfString:@"."].location;
  if (versionEndLoc != NSNotFound) {
    vstring = [vstring substringToIndex:versionEndLoc];
  }
  
  return [vstring intValue];
}


NSString * convertRelativeFilePath(NSString * path) {
    return [path stringByStandardizingPath];
}

NSString * addDirectoryToSystemArguments(NSUInteger searchPath, NSSearchPathDomainMask domainMask, 
		NSString *systemProperty, NSMutableArray *systemArguments) {
	NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath,domainMask, YES);
    if ([paths count] > 0) {
		NSString *basePath = [paths objectAtIndex:0];
		NSString *directory = [NSString stringWithFormat:@"-D%@=%@", systemProperty, basePath];
		[systemArguments addObject:directory];
		return basePath;
    }
    return nil;
}

void addModifierFlagToSystemArguments(NSEventModifierFlags mask, NSString *systemProperty, NSEventModifierFlags modifierFlags, NSMutableArray *systemArguments) {
	NSString *modifierFlagValue = (modifierFlags & mask) ? @"true" : @"false";
	NSString *modifierFlagVar = [NSString stringWithFormat:@"-D%@=%@", systemProperty, modifierFlagValue];
	[systemArguments addObject:modifierFlagVar];
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy