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)