From 6599989e82adf89e33ee96a573b51b4d02ad599a Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 27 Jun 2017 00:19:38 +0200 Subject: [PATCH] Code polish/cleanup. Moved "autodetect" tray logic to it's own method. --- src/dorkbox/systemTray/SystemTray.java | 601 +++++++++++++------------ 1 file changed, 304 insertions(+), 297 deletions(-) diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index af0cfaf..3affe13 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -42,12 +42,12 @@ import com.sun.java.swing.plaf.gtk.GTKLookAndFeel; import dorkbox.systemTray.jna.linux.AppIndicator; import dorkbox.systemTray.jna.linux.Gtk; -import dorkbox.systemTray.nativeUI.NativeUI; import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray; import dorkbox.systemTray.nativeUI._AwtTray; import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray; import dorkbox.systemTray.swingUI.SwingUIFactory; import dorkbox.systemTray.swingUI._SwingTray; +import dorkbox.systemTray.util.ImageResizeUtil; import dorkbox.systemTray.util.JavaFX; import dorkbox.systemTray.util.SizeAndScalingUtil; import dorkbox.systemTray.util.Swt; @@ -210,8 +210,189 @@ class SystemTray { return null; } + // This will return the default "autodetect" tray type + private static + Class getAutoDetectTrayType() { + if (OS.isWindows()) { + try { + return selectType(TrayType.Swing); + } catch (Throwable e) { + logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager.", e); + } + } + else if (OS.isMacOsX()) { + // macos can ONLY use the AWT if you want it to follow the L&F of the OS. It is the default. + try { + return selectType(TrayType.AWT); + } catch (Throwable e) { + logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager."); + } + } + else if ((OS.isLinux() || OS.isUnix())) { + // see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running + + // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python. + // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py + + // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least + OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get(); + + if (DEBUG) { + logger.debug("Currently using the '{}' desktop environment", de); + } + + switch (de) { + case Gnome: { + // check other DE / OS combos that are based on gnome + String GDM = System.getenv("GDMSESSION"); + + if (DEBUG) { + logger.debug("Currently using the '{}' session type", GDM); + } + + if ("gnome".equalsIgnoreCase(GDM)) { + Tray.usingGnome = true; + + // are we fedora? If so, what version? + // now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon + if (OSUtil.Linux.isFedora()) { + if (DEBUG) { + logger.debug("Running Fedora"); + } + + // 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this) + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if (OSUtil.Linux.isUbuntu()) { + // so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works. + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if (OSUtil.Unix.isFreeBSD()) { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else { + // arch likely will have problems unless the correct/appropriate libraries are installed. + return selectTypeQuietly(TrayType.AppIndicator); + } + } + else if ("cinnamon".equalsIgnoreCase(GDM)) { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if ("default".equalsIgnoreCase(GDM)) { + // this can be gnome3 on debian + + if (OSUtil.Linux.isDebian()) { + // note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs) + logger.warn("Debian with Gnome detected. SystemTray support is not known to work."); + } + + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if ("gnome-classic".equalsIgnoreCase(GDM)) { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if ("gnome-fallback".equalsIgnoreCase(GDM)) { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else if ("ubuntu".equalsIgnoreCase(GDM)) { + return selectTypeQuietly(TrayType.AppIndicator); + } + break; + } + case KDE: { + if (OSUtil.Linux.isFedora()) { + // Fedora KDE requires GtkStatusIcon + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + else { + // kde (at least, plasma 5.5.6) requires appindicator + return selectTypeQuietly(TrayType.AppIndicator); + } + + // kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that + } + case Unity: { + // Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell". + return selectTypeQuietly(TrayType.AppIndicator); + } + case XFCE: { + // NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted. + // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/ + // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25 + + // so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + case LXDE: { + return selectTypeQuietly(TrayType.GtkStatusIcon); + } + case Pantheon: { + // elementaryOS. It only supports appindicator (not gtkstatusicon) + // http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/view/head:/sample/SampleIndicator.vala + + // ElementaryOS shows the checkbox on the right, everyone else is on the left. + // With eOS, we CANNOT show the spacer image. It does not work. + return selectTypeQuietly(TrayType.AppIndicator); + } + } + + // Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators) + BufferedReader bin = null; + try { + // the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator), + // is to look through all /proc//status, and first line should be Name:\tindicator-appli + File proc = new File("/proc"); + File[] listFiles = proc.listFiles(); + if (listFiles != null) { + for (File procs : listFiles) { + String name = procs.getName(); + + if (!Character.isDigit(name.charAt(0))) { + continue; + } + + File status = new File(procs, "status"); + if (!status.canRead()) { + continue; + } + + try { + bin = new BufferedReader(new FileReader(status)); + String readLine = bin.readLine(); + + if (readLine != null && readLine.contains("indicator-app")) { + // make sure we can also load the library (it might be the wrong version) + try { + return selectType(TrayType.AppIndicator); + } catch (Exception e) { + if (DEBUG) { + logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", + e); + } + else { + logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK"); + } + } + break; + } + } finally { + IO.closeQuietly(bin); + } + } + } + } catch (Throwable e) { + if (DEBUG) { + logger.error("Error detecting gnome version", e); + } + } + } + + return null; + } + @SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"}) - private static void init() { + private static + void init() { if (systemTray != null) { return; } @@ -225,8 +406,30 @@ class SystemTray { // } // } - if (OS.isMacOsX()) { - // cannot mix AWT and JavaFX for MacOSX in java7 (fixed in java8) without special stuff. + // no tray in a headless environment + if (GraphicsEnvironment.isHeadless()) { + logger.error("Cannot use the SystemTray in a headless environment"); + + systemTrayMenu = null; + systemTray = null; + return; + } + + boolean isNix = OS.isLinux() || OS.isUnix(); + + // Windows can ONLY use Swing (non-native) - AWT/native looks absolutely horrid and is not an option + // OSx can use Swing (non-native) or AWT (native) . + // Linux can use Swing (non-native), AWT (native), GtkStatusIcon (native), or AppIndicator (native) + if (OS.isWindows()) { + if (FORCE_TRAY_TYPE != TrayType.AutoDetect && FORCE_TRAY_TYPE != TrayType.Swing) { + // windows MUST use swing only! + FORCE_TRAY_TYPE = TrayType.AutoDetect; + + logger.warn("Windows cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Swing"); + } + } + else if (OS.isMacOsX()) { + // cannot mix Swing/AWT and JavaFX for MacOSX in java7 (fixed in java8) without special stuff. // https://bugs.openjdk.java.net/browse/JDK-8116017 // https://bugs.openjdk.java.net/browse/JDK-8118714 if (isJavaFxLoaded && OS.javaVersion <= 7 && !System.getProperty("javafx.macosx.embedded", "false").equals("true")) { @@ -247,35 +450,32 @@ class SystemTray { // cannot mix Swing and SWT on MacOSX (for all versions of java) so we force native menus instead, which work just fine with SWT // http://mail.openjdk.java.net/pipermail/bsd-port-dev/2008-December/000173.html - } + if (isSwtLoaded && FORCE_TRAY_TYPE == TrayType.Swing) { + if (AUTO_FIX_INCONSISTENCIES) { + logger.warn("Unable to load Swing + SWT (for all versions of Java). Using the AWT Tray type instead."); - // no tray in a headless environment - if (GraphicsEnvironment.isHeadless()) { - logger.error("Cannot use the SystemTray in a headless environment"); + FORCE_TRAY_TYPE = TrayType.AWT; + } + else { + logger.error("Unable to load Swing + SWT (for all versions of Java). " + + "Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.\""); - systemTrayMenu = null; - systemTray = null; - return; - } - - // Windows can ONLY use Swing (non-native) - AWT/native looks absolutely horrid and is not an option - // OSx can use Swing (non-native) or AWT (native) . - // Linux can use Swing (non-native), AWT (native), GtkStatusIcon (native), or AppIndicator (native) - if (OS.isWindows()) { - if (FORCE_TRAY_TYPE != TrayType.AutoDetect && FORCE_TRAY_TYPE != TrayType.Swing) { - // windows MUST use swing only! - FORCE_TRAY_TYPE = TrayType.AutoDetect; - logger.warn("Windows cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Swing"); + systemTrayMenu = null; + systemTray = null; + return; + } } - } - else if (OS.isMacOsX()) { + if (FORCE_TRAY_TYPE != TrayType.AutoDetect && FORCE_TRAY_TYPE != TrayType.Swing && FORCE_TRAY_TYPE != TrayType.AWT) { - // MacOsX MUST use swing (and AWT) only! + // MacOsX can only use swing and AWT FORCE_TRAY_TYPE = TrayType.AutoDetect; - logger.warn("MacOS cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Awt"); + + logger.warn("MacOS cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to the AWT Tray type instead."); } } - else if (OS.isLinux() || OS.isUnix()) { + else if (isNix) { + // linux/unix can use all of the tray types + // NOTE: if the UI uses the 'getSystemLookAndFeelClassName' and is on Linux, this will cause GTK2 to get loaded first, // which will cause conflicts if one tries to use GTK3 if (!FORCE_GTK2 && !isJavaFxLoaded && !isSwtLoaded) { @@ -300,8 +500,9 @@ class SystemTray { if (AUTO_FIX_INCONSISTENCIES) { // we must use GTK2 because Swing is configured to use GTK2 - logger.warn("Forcing GTK2 because the Swing UIManager is GTK2"); FORCE_GTK2 = true; + + logger.warn("Forcing GTK2 because the Swing UIManager is GTK2"); } else { logger.error("Unable to use the SystemTray when the Swing UIManager is configured to use the native L&F, which " + "uses GTK2. This is incompatible with GTK3. " + @@ -309,6 +510,7 @@ class SystemTray { systemTrayMenu = null; systemTray = null; + return; } } } @@ -335,8 +537,9 @@ class SystemTray { return; } else if (!isSwt_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) { // we must use GTK2, because SWT is GTK2 - logger.warn("Forcing GTK2 because SWT is GTK2"); FORCE_GTK2 = true; + + logger.warn("Forcing GTK2 because SWT is GTK2"); } } else if (isJavaFxLoaded) { @@ -349,11 +552,12 @@ class SystemTray { if (isJava_GTK3_Possible && FORCE_GTK2) { // if we are java9, then we can change it -- otherwise we cannot. if (OS.javaVersion == 9 && AUTO_FIX_INCONSISTENCIES) { + FORCE_GTK2 = false; + logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " + "configured to use GTK2. Please configure JavaFX to use GTK2 (via `System.setProperty(\"jdk.gtk.version\", \"3\");`) " + "before JavaFX is initialized, or set `SystemTray.FORCE_GTK2=false;` Undoing `FORCE_GTK2`."); - FORCE_GTK2 = false; } else { logger.error("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is configured to use " + "GTK2. Please set `SystemTray.FORCE_GTK2=false;` if that is not possible then it will not work."); @@ -364,13 +568,14 @@ class SystemTray { } } else if (!isJava_GTK3_Possible && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) { // we must use GTK2, because JavaFX is GTK2 - logger.warn("Forcing GTK2 because JavaFX is GTK2"); FORCE_GTK2 = true; + + logger.warn("Forcing GTK2 because JavaFX is GTK2"); } } } - Class trayType = null; + if (DEBUG) { logger.debug("OS: {}", System.getProperty("os.name")); @@ -400,57 +605,24 @@ class SystemTray { // this has to happen BEFORE any sort of swing system tray stuff is accessed - if (OS.isWindows()) { - try { - trayType = selectType(TrayType.Swing); - } catch (Throwable e) { - logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager."); - } + Class trayType; + if (SystemTray.FORCE_TRAY_TYPE == TrayType.AutoDetect) { + trayType = getAutoDetectTrayType(); + } else { + trayType = selectTypeQuietly(SystemTray.FORCE_TRAY_TYPE); } - else if (OS.isMacOsX()) { - if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) { - // macos can ONLY use the AWT tray indicator, if you want it to follow the L&F of the OS. If we force a specific type, - // then allow that. - try { - trayType = selectType(TrayType.AWT); - } catch (Throwable e) { - logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager."); - } - } - } - else if ((OS.isLinux() || OS.isUnix())) { - // see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running - // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python. - // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py - // this can never be swing - // don't check for SWING type at this spot, it is done elsewhere. - if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) { - trayType = selectTypeQuietly(SystemTray.FORCE_TRAY_TYPE); - } - - if (SystemTray.FORCE_TRAY_TYPE == TrayType.Swing && isSwtLoaded) { - if (AUTO_FIX_INCONSISTENCIES) { - logger.warn("Forcing AWT because SWT cannot load Swing tray type."); - trayType = selectTypeQuietly(TrayType.AWT); - } - else { - logger.error("Cannot initialize Swing type if SWT is loaded."); - - systemTrayMenu = null; - systemTray = null; - return; - } - } - - // Ubuntu UNITY has issues with GtkStatusIcon (it won't work...) + // fix various incompatibilities + if (isNix) { + // Ubuntu UNITY has issues with GtkStatusIcon (it won't work at all...) if (isTrayType(trayType, TrayType.GtkStatusIcon) && OSUtil.DesktopEnv.get() == OSUtil.DesktopEnv.Env.Unity && OSUtil.Linux.isUbuntu()) { if (AUTO_FIX_INCONSISTENCIES) { // we must use AppIndicator because Ubuntu Unity removed GtkStatusIcon support - logger.warn("Forcing AppIndicator because Ubuntu Unity display environment removed support for GtkStatusIcons."); SystemTray.FORCE_TRAY_TYPE = TrayType.AppIndicator; // this is required because of checks inside of AppIndicator... trayType = selectTypeQuietly(TrayType.AppIndicator); + + logger.warn("Forcing AppIndicator because Ubuntu Unity display environment removed support for GtkStatusIcons."); } else { logger.error("Unable to use the GtkStatusIcons when running on Ubuntu with the Unity display environment, and thus" + @@ -463,194 +635,26 @@ class SystemTray { } } - // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least - if (trayType == null) { - OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get(); - - if (DEBUG) { - logger.debug("Currently using the '{}' desktop environment", de); - } - - switch (de) { - case Gnome: { - // check other DE / OS combos that are based on gnome - String GDM = System.getenv("GDMSESSION"); - - if (DEBUG) { - logger.debug("Currently using the '{}' session type", GDM); - } - - if ("gnome".equalsIgnoreCase(GDM)) { - Tray.usingGnome = true; - - // are we fedora? If so, what version? - // now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon - if (OSUtil.Linux.isFedora()) { - if (DEBUG) { - logger.debug("Running Fedora"); - } - - // 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this) - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if (OSUtil.Linux.isUbuntu()) { - // so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works. - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if (OSUtil.Unix.isFreeBSD()) { - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else { - // arch likely will have problems unless the correct/appropriate libraries are installed. - trayType = selectTypeQuietly(TrayType.AppIndicator); - } - } - else if ("cinnamon".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if ("default".equalsIgnoreCase(GDM)) { - // this can be gnome3 on debian - - if (OSUtil.Linux.isDebian()) { - // note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs) - logger.warn("Debian with Gnome detected. SystemTray support is not known to work."); - } - - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if ("gnome-classic".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if ("gnome-fallback".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } - else if ("ubuntu".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(TrayType.AppIndicator); - } - break; - } - case KDE: { - if (OSUtil.Linux.isFedora()) { - // Fedora KDE requires GtkStatusIcon - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - } else { - // kde (at least, plasma 5.5.6) requires appindicator - trayType = selectTypeQuietly(TrayType.AppIndicator); - } - - // kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that - break; - } - case Unity: { - // Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell". - trayType = selectTypeQuietly(TrayType.AppIndicator); - break; - } - case XFCE: { - // NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted. - // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/ - // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25 - - // so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - break; - } - case LXDE: { - trayType = selectTypeQuietly(TrayType.GtkStatusIcon); - break; - } - case Pantheon: { - // elementaryOS. It only supports appindicator (not gtkstatusicon) - // http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/view/head:/sample/SampleIndicator.vala - - // ElementaryOS shows the checkbox on the right, everyone else is on the left. - // With eOS, we CANNOT show the spacer image. It does not work. - trayType = selectTypeQuietly(TrayType.AppIndicator); - break; - } - } - } - - // Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators) - if (trayType == null) { - BufferedReader bin = null; - try { - // the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator), - // is to look through all /proc//status, and first line should be Name:\tindicator-appli - File proc = new File("/proc"); - File[] listFiles = proc.listFiles(); - if (listFiles != null) { - for (File procs : listFiles) { - String name = procs.getName(); - - if (!Character.isDigit(name.charAt(0))) { - continue; - } - - File status = new File(procs, "status"); - if (!status.canRead()) { - continue; - } - - try { - bin = new BufferedReader(new FileReader(status)); - String readLine = bin.readLine(); - - if (readLine != null && readLine.contains("indicator-app")) { - // make sure we can also load the library (it might be the wrong version) - try { - trayType = selectType(TrayType.AppIndicator); - } catch (Exception e) { - if (DEBUG) { - logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e); - } else { - logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK"); - } - } - break; - } - } finally { - IO.closeQuietly(bin); - } - } - } - } catch (Throwable e) { - if (DEBUG) { - logger.error("Error detecting gnome version", e); - } - } - } - - - // fallback... - if (trayType == null) { - logger.warn("Unable to determine the system window manager type. Using the Swing Tray type instead."); - - trayType = selectTypeQuietly(TrayType.Swing); - } - - // this is bad... - if (trayType == null) { - logger.error("SystemTray initialization failed. Unable to load the system tray native libraries. " + - "Using the Swing Tray type instead. Please write an issue and include your OS type and configuration"); - - trayType = selectTypeQuietly(TrayType.Swing); - } - if (isTrayType(trayType, TrayType.AppIndicator) && OSUtil.Linux.isRoot()) { // if are we running as ROOT, there can be issues (definitely on Ubuntu 16.04, maybe others)! - logger.error("Attempting to load the SystemTray as the 'root/sudo' user. This will likely not work because of dbus " + - "restrictions. Using the Swing Tray type instead."); + if (AUTO_FIX_INCONSISTENCIES) { + trayType = selectTypeQuietly(TrayType.Swing); - trayType = selectTypeQuietly(TrayType.Swing); + logger.warn("Attempting to load the SystemTray as the 'root/sudo' user. This will likely not work because of dbus " + + "restrictions. Using the Swing Tray type instead."); + + } else { + logger.error("Attempting to load the SystemTray as the 'root/sudo' user. This will likely NOT WORK because of dbus " + + "restrictions."); + } } } if (trayType == null) { + // unsupported tray, or unknown type trayType = selectTypeQuietly(TrayType.Swing); - // unsupported tray, or unknown type logger.error("SystemTray initialization failed. (Unable to discover which implementation to use). Falling back to the Swing Tray."); } @@ -658,56 +662,55 @@ class SystemTray { // - appIndicator/gtk require strings (which is the path) // - swing version loads as an image (which can be stream or path, we use path) - CacheUtil.tempDir = "SysTray"; + CacheUtil.tempDir = "SystemTrayImages"; try { // at this point, the tray type is what it should be. If there are failures or special cases, all types will fall back to // Swing. - if (OS.isLinux() || OS.isUnix()) { + if (isNix) { // NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3. // appindicator3 doesn't support menu icons via GTK2!! if (!Gtk.isLoaded) { - logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead."); trayType = selectTypeQuietly(TrayType.Swing); + + logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead."); } else if (OSUtil.Linux.isArch()) { // arch linux is fun! - if (isTrayType(trayType, TrayType.AppIndicator)) { + if (isTrayType(trayType, TrayType.AppIndicator) && !AppIndicator.isLoaded) { // appindicators // requires the install of libappindicator which is GTK2 (as of 25DEC2016) // requires the install of libappindicator3 which is GTK3 (as of 25DEC2016) + trayType = selectTypeQuietly(TrayType.Swing); - if (!AppIndicator.isLoaded) { - if (Gtk.isGtk2) { - logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " + - "Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " + - "Using the Swing Tray type instead."); - } else { - logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " + - "Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " + - "Using the Swing Tray type instead."); // GTK3 - } - - trayType = selectTypeQuietly(TrayType.Swing); + if (Gtk.isGtk2) { + logger.warn("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " + + "Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " + + "Using the Swing Tray type instead."); + } else { + logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " + + "Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " + + "Using the Swing Tray type instead."); // GTK3 } } } else if (isTrayType(trayType, TrayType.AppIndicator)) { if (Gtk.isGtk2 && AppIndicator.isVersion3) { + trayType = selectTypeQuietly(TrayType.Swing); + logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " + "Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'. " + "Using the Swing Tray type instead."); - trayType = selectTypeQuietly(TrayType.Swing); } else if (!AppIndicator.isLoaded) { // YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load. - logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead."); - trayType = selectTypeQuietly(TrayType.Swing); + + logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead."); } } } @@ -724,7 +727,7 @@ class SystemTray { Swt.init(); } - if (OS.isLinux() || OS.isUnix()) { + if (isNix) { // linux/unix need access to GTK, so load it up before the tray is loaded! Gtk.startGui(); Gtk.waitForEventsToComplete(); @@ -737,8 +740,9 @@ class SystemTray { + if (AUTO_FIX_INCONSISTENCIES) { - // this logic has to be before we create the system Tray + // this logic has to be before we create the system Tray, but after GTK is started (if applicable) if (OS.isWindows()) { // windows hard-codes the image size SystemTrayFixes.fixWindows(trayImageSize); @@ -747,7 +751,7 @@ class SystemTray { // macosx doesn't respond to all buttons (but should) SystemTrayFixes.fixMacOS(); } - else if ((OS.isLinux() || OS.isUnix())) { + else if (isNix) { // linux/mac doesn't have transparent backgrounds for swing and hard-codes the image size SystemTrayFixes.fixLinux(trayImageSize); } @@ -755,8 +759,9 @@ class SystemTray { + if ((isJavaFxLoaded || isSwtLoaded) && SwingUtilities.isEventDispatchThread()) { - // oh boy! This WILL NOT WORK. Let the dev know + // This WILL NOT WORK. Let the dev know logger.error("SystemTray initialization for JavaFX or SWT **CAN NOT** occur on the Swing Event Dispatch Thread " + "(EDT). Something is seriously wrong."); @@ -765,23 +770,23 @@ class SystemTray { return; } - // javaFX and SWT should not start on the EDT!! - // if it's linux + native menus must not start on the EDT! - // _AwtTray must be constructed on the EDT however... + // javaFX and SWT **CAN NOT** start on the EDT!! + // linux + GTK/AppIndicator menus must not start on the EDT! + + // AWT/Swing must be constructed on the EDT however... if (isJavaFxLoaded || isSwtLoaded || - ((OS.isLinux() || OS.isUnix()) && NativeUI.class.isAssignableFrom(trayType) && trayType != _AwtTray.class)) { + (isNix && (isTrayType(trayType, TrayType.GtkStatusIcon) || isTrayType(trayType, TrayType.AppIndicator))) + ) { try { reference.set((Tray) trayType.getConstructors()[0].newInstance(systemTray)); } catch (Exception e) { logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e); } - } else { - if (trayType == _AwtTray.class) { - // ensure awt toolkit is initialized. - java.awt.Toolkit.getDefaultToolkit(); - } + } else if (isTrayType(trayType, TrayType.Swing) || isTrayType(trayType, TrayType.AWT)) { + // ensure AWT toolkit is initialized. + java.awt.Toolkit.getDefaultToolkit(); // have to construct swing stuff inside the swing EDT final Class finalTrayType = trayType; @@ -796,9 +801,11 @@ class SystemTray { } } }); + } else { + logger.error("Unable to create tray type: '{}'. Aborting!", trayType.getSimpleName()); } } catch (Exception e) { - logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e); + logger.error("Unable to create tray type: '{}'", trayType.getSimpleName(), e); } systemTrayMenu = reference.get(); @@ -991,12 +998,12 @@ class SystemTray { public void setImage(final File imageFile) { if (imageFile == null) { - throw new NullPointerException("imageFile cannot be null!"); + throw new NullPointerException("imageFile"); } final Menu menu = systemTrayMenu; if (menu != null) { - menu.setImage(imageFile); + menu.setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageFile)); } } @@ -1010,12 +1017,12 @@ class SystemTray { public void setImage(final String imagePath) { if (imagePath == null) { - throw new NullPointerException("imagePath cannot be null!"); + throw new NullPointerException("imagePath"); } final Tray tray = systemTrayMenu; if (tray != null) { - tray.setImage(imagePath); + tray.setImage_(ImageResizeUtil.shouldResizeOrCache(true, imagePath)); } } @@ -1029,12 +1036,12 @@ class SystemTray { public void setImage(final URL imageUrl) { if (imageUrl == null) { - throw new NullPointerException("imageUrl cannot be null!"); + throw new NullPointerException("imageUrl"); } final Menu menu = systemTrayMenu; if (menu != null) { - menu.setImage(imageUrl); + menu.setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageUrl)); } } @@ -1048,12 +1055,12 @@ class SystemTray { public void setImage(final InputStream imageStream) { if (imageStream == null) { - throw new NullPointerException("imageStream cannot be null!"); + throw new NullPointerException("imageStream"); } final Menu menu = systemTrayMenu; if (menu != null) { - menu.setImage(imageStream); + menu.setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageStream)); } } @@ -1067,12 +1074,12 @@ class SystemTray { public void setImage(final Image image) { if (image == null) { - throw new NullPointerException("image cannot be null!"); + throw new NullPointerException("image"); } final Menu menu = systemTrayMenu; if (menu != null) { - menu.setImage(image); + menu.setImage_(ImageResizeUtil.shouldResizeOrCache(true, image)); } } @@ -1086,12 +1093,12 @@ class SystemTray { public void setImage(final ImageInputStream imageStream) { if (imageStream == null) { - throw new NullPointerException("image cannot be null!"); + throw new NullPointerException("image"); } final Tray tray = systemTrayMenu; if (tray != null) { - tray.setImage(imageStream); + tray.setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageStream)); } }