From a717dfb923010620c9dbe4c16bf5891c853bdb2f Mon Sep 17 00:00:00 2001 From: zzz <zzz@i2pmail.org> Date: Wed, 9 Mar 2022 05:21:58 -0500 Subject: [PATCH] DTG: Add new ExternalMain class for app context use that does not require router.jar and rework other classes as necessary --- .../src/net/i2p/desktopgui/ExternalMain.java | 227 ++++++++++++++++++ .../i2p/desktopgui/ExternalTrayManager.java | 4 +- .../i2p/desktopgui/InternalTrayManager.java | 4 +- .../src/net/i2p/desktopgui/Main.java | 4 +- .../src/net/i2p/desktopgui/TrayManager.java | 4 +- 5 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 apps/desktopgui/src/net/i2p/desktopgui/ExternalMain.java diff --git a/apps/desktopgui/src/net/i2p/desktopgui/ExternalMain.java b/apps/desktopgui/src/net/i2p/desktopgui/ExternalMain.java new file mode 100644 index 0000000000..7d44c50103 --- /dev/null +++ b/apps/desktopgui/src/net/i2p/desktopgui/ExternalMain.java @@ -0,0 +1,227 @@ +package net.i2p.desktopgui; + +import java.awt.Image; +import java.awt.SystemTray; +import java.awt.Toolkit; +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; + +import javax.swing.SwingUtilities; + +import net.i2p.I2PAppContext; +import net.i2p.app.ClientAppManager; +import net.i2p.app.ClientApp; +import net.i2p.app.ClientAppState; +import net.i2p.app.NotificationService; +import net.i2p.util.Log; +import net.i2p.util.SystemVersion; + +/** + * A simplified Main that does not require router.jar, for App Context only. + * Invokes ExternalTrayManager only. + * No state tracking, ClientAppManager doesn't care. + * + * @since 0.9.54 + */ +public class ExternalMain implements ClientApp, NotificationService { + + private final I2PAppContext _appContext; + private final ClientAppManager _mgr; + private final Log log; + private TrayManager _trayManager; + + private static final String PROP_SWING = "desktopgui.swing"; + + public ExternalMain(I2PAppContext ctx, ClientAppManager mgr, String args[]) { + _appContext = ctx; + _mgr = mgr; + log = _appContext.logManager().getLog(ExternalMain.class); + } + + public ExternalMain() { + _appContext = I2PAppContext.getGlobalContext(); + _mgr = _appContext.clientAppManager(); + log = _appContext.logManager().getLog(ExternalMain.class); + } + + public static void main(String[] args) { + // early check so we can bail out when started via CLI + if (!SystemTray.isSupported()) { + System.err.println("SystemTray not supported"); + return; + } + ExternalMain main = new ExternalMain(); + main.beginStartup(args); + } + + /** + * Start the tray icon code (loads tray icon in the tray area). + * @throws AWTException on startup error, including systray not supported + */ + private synchronized void startUp() throws Exception { + final TrayManager trayManager; + boolean useSwingDefault = !(SystemVersion.isWindows() || SystemVersion.isMac()); + boolean useSwing = _appContext.getProperty(PROP_SWING, useSwingDefault); + _trayManager = new ExternalTrayManager(_appContext, useSwing); + _trayManager.startManager(); + if (_mgr != null) + _mgr.register(this); + } + + /** + * Main method launching the application. + * + * @param args unused + */ + private void beginStartup(String[] args) { + String headless = System.getProperty("java.awt.headless"); + boolean isHeadless = Boolean.parseBoolean(headless); + if (isHeadless) { + log.warn("Headless environment: not starting desktopgui!"); + return; + } + if (SystemVersion.isMac()) + setMacTrayIcon(); + launchForeverLoop(); + + // We'll be doing GUI work, so let's stay in the event dispatcher thread. + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + try { + startUp(); + } catch(Exception e) { + log.error("Failed while running desktopgui!", e); + } + + } + + }); + } + + /** + * Unless we do this, when we start DesktopGUI we get a Java coffee cup + * in the tray. + * + * Based on code from https://gist.github.com/bchapuis/1562406 , no apparent license. + * See also https://stackoverflow.com/questions/6006173/how-do-you-change-the-dock-icon-of-a-java-program + * + * TODO, if we wanted to add our own menu, see + * https://stackoverflow.com/questions/1319805/java-os-x-dock-menu + * + * TODO, if we want to make it bounce, see + * https://stackoverflow.com/questions/15079783/how-to-make-my-app-icon-bounce-in-the-mac-dock + * + * TODO, if we want to handle Quit, see + * https://nakkaya.com/2009/04/19/java-osx-integration/ + * + * @since 0.9.33 + */ + @SuppressWarnings("unchecked") + private void setMacTrayIcon() { + File f = new File(_appContext.getBaseDir(), "docs/themes/console/images/itoopie_sm.png"); + if (!f.exists()) + return; + try { + Class util = Class.forName("com.apple.eawt.Application"); + Method getApplication = util.getMethod("getApplication", new Class[0]); + Object application = getApplication.invoke(util); + Class params[] = new Class[1]; + params[0] = Image.class; + Method setDockIconImage = util.getMethod("setDockIconImage", params); + URL url = f.toURI().toURL(); + Image image = Toolkit.getDefaultToolkit().getImage(url); + setDockIconImage.invoke(application, image); + } catch (Exception e) { + if (log.shouldWarn()) + log.warn("Can't set OSX Dock icon", e); + } + } + + /** + * Avoids the app terminating because no Window is opened anymore. + * More info: http://java.sun.com/javase/6/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown + */ + private static void launchForeverLoop() { + Runnable r = new Runnable() { + public void run() { + try { + Object o = new Object(); + synchronized (o) { + o.wait(); + } + } catch (InterruptedException ie) { + } + } + }; + Thread t = new Thread(r, "DesktopGUI spinner"); + t.setDaemon(false); + t.start(); + } + + /////// NotificationService methods + + /** + * Send a notification to the user. + * + * @param source unsupported + * @param category unsupported + * @param priority unsupported + * @param title for the popup, translated + * @param message translated + * @param path unsupported + * @return 0, or -1 on failure + */ + public int notify(String source, String category, int priority, String title, String message, String path) { + TrayManager tm = _trayManager; + if (tm == null) + return -1; + return tm.displayMessage(priority, title, message, path); + } + + /** + * Cancel a notification if possible. + * Unsupported. + * + * @return false always + */ + public boolean cancel(int id) { + return false; + } + + /** + * Update the text of a notification if possible. + * Unsupported. + * + * @return false always + */ + public boolean update(int id, String title, String message, String path) { + return false; + } + + /////// ClientApp methods + + public synchronized void startup() { + beginStartup(null); + } + + public synchronized void shutdown(String[] args) { + if (_trayManager != null) + _trayManager.stopManager(); + } + + public ClientAppState getState() { + return ClientAppState.INITIALIZED; + } + + public String getName() { + return "desktopgui"; + } + + public String getDisplayName() { + return "Desktop GUI"; + } + + /////// end ClientApp methods +} diff --git a/apps/desktopgui/src/net/i2p/desktopgui/ExternalTrayManager.java b/apps/desktopgui/src/net/i2p/desktopgui/ExternalTrayManager.java index 5c0c0a1cc7..c9317b0678 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/ExternalTrayManager.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/ExternalTrayManager.java @@ -22,8 +22,8 @@ import net.i2p.desktopgui.router.RouterManager; */ class ExternalTrayManager extends TrayManager { - public ExternalTrayManager(I2PAppContext ctx, Main main, boolean useSwing) { - super(ctx, main, useSwing); + public ExternalTrayManager(I2PAppContext ctx, boolean useSwing) { + super(ctx, useSwing); } public PopupMenu getMainMenu() { diff --git a/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java b/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java index 1a5d6c42ea..4a5afb5608 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/InternalTrayManager.java @@ -30,6 +30,7 @@ class InternalTrayManager extends TrayManager { private final RouterContext _context; private final Log log; + private final Main _main; private MenuItem _statusItem, _browserItem, _configItem, _restartItem, _stopItem, _restartHardItem, _stopHardItem, _cancelItem, _notificationItem1, _notificationItem2; @@ -42,8 +43,9 @@ class InternalTrayManager extends TrayManager { private static final String CONSOLE_BUNDLE_NAME = "net.i2p.router.web.messages"; public InternalTrayManager(RouterContext ctx, Main main, boolean useSwing) { - super(ctx, main, useSwing); + super(ctx, useSwing); _context = ctx; + _main = main; log = ctx.logManager().getLog(InternalTrayManager.class); } diff --git a/apps/desktopgui/src/net/i2p/desktopgui/Main.java b/apps/desktopgui/src/net/i2p/desktopgui/Main.java index aad835cf5e..0a7e3f9bc2 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/Main.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/Main.java @@ -57,7 +57,7 @@ public class Main implements RouterApp, NotificationService { */ public Main() { _appContext = I2PAppContext.getGlobalContext(); - if (_appContext instanceof RouterContext) + if (_appContext.isRouterContext()) _context = (RouterContext) _appContext; else _context = null; @@ -77,7 +77,7 @@ public class Main implements RouterApp, NotificationService { if (_context != null) trayManager = new InternalTrayManager(_context, this, useSwing); else - trayManager = new ExternalTrayManager(_appContext, this, useSwing); + trayManager = new ExternalTrayManager(_appContext, useSwing); trayManager.startManager(); _trayManager = trayManager; changeState(RUNNING); diff --git a/apps/desktopgui/src/net/i2p/desktopgui/TrayManager.java b/apps/desktopgui/src/net/i2p/desktopgui/TrayManager.java index 4c16c45d87..f17f8b2d04 100644 --- a/apps/desktopgui/src/net/i2p/desktopgui/TrayManager.java +++ b/apps/desktopgui/src/net/i2p/desktopgui/TrayManager.java @@ -37,7 +37,6 @@ import net.i2p.util.SystemVersion; abstract class TrayManager { protected final I2PAppContext _appContext; - protected final Main _main; protected final boolean _useSwing; ///The tray area, or null if unsupported protected SystemTray tray; @@ -55,9 +54,8 @@ abstract class TrayManager { /** * Instantiate tray manager. */ - protected TrayManager(I2PAppContext ctx, Main main, boolean useSwing) { + protected TrayManager(I2PAppContext ctx, boolean useSwing) { _appContext = ctx; - _main = main; _useSwing = useSwing; } -- GitLab