/* -*- 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
* 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
* 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");
}
}