diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java
index bc11927c0..f831ac13b 100644
--- a/app/src/processing/app/Base.java
+++ b/app/src/processing/app/Base.java
@@ -600,6 +600,10 @@ public class Base {
item.addActionListener(e -> thinkDifferentExamples());
defaultFileMenu.add(item);
+ item = new JMenuItem("Restart");
+ item.addActionListener(e -> handleRestart());
+ defaultFileMenu.add(item);
+
return defaultFileMenu;
}
@@ -858,16 +862,15 @@ public class Base {
if (internalTools == null) {
internalTools = new ArrayList<>();
- initInternalTool("processing.app.tools.Archiver");
- initInternalTool("processing.app.tools.ColorSelector");
- initInternalTool("processing.app.tools.CreateFont");
+ initInternalTool(processing.app.tools.Archiver.class);
+ initInternalTool(processing.app.tools.ColorSelector.class);
+ initInternalTool(processing.app.tools.CreateFont.class);
if (Platform.isMacOS()) {
- initInternalTool("processing.app.tools.InstallCommander");
+ initInternalTool(processing.app.tools.InstallCommander.class);
}
- initInternalTool("processing.app.tools.ThemeSelector");
- //initInternalTool("processing.app.tools.UpdateTheme");
+ initInternalTool(processing.app.tools.ThemeSelector.class);
}
// Only init() these the first time they're loaded
@@ -913,9 +916,8 @@ public class Base {
}
- protected void initInternalTool(String className) {
+ protected void initInternalTool(Class> toolClass) {
try {
- Class> toolClass = Class.forName(className);
final Tool tool = (Tool)
toolClass.getDeclaredConstructor().newInstance();
@@ -1681,18 +1683,6 @@ public class Base {
}
}
- /*
- // wow, this is wrong (should only be called after the last window)
- // but also outdated, because it's instance_server.* not server.*
- // and Preferences.save() is also about restoring sketches.
-
- Preferences.unset("server.port"); //$NON-NLS-1$
- Preferences.unset("server.key"); //$NON-NLS-1$
-
- // Save out the current prefs state
- Preferences.save();
- */
-
if (defaultFileMenu == null) {
if (preventQuit) {
// need to close this editor, ever so temporarily
@@ -1724,7 +1714,10 @@ public class Base {
/**
- * Handler for File → Quit.
+ * Handler for File → Quit. Note that this is *only* for the
+ * File menu. On macOS, it will not call System.exit() because the
+ * application will handle that. If calling this from elsewhere,
+ * you'll need a System.exit() call on macOS.
* @return false if canceled, true otherwise.
*/
public boolean handleQuit() {
@@ -1777,6 +1770,46 @@ public class Base {
}
+ public void handleRestart() {
+ File app = Platform.getProcessingApp();
+ System.out.println(app);
+ if (app.exists()) {
+ if (handleQuitEach()) { // only if everything saved
+ SingleInstance.clearRunning();
+
+ // Launch on quit
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ try {
+ //Runtime.getRuntime().exec(app.getAbsolutePath());
+ System.out.println("launching");
+ Process p;
+ if (Platform.isMacOS()) {
+ p = Runtime.getRuntime().exec(new String[]{
+ "open", "-n", "-a", app.getAbsolutePath()
+ });
+ } else {
+ p = PApplet.launch(app.getAbsolutePath());
+ }
+ System.out.println("launched with result " + p.waitFor());
+ System.out.flush();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }));
+ handleQuit();
+ // handleQuit() does not call System.exit() on macOS
+ if (Platform.isMacOS()) {
+ System.exit(0);
+ }
+ }
+ } else {
+ Messages.showWarning("Cannot Restart",
+ "Cannot automatically restart because the Processing\n" +
+ "application has been renamed. Please quit and then restart manually.");
+ }
+ }
+
+
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java
index d6c73be9e..d6f472abe 100644
--- a/app/src/processing/app/Platform.java
+++ b/app/src/processing/app/Platform.java
@@ -3,7 +3,7 @@
/*
Part of the Processing project - http://processing.org
- Copyright (c) 2012-20 The Processing Foundation
+ 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
@@ -25,9 +25,12 @@ package processing.app;
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;
import com.sun.jna.platform.FileUtils;
@@ -404,6 +407,114 @@ public class Platform {
}
+ 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);
+ }
+
+
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
diff --git a/app/src/processing/app/SingleInstance.java b/app/src/processing/app/SingleInstance.java
index 405f6720c..f900d9e8f 100644
--- a/app/src/processing/app/SingleInstance.java
+++ b/app/src/processing/app/SingleInstance.java
@@ -54,6 +54,15 @@ public class SingleInstance {
}
+ /**
+ * Disable briefly for Processing to restart itself.
+ */
+ static public void clearRunning() {
+ Preferences.unset(SERVER_PORT);
+ Preferences.save();
+ }
+
+
static void startServer(final Base base) {
try {
Messages.log("Opening SingleInstance socket");
diff --git a/todo.txt b/todo.txt
index e214d19cf..6922f3e01 100755
--- a/todo.txt
+++ b/todo.txt
@@ -29,6 +29,11 @@ _ update console.scrollbar.thumb.rollover.color and console.scrollbar.thumb.pres
_ currently just using .enabled.color because they weren't in ColorSet
+_ remove temporary 'restart' menu before release
+
+_ implement automatic updates?
+_ especially with fixes to updates on startup...
+
_ export to IntelliJ? how tricky?
_ just copy jars to /lib?
_ point to binaries in /Applications/Processing.app? (no)