See More

/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2012-23 The Processing Foundation Copyright (c) 2008-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.app; import com.sun.jna.platform.FileUtils; import processing.app.platform.DefaultPlatform; import processing.core.PApplet; import processing.core.PConstants; import processing.data.StringDict; import java.io.File; import java.io.IOException; import java.lang.management.ManagementFactory; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class Platform extends processing.utils.Platform { static DefaultPlatform inst; /* static Map platformNames = new HashMap<>(); static { platformNames.put(PConstants.WINDOWS, "windows"); //$NON-NLS-1$ platformNames.put(PConstants.MACOS, "macos"); //$NON-NLS-1$ platformNames.put(PConstants.LINUX, "linux"); //$NON-NLS-1$ } */ // TODO only used in one place, probably overkill for this to be a map static Map platformIndices = new HashMap<>(); static { platformIndices.put("windows", PConstants.WINDOWS); //$NON-NLS-1$ platformIndices.put("macos", PConstants.MACOS); //$NON-NLS-1$ platformIndices.put("linux", PConstants.LINUX); //$NON-NLS-1$ } /** How many bits this machine is */ static int nativeBits; static { nativeBits = 32; // perhaps start with 32 String bits = System.getProperty("sun.arch.data.model"); //$NON-NLS-1$ if (bits != null) { if (bits.equals("64")) { //$NON-NLS-1$ nativeBits = 64; } } else { // if some other strange vm, maybe try this instead if (System.getProperty("java.vm.name").contains("64")) { //$NON-NLS-1$ //$NON-NLS-2$ nativeBits = 64; } } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static public boolean isAvailable() { return inst != null; } static { init(); } static public void init() { try { // Start with DefaultPlatform, but try to upgrade to a known platform final String packageName = DefaultPlatform.class.getPackageName(); Class> platformClass = Class.forName(packageName + ".DefaultPlatform"); if (Platform.isMacOS()) { platformClass = Class.forName(packageName + ".MacPlatform"); } else if (Platform.isWindows()) { platformClass = Class.forName(packageName + ".WindowsPlatform"); } else if (Platform.isLinux()) { platformClass = Class.forName(packageName + ".LinuxPlatform"); } inst = (DefaultPlatform) platformClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { Messages.showError("Problem Setting the Platform", "An unknown error occurred while trying to load\n" + "platform-specific code for your machine.", e); } // Fix the issue where `java.home` points to the JRE instead of the JDK. processing/processing4#1163 System.setProperty("java.home", getJavaHome().getAbsolutePath()); } static public void initBase(Base base) throws Exception { inst.initBase(base); } static public void setLookAndFeel() throws Exception { inst.setLookAndFeel(); } static public void setInterfaceZoom() throws Exception { inst.setInterfaceZoom(); } static public float getSystemZoom() { return inst == null ? 1 : inst.getSystemZoom(); } static public File getDefaultSketchbookFolder() throws Exception { return inst.getDefaultSketchbookFolder(); } static public void saveLanguage(String languageCode) { inst.saveLanguage(languageCode); } /** * Implements the cross-platform headache of opening URLs. *

* Since 2.0a8, this requires the parameter to be an actual URL, * meaning that you can't send it a file:// path without a prefix. * It also just calls into Platform, which now uses java.awt.Desktop * (where possible). The URL must also be properly URL-encoded. */ static public void openURL(String url) { try { inst.openURL(url); } catch (Exception e) { Messages.showWarning("Problem Opening URL", "Could not open the URL\n" + url, e); } } /** * Used to determine whether to disable the "Show Sketch Folder" option. * @return true If a means of opening a folder is known to be available. */ static public boolean openFolderAvailable() { return inst.openFolderAvailable(); } /** * Implements the other cross-platform headache of opening * a folder in the machine's native file browser. */ static public void openFolder(File file) { try { inst.openFolder(file); } catch (Exception e) { Messages.showWarning("Problem Opening Folder", "Could not open the folder\n" + file.getAbsolutePath(), e); } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . // /** // * Return whether sketches will run as 32- or 64-bits based // * on the JVM that's in use. // */ // static public int getNativeBits() { // return nativeBits; // } /** * Return the value of the os.arch property */ static public String getNativeArch() { // This will return "arm" for 32-bit ARM on Linux, // and "aarch64" for 64-bit ARM on Linux (rpi) and Apple Silicon // (the latter only when using a native 64-bit ARM VM on macOS, // which as of 4.0 alpha 5 is not being used b/c of missing libs). return System.getProperty("os.arch"); } static public String getVariant() { return getName() + "-" + getNativeArch(); } static StringDict supportedVariants = new StringDict(new String[][] { { "macos-x86_64", "macOS (Intel 64-bit)" }, { "macos-aarch64", "macOS (Apple Silicon)" }, { "windows-amd64", "Windows (Intel 64-bit)" }, { "linux-amd64", "Linux (Intel 64-bit)" }, { "linux-arm", "Linux (Raspberry Pi 32-bit)" }, { "linux-aarch64", "Linux (Raspberry Pi 64-bit)" } }); /** * List of variants that are supported by this release of the PDE. */ static public StringDict getSupportedVariants() { return supportedVariants; } // /* // * Return a string that identifies the variant of a platform // * e.g. "32" or "64" on Intel // */ /* static public String getVariant() { return getVariant(PApplet.platform, getNativeArch(), getNativeBits()); } static public String getVariant(int platform, String arch, int bits) { if (platform == PConstants.LINUX && bits == 32 && "arm".equals(Platform.getNativeArch())) { return "armv6hf"; // assume armv6hf } else if (platform == PConstants.LINUX && bits == 64 && "aarch64".equals(Platform.getNativeArch())) { return "arm64"; } return Integer.toString(bits); // 32 or 64 } */ /** * Returns one of macos, windows, linux, or other. * Changed in 4.0b4 to return macos instead of macosx. * Only used inside processing.app.java. */ static public String getName() { return PConstants.platformNames[PApplet.platform]; } static public String getPrettyName() { return supportedVariants.get(getVariant()); } // /** // * Map a platform constant to its name. // * @param which PConstants.WINDOWS, PConstants.MACOSX, PConstants.LINUX // * @return one of "windows", "macosx", or "linux" // */ // static public String getName(int which) { // return platformNames.get(which); // } static public int getIndex(String platformName) { // if this has os.arch at the end, remove it int index = platformName.indexOf('-'); if (index != -1) { platformName = platformName.substring(0, index); } return platformIndices.getOrDefault(platformName, -1); } // These were changed to no longer rely on PApplet and PConstants because // of conflicts that could happen with older versions of core.jar, where // the MACOSX constant would instead read as the LINUX constant. // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . static protected File processingRoot; /** * Get reference to a file adjacent to the executable on Windows and Linux, * or inside Contents/Resources/Java on Mac OS X. This will return the local * JRE location, *whether or not it is the active JRE*. * @deprecated start using the build in JAR Resources system instead. */ @Deprecated static public File getContentFile(String name) { if (processingRoot == null) { var resourcesDir = System.getProperty("compose.application.resources.dir"); if(resourcesDir != null) { var directory = new File(resourcesDir); if(directory.exists()){ return new File(directory, name); } } // Get the path to the .jar file that contains Base.class URL pathURL = Base.class.getProtectionDomain().getCodeSource().getLocation(); // Decode URL String decodedPath; try { decodedPath = pathURL.toURI().getSchemeSpecificPart(); } catch (URISyntaxException e) { Messages.showError("Missing File", "Could not access a required file:\n" + "" + name + "\n" + "You may need to reinstall Processing.", e); return null; } if (decodedPath.contains("/app/bin")) { // This means we're in Eclipse final File build = new File(decodedPath, "../../build").getAbsoluteFile(); if (Platform.isMacOS()) { processingRoot = new File(build, "macos/work/Processing.app/Contents/Java"); } else if (Platform.isWindows()) { processingRoot = new File(build, "windows/work"); } else if (Platform.isLinux()) { processingRoot = new File(build, "linux/work"); } } else { // The .jar file will be in the lib folder File jarFolder = new File(decodedPath).getParentFile(); if (jarFolder.getName().equals("lib")) { // The main Processing installation directory. // This works for Windows, Linux, and Apple's Java 6 on OS X. processingRoot = jarFolder.getParentFile(); } else if (Platform.isMacOS()) { // This works for Java 8 on OS X. We don't have things inside a 'lib' // folder on OS X. Adding it caused more problems than it was worth. processingRoot = jarFolder; } if (processingRoot == null || !processingRoot.exists()) { // Try working directory instead (user.dir, different from user.home) System.err.println("Could not find lib folder via " + jarFolder.getAbsolutePath() + ", switching to user.dir"); processingRoot = new File(""); // resolves to "user.dir" } } } return new File(processingRoot, name); } static public File getJavaHome() { // Get the build in JDK location from the Jetpack Compose resources var resourcesDir = System.getProperty("compose.application.resources.dir"); if(resourcesDir != null) { var jdkFolder = new File(resourcesDir,"jdk"); if(jdkFolder.exists()){ return jdkFolder; } } // If the JDK is set in the environment, use that. var home = System.getProperty("java.home"); if(home != null){ return new File(home); } // Otherwise try to use the Ant embedded JDK. if (Platform.isMacOS()) { //return "Contents/PlugIns/jdk1.7.0_40.jdk/Contents/Home/jre/bin/java"; File[] plugins = getContentFile("../PlugIns").listFiles((dir, name) -> dir.isDirectory() && name.contains("jdk") && !name.startsWith(".")); return new File(plugins[0], "Contents/Home"); } // On all other platforms, it's the 'java' folder adjacent to Processing return getContentFile("java"); } /** Get the path to the embedded Java executable. */ static public String getJavaPath() { String javaPath = "bin/java" + (Platform.isWindows() ? ".exe" : ""); File javaFile = new File(getJavaHome(), javaPath); try { return javaFile.getCanonicalPath(); } catch (IOException e) { return javaFile.getAbsolutePath(); } } static protected File getProcessingApp() { File appFile; if (Platform.isMacOS()) { // walks up from Processing.app/Contents/Java to Processing.app // (or whatever the user has renamed it to) appFile = getContentFile("../.."); } else if (Platform.isWindows()) { appFile = getContentFile("processing.exe"); } else { appFile = getContentFile("processing"); } try { return appFile.getCanonicalFile(); } catch (Exception e) { e.printStackTrace(); } return null; } // Not great, shows the crusty Duke icon in the dock. // Better to just re-launch the .exe instead. // Hacked up from this code. static private void restartJavaApplication() { // System.out.println("java path: " + javaPath); // String java = System.getProperty("java.home") + "/bin/java"; // Tested and working with JDK 17 [fry 230122] // System.out.println("sun java command: " + System.getProperty("sun.java.command")); // System.out.println("class path: " + System.getProperty("java.class.path")); List cmd = new ArrayList<>(); // Add the path to the current java binary cmd.add(getJavaPath()); // Get all the VM arguments that are currently in use List vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); // Add all the arguments we're using now, except for -agentlib for (String arg : vmArguments) { if (!arg.contains("-agentlib")) { cmd.add(arg); } } // Does not work for .jar files, should this be used in a more general way cmd.add("-cp"); cmd.add(System.getProperty("java.class.path")); // Finally, add the class that was used to launch the app // (in our case, this is the Processing splash screen) String javaCommand = System.getProperty("sun.java.command"); String[] splitCommand = PApplet.split(javaCommand, ' '); // if (splitCommand.length > 1) { // try { // Util.saveFile(javaCommand, PApplet.desktopFile("arrrrrghs.txt")); // } catch (IOException e) { // throw new RuntimeException(e); // } // } cmd.add(splitCommand[0]); // should be the main class name ProcessBuilder builder = new ProcessBuilder(cmd); /* StringBuffer vmArgsOneLine = new StringBuffer(); for (String arg : vmArguments) { // if it's the agent argument : we ignore it otherwise the // address of the old application and the new one will be in conflict if (!arg.contains("-agentlib")) { vmArgsOneLine.append(arg); vmArgsOneLine.append(" "); } } // init the command to execute, add the vm args final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine); // program main and program arguments (be careful a sun property. might not be supported by all JVM) String[] mainCommand = System.getProperty("sun.java.command").split(" "); // program main is a jar if (mainCommand[0].endsWith(".jar")) { // if it's a jar, add -jar mainJar cmd.append("-jar " + new File(mainCommand[0]).getPath()); } else { // else it's a .class, add the classpath and mainClass cmd.append("-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0]); } // finally add program arguments for (int i = 1; i < mainCommand.length; i++) { cmd.append(" "); cmd.append(mainCommand[i]); } */ // execute the command in a shutdown hook, to be sure that all the // resources have been disposed before restarting the application Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { // System.out.println(new StringList(cmd).join(" ")); // Runtime.getRuntime().exec(cmd.toArray(new String[0])); builder.start(); } catch (IOException e) { e.printStackTrace(); } })); System.exit(0); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * Delete a file or directory in a platform-specific manner. Removes a File * object (a file or directory) from the system by placing it in the Trash * or Recycle Bin (if available) or simply deleting it (if not). * Also tries to find a suitable Trash location on Linux. *

* When the file/folder is on another file system, it may simply be removed * immediately, without additional warning. So only use this if you want to, * you know, "delete" the subject in question. * * @param file the victim (a directory or individual file) * @return true if all ends well * @throws IOException what went wrong */ static public boolean deleteFile(File file) throws IOException { try { FileUtils fu = FileUtils.getInstance(); if (fu.hasTrash()) { fu.moveToTrash(file); return true; } } catch (Throwable t) { // On macOS getting NoClassDefFoundError inside JNA on Big Sur. // (Can't find com.sun.jna.platform.mac.MacFileUtils$FileManager) // Just adding a catch-all here so that it does the fall-through below. System.err.println(t.getMessage()); } if (file.isDirectory()) { Util.removeDir(file); return true; } else { return file.delete(); } } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** * These methods were refactored to use the Preferences system instead of * actual environment variables, since modifying environment variables at runtime * proved problematic. This approach provides similar functionality * while being compatible with various platforms and execution environments. * * This abstraction maintains a consistent API for environment-like variable storage * while implementing it differently under the hood to work around runtime limitations. */ static public void setenv(String variable, String value) { Preferences.set(variable, value); } static public String getenv(String variable) { return Preferences.get(variable); } static public int unsetenv(String variable) { throw new RuntimeException("unsetenv() not yet implemented"); } }