Code polish and cleanup for new methods and logic

This commit is contained in:
nathan 2017-06-15 14:44:54 +02:00
parent 99b80c807a
commit d5f7a53210
3 changed files with 248 additions and 216 deletions

View File

@ -24,7 +24,7 @@ import java.net.URL;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import dorkbox.systemTray.peer.MenuItemPeer; import dorkbox.systemTray.peer.MenuItemPeer;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
/** /**
@ -83,32 +83,32 @@ class MenuItem extends Entry {
public public
MenuItem(final String text, final String imagePath, final ActionListener callback) { MenuItem(final String text, final String imagePath, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, imagePath), callback, false);
} }
public public
MenuItem(final String text, final File imageFile, final ActionListener callback) { MenuItem(final String text, final File imageFile, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, imageFile), callback, false);
} }
public public
MenuItem(final String text, final URL imageUrl, final ActionListener callback) { MenuItem(final String text, final URL imageUrl, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, imageUrl), callback, false);
} }
public public
MenuItem(final String text, final InputStream imageStream, final ActionListener callback) { MenuItem(final String text, final InputStream imageStream, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, imageStream), callback, false);
} }
public public
MenuItem(final String text, final Image image, final ActionListener callback) { MenuItem(final String text, final Image image, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, image), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, image), callback, false);
} }
public public
MenuItem(final String text, final ImageInputStream imageStream, final ActionListener callback) { MenuItem(final String text, final ImageInputStream imageStream, final ActionListener callback) {
this(text, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback, false); this(text, ImageResizeUtil.shouldResizeOrCache(false, imageStream), callback, false);
} }
// the last parameter (unused) is there so the signature is different // the last parameter (unused) is there so the signature is different
@ -213,7 +213,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final File imageFile) { void setImage(final File imageFile) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, imageFile));
} }
/** /**
@ -225,7 +225,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final String imagePath) { void setImage(final String imagePath) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, imagePath));
} }
/** /**
@ -237,7 +237,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final URL imageUrl) { void setImage(final URL imageUrl) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, imageUrl));
} }
/** /**
@ -249,7 +249,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final InputStream imageStream) { void setImage(final InputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, imageStream));
} }
/** /**
@ -261,7 +261,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final Image image) { void setImage(final Image image) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, image)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, image));
} }
/** /**
@ -273,7 +273,7 @@ class MenuItem extends Entry {
*/ */
public public
void setImage(final ImageInputStream imageStream) { void setImage(final ImageInputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream)); setImage_(ImageResizeUtil.shouldResizeOrCache(false, imageStream));
} }

View File

@ -20,11 +20,9 @@ import java.awt.GraphicsEnvironment;
import java.awt.Image; import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -35,10 +33,13 @@ import javax.swing.JMenu;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JSeparator; import javax.swing.JSeparator;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.sun.java.swing.plaf.gtk.GTKLookAndFeel;
import dorkbox.systemTray.jna.linux.AppIndicator; import dorkbox.systemTray.jna.linux.AppIndicator;
import dorkbox.systemTray.jna.linux.Gtk; import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.nativeUI.NativeUI; import dorkbox.systemTray.nativeUI.NativeUI;
@ -47,8 +48,8 @@ import dorkbox.systemTray.nativeUI._AwtTray;
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray; import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
import dorkbox.systemTray.swingUI.SwingUIFactory; import dorkbox.systemTray.swingUI.SwingUIFactory;
import dorkbox.systemTray.swingUI._SwingTray; import dorkbox.systemTray.swingUI._SwingTray;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.systemTray.util.JavaFX; import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.SizeAndScalingUtil;
import dorkbox.systemTray.util.Swt; import dorkbox.systemTray.util.Swt;
import dorkbox.systemTray.util.SystemTrayFixes; import dorkbox.systemTray.util.SystemTrayFixes;
import dorkbox.util.CacheUtil; import dorkbox.util.CacheUtil;
@ -57,15 +58,15 @@ import dorkbox.util.OS;
import dorkbox.util.OSUtil; import dorkbox.util.OSUtil;
import dorkbox.util.Property; import dorkbox.util.Property;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
import dorkbox.util.process.ShellProcessBuilder; import sun.security.action.GetPropertyAction;
/** /**
* Professional, cross-platform **SystemTray**, **AWT**, **GtkStatusIcon**, and **AppIndicator** support for java applications. * Professional, cross-platform **SystemTray**, **AWT**, **GtkStatusIcon**, and **AppIndicator** support for Java applications.
* <p> * <p>
* This library provides **OS native** menus and **Swing** menus. * This library provides **OS native** menus and **Swing** menus.
* <ul> * <ul>
* <li> Swing menus are the default prefered type becuase they offer more features (images attached to menu entries, text styling, etc) and * <li> Swing menus are the default preferred type because they offer more features (images attached to menu entries, text styling, etc) and
* a consistent look & feel across all platforms. * a consistent look & feel across all platforms.
* </li> * </li>
* <li> Native menus, should one want them, follow the specified look and feel of that OS, and thus are limited by what is supported on the * <li> Native menus, should one want them, follow the specified look and feel of that OS, and thus are limited by what is supported on the
@ -90,33 +91,12 @@ class SystemTray {
@Property @Property
/** Enables auto-detection for the system tray. This should be mostly successful. /** Enables auto-detection for the system tray. This should be mostly successful.
* <p> * <p>
* Auto-detection will use DEFAULT_TRAY_SIZE or DEFAULT_MENU_SIZE as a 'base-line' for determining what size to use. * Auto-detection will use DEFAULT_TRAY_SIZE as a 'base-line' for determining what size to use.
* <p> * <p>
* If auto-detection fails and the incorrect size is detected or used, disable this and specify the correct DEFAULT_TRAY_SIZE or * If auto-detection fails and the incorrect size is detected or used, disable this and specify the correct DEFAULT_TRAY_SIZE or
* DEFAULT_MENU_SIZE instead * DEFAULT_MENU_SIZE instead
*/ */
public static boolean AUTO_TRAY_SIZE = true; public static boolean AUTO_SIZE = true;
@Property
/**
* Size of the tray, so that the icon can be properly scaled based on OS.
* <p>
* This value can be automatically scaled based on the the platform and scaling-factor.
* - Windows will automatically scale up/down.
* - GtkStatusIcon will usually automatically scale up/down
* - AppIndicators will not always automatically scale (it will sometimes display whatever is specified here)
* <p>
* You will experience WEIRD graphical glitches if this is NOT a power of 2.
*/
public static int DEFAULT_TRAY_SIZE = 16;
@Property
/**
* Size of the menu entries, so that the icon can be properly scaled based on OS.
* <p>
* You will experience WEIRD graphical glitches if this is NOT a power of 2.
*/
public static int DEFAULT_MENU_SIZE = 16;
@Property @Property
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */ /** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
@ -128,7 +108,7 @@ class SystemTray {
* <p> * <p>
* This is an advanced feature, and it is recommended to leave at AutoDetect. * This is an advanced feature, and it is recommended to leave at AutoDetect.
*/ */
public static TrayType FORCE_TRAY_TYPE = TrayType.AutoDetect; public static TrayType FORCE_TRAY_TYPE = TrayType.Swing;
@Property @Property
/** /**
@ -149,7 +129,7 @@ class SystemTray {
@Property @Property
/** /**
* Allows the developer to provide a custom look and feel for the Swing UI, if defined. See the test example for specific use. * Allows a custom look and feel for the Swing UI, if defined. See the test example for specific use.
*/ */
public static SwingUIFactory SWING_UI = null; public static SwingUIFactory SWING_UI = null;
@ -157,7 +137,7 @@ class SystemTray {
/** /**
* This property is provided for debugging any errors in the logic used to determine the system-tray type. * This property is provided for debugging any errors in the logic used to determine the system-tray type.
*/ */
public static boolean DEBUG = false; public static boolean DEBUG = true;
private static volatile SystemTray systemTray = null; private static volatile SystemTray systemTray = null;
@ -201,6 +181,7 @@ class SystemTray {
case Swing: return tray == _SwingTray.class; case Swing: return tray == _SwingTray.class;
case AWT: return tray == _AwtTray.class; case AWT: return tray == _AwtTray.class;
} }
return false; return false;
} }
@ -301,8 +282,47 @@ class SystemTray {
} }
} }
else if (OS.isLinux() || OS.isUnix()) { else if (OS.isLinux() || OS.isUnix()) {
// kablooie if SWT/JavaFX is not configured in a way that works with us. // kablooie if SWT/JavaFX or Swing is not configured in a way that works with us.
if (FORCE_TRAY_TYPE != TrayType.Swing) { if (FORCE_TRAY_TYPE != TrayType.Swing) {
// 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) {
String currentUI = UIManager.getLookAndFeel()
.getClass()
.getName();
boolean mustForceGtk2 = false;
if (currentUI.equals(GTKLookAndFeel.class.getCanonicalName())) {
// this means our look and feel is the GTK look and feel... THIS CREATES PROBLEMS!
// THIS IS NOT DOCUMENTED ANYWHERE...
String swingGtkVersion = java.security.AccessController.doPrivileged(new GetPropertyAction("swing.gtk.version"));
mustForceGtk2 = swingGtkVersion == null || swingGtkVersion.startsWith("2");
}
if (mustForceGtk2) {
// we are NOT using javaFX/SWT and our UI is GTK2 and we want GTK3
// JavaFX/SWT can be GTK3, but Swing can not be GTK3.
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;
} 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. " +
"Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.");
systemTrayMenu = null;
systemTray = null;
return;
}
}
}
if (isSwtLoaded) { if (isSwtLoaded) {
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to // Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
// System.setProperty("SWT_GTK3", "0"); // System.setProperty("SWT_GTK3", "0");
@ -336,8 +356,8 @@ class SystemTray {
// http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html // http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
// https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm // https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
boolean isJFX_GTK3 = System.getProperty("jdk.gtk.version", "2").equals("3"); boolean isJava_GTK3_Possible = OS.javaVersion >= 9 && System.getProperty("jdk.gtk.version", "2").equals("3");
if (isJFX_GTK3 && FORCE_GTK2) { if (isJava_GTK3_Possible && FORCE_GTK2) {
// if we are java9, then we can change it -- otherwise we cannot. // if we are java9, then we can change it -- otherwise we cannot.
if (OS.javaVersion == 9 && AUTO_FIX_INCONSISTENCIES) { if (OS.javaVersion == 9 && AUTO_FIX_INCONSISTENCIES) {
logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " + logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " +
@ -353,7 +373,7 @@ class SystemTray {
systemTray = null; systemTray = null;
return; return;
} }
} else if (!isJFX_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) { } else if (!isJava_GTK3_Possible && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2, because JavaFX is GTK2 // we must use GTK2, because JavaFX is GTK2
logger.warn("Forcing GTK2 because JavaFX is GTK2"); logger.warn("Forcing GTK2 because JavaFX is GTK2");
FORCE_GTK2 = true; FORCE_GTK2 = true;
@ -374,10 +394,15 @@ class SystemTray {
logger.debug("{} {} {}", jvmVendor, jvmName, jvmVersion); logger.debug("{} {} {}", jvmVendor, jvmName, jvmVersion);
logger.debug("Is AutoTraySize? {}", AUTO_TRAY_SIZE); logger.debug("Is Auto sizing tray/menu? {}", AUTO_SIZE);
logger.debug("Is JavaFX detected? {}", isJavaFxLoaded); logger.debug("Is JavaFX detected? {}", isJavaFxLoaded);
logger.debug("Is SWT detected? {}", isSwtLoaded); logger.debug("Is SWT detected? {}", isSwtLoaded);
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name()); if (FORCE_TRAY_TYPE == TrayType.AutoDetect) {
logger.debug("Auto-detecting tray type");
}
else {
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
}
logger.debug("FORCE_GTK2: {}", FORCE_GTK2); logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
} }
@ -388,9 +413,6 @@ class SystemTray {
// this has to happen BEFORE any sort of swing system tray stuff is accessed // this has to happen BEFORE any sort of swing system tray stuff is accessed
if (OS.isWindows()) { if (OS.isWindows()) {
// windows is funky, and is hardcoded to 16x16. We fix that.
SystemTrayFixes.fixWindows();
try { try {
trayType = selectType(TrayType.Swing); trayType = selectType(TrayType.Swing);
} catch (Throwable e) { } catch (Throwable e) {
@ -421,9 +443,10 @@ class SystemTray {
if (SystemTray.FORCE_TRAY_TYPE == TrayType.Swing && isSwtLoaded) { if (SystemTray.FORCE_TRAY_TYPE == TrayType.Swing && isSwtLoaded) {
if (AUTO_FIX_INCONSISTENCIES) { if (AUTO_FIX_INCONSISTENCIES) {
logger.warn("Forcing AWT because SWT cannot load Swing type."); logger.warn("Forcing AWT because SWT cannot load Swing tray type.");
trayType = selectTypeQuietly(TrayType.AWT); trayType = selectTypeQuietly(TrayType.AWT);
} else { }
else {
logger.error("Cannot initialize Swing type if SWT is loaded."); logger.error("Cannot initialize Swing type if SWT is loaded.");
systemTrayMenu = null; systemTrayMenu = null;
@ -432,123 +455,129 @@ class SystemTray {
} }
} }
// Ubuntu UNITY has issues with GtkStatusIcon (it won't work...)
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);
}
else {
logger.error("Unable to use the GtkStatusIcons when running on Ubuntu with the Unity display environment, and thus" +
" the SystemTray will not work. " +
"Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.");
systemTrayMenu = null;
systemTray = null;
return;
}
}
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
// if we are running as ROOT, we *** WILL NOT *** have access to 'XDG_CURRENT_DESKTOP'
// *unless env's are preserved, but they are not guaranteed to be
// see: http://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
if (XDG == null) {
// maybe we are running as root???
XDG = "unknown"; // try to autodetect if we should use app indicator or gtkstatusicon
}
// BLEH. if gnome-shell is running, IT'S REALLY GNOME!
// we must ALWAYS do this check!!
boolean isReallyGnome = OSUtil.DesktopEnv.isGnome();
if (isReallyGnome) {
if (DEBUG) {
logger.error("Auto-detected that gnome-shell is running");
}
XDG = "gnome";
}
if (DEBUG) {
logger.debug("Currently using the '{}' desktop", XDG);
}
if (trayType == null) { if (trayType == null) {
// Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell". OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get();
if ("unity".equalsIgnoreCase(XDG)) {
trayType = selectTypeQuietly(TrayType.AppIndicator); if (DEBUG) {
logger.debug("Currently using the '{}' desktop environment", de);
} }
else if ("xfce".equalsIgnoreCase(XDG)) {
// 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 switch (de) {
trayType = selectTypeQuietly(TrayType.GtkStatusIcon); case Gnome: {
} // check other DE / OS combos that are based on gnome
else if ("lxde".equalsIgnoreCase(XDG)) { String GDM = System.getenv("GDMSESSION");
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("kde".equalsIgnoreCase(XDG)) {
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 if (DEBUG) {
} logger.debug("Currently using the '{}' session type", GDM);
else if ("pantheon".equalsIgnoreCase(XDG)) { }
// 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. if ("gnome".equalsIgnoreCase(GDM)) {
// With eOS, we CANNOT show the spacer image. It does not work. Tray.usingGnome = true;
trayType = selectTypeQuietly(TrayType.AppIndicator);
}
else if ("gnome".equalsIgnoreCase(XDG)) {
// check other DE
String GDM = System.getenv("GDMSESSION");
if (DEBUG) { // are we fedora? If so, what version?
logger.debug("Currently using the '{}' session type", GDM); // 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");
}
if ("gnome".equalsIgnoreCase(GDM)) { // 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this)
Tray.usingGnome = true; 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
// are we fedora? If so, what version? if (OSUtil.Linux.isDebian()) {
// now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon // note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs)
if (OSUtil.Linux.isFedora()) { logger.warn("Debian with Gnome detected. SystemTray support is not known to work.");
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); trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
} }
else if (OSUtil.Linux.isUbuntu()) { else if ("gnome-classic".equalsIgnoreCase(GDM)) {
// so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works.
trayType = selectTypeQuietly(TrayType.GtkStatusIcon); trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
} }
else if (OSUtil.Unix.isFreeBSD()) { else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
trayType = selectTypeQuietly(TrayType.GtkStatusIcon); trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
} }
else { else if ("ubuntu".equalsIgnoreCase(GDM)) {
// arch likely will have problems unless the correct/appropriate libraries are installed.
trayType = selectTypeQuietly(TrayType.AppIndicator); trayType = selectTypeQuietly(TrayType.AppIndicator);
} }
break;
} }
else if ("cinnamon".equalsIgnoreCase(GDM)) { case KDE: {
trayType = selectTypeQuietly(TrayType.GtkStatusIcon); if (OSUtil.Linux.isFedora()) {
} // Fedora KDE requires GtkStatusIcon
else if ("default".equalsIgnoreCase(GDM)) { trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
// this can be gnome3 on debian } else {
// kde (at least, plasma 5.5.6) requires appindicator
if (OSUtil.Linux.isDebian()) { trayType = selectTypeQuietly(TrayType.AppIndicator);
// 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); // kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
break;
} }
else if ("gnome-classic".equalsIgnoreCase(GDM)) { case Unity: {
trayType = selectTypeQuietly(TrayType.GtkStatusIcon); // Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
}
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("ubuntu".equalsIgnoreCase(GDM)) {
trayType = selectTypeQuietly(TrayType.AppIndicator); 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;
} }
} }
} }
@ -620,39 +649,11 @@ class SystemTray {
return; return;
} }
if (isTrayType(trayType, TrayType.AppIndicator)) { 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)! // if are we running as ROOT, there can be issues (definitely on Ubuntu 16.04, maybe others)!
// this means we are running as sudo logger.error("Attempting to load the SystemTray as the 'root/sudo' user. This will likely not work because of dbus " +
String sudoUser = System.getenv("SUDO_USER"); "restrictions.");
if (sudoUser != null) {
// running as a "sudo" user
logger.error("Attempting to load the SystemTray as the 'root' user. This will likely not work because of dbus restrictions.");
}
else {
// running as root (also can be "sudo" user). A bit slower that checking a sys env, but this is guaranteed to work
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
// id -u
final ShellProcessBuilder shell = new ShellProcessBuilder(outputStream);
shell.setExecutable("id");
shell.addArgument("-u");
shell.start();
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
if ("0".equals(output)) {
logger.error("Attempting to load the SystemTray as the 'root' user. This will likely not work because of dbus " +
"restrictions.");
}
} catch (Throwable e) {
if (DEBUG) {
logger.error("Cannot get id for root", e);
}
}
}
} }
} }
@ -666,8 +667,6 @@ class SystemTray {
return; return;
} }
ImageUtils.determineIconSize();
final AtomicReference<Tray> reference = new AtomicReference<Tray>(); final AtomicReference<Tray> reference = new AtomicReference<Tray>();
// - appIndicator/gtk require strings (which is the path) // - appIndicator/gtk require strings (which is the path)
@ -675,7 +674,7 @@ class SystemTray {
CacheUtil.tempDir = "SysTray"; CacheUtil.tempDir = "SysTray";
try { try {
if (OS.isLinux() || OS.isUnix()) { if ((OS.isLinux() || OS.isUnix()) && (isTrayType(trayType, TrayType.GtkStatusIcon) || isTrayType(trayType, TrayType.AppIndicator))) {
// NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3. // NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3.
// appindicator3 doesn't support menu icons via GTK2!! // appindicator3 doesn't support menu icons via GTK2!!
if (!Gtk.isLoaded) { if (!Gtk.isLoaded) {
@ -685,7 +684,6 @@ class SystemTray {
return; return;
} }
if (OSUtil.Linux.isArch()) { if (OSUtil.Linux.isArch()) {
// arch linux is fun! // arch linux is fun!
@ -766,10 +764,21 @@ class SystemTray {
return; return;
} }
// this logic has to be JUST before we create the system Tray
if (OS.isWindows()) {
// windows hard-codes the image size
SystemTrayFixes.fixWindows(SizeAndScalingUtil.getTrayImageSize(trayType));
} else if (OS.isMacOsX()) {
// macosx doesn't respond to all buttons (but should)
SystemTrayFixes.fixMacOS();
}
// initialize tray/menu image sizes
SizeAndScalingUtil.getTrayImageSize(trayType);
SizeAndScalingUtil.getMenuImageSize(trayType);
// javaFX and SWT should not start on the EDT!! // javaFX and SWT should not start on the EDT!!
// if it's linux + native menus must 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... // _AwtTray must be constructed on the EDT however...
if (isJavaFxLoaded || isSwtLoaded || if (isJavaFxLoaded || isSwtLoaded ||
@ -1004,9 +1013,9 @@ class SystemTray {
} }
/** /**
* Specifies the new image to set for a menu entry, NULL to delete the image * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imagePath the full path of the image to use or null * @param imagePath the full path of the image to use or null
*/ */
@ -1016,16 +1025,16 @@ class SystemTray {
throw new NullPointerException("imagePath cannot be null!"); throw new NullPointerException("imagePath cannot be null!");
} }
final Menu menu = systemTrayMenu; final Tray tray = systemTrayMenu;
if (menu != null) { if (tray != null) {
menu.setImage(imagePath); tray.setImage(imagePath);
} }
} }
/** /**
* Specifies the new image to set for a menu entry, NULL to delete the image * Specifies the new image to set for the tray icon.
*<p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageUrl the URL of the image to use or null * @param imageUrl the URL of the image to use or null
*/ */
@ -1042,9 +1051,9 @@ class SystemTray {
} }
/** /**
* Specifies the new image to set for a menu entry, NULL to delete the image * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageStream the InputStream of the image to use * @param imageStream the InputStream of the image to use
*/ */
@ -1061,9 +1070,9 @@ class SystemTray {
} }
/** /**
* Specifies the new image to set for a menu entry, NULL to delete the image * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param image the image of the image to use * @param image the image of the image to use
*/ */
@ -1080,9 +1089,9 @@ class SystemTray {
} }
/** /**
* Specifies the new image to set for a menu entry, NULL to delete the image * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
*@param imageStream the ImageInputStream of the image to use *@param imageStream the ImageInputStream of the image to use
*/ */
@ -1092,10 +1101,27 @@ class SystemTray {
throw new NullPointerException("image cannot be null!"); throw new NullPointerException("image cannot be null!");
} }
final Menu menu = systemTrayMenu; final Tray tray = systemTrayMenu;
if (menu != null) { if (tray != null) {
menu.setImage(imageStream); tray.setImage(imageStream);
} }
} }
/**
* @return the system tray image size, accounting for OS and theme differences
*/
public
int getTrayImageSize() {
return SizeAndScalingUtil.getTrayImageSize(systemTrayMenu.getClass());
}
/**
* @return the system tray menu image size, accounting for OS and theme differences
*/
public
int getMenuImageSize() {
return SizeAndScalingUtil.getMenuImageSize(systemTrayMenu.getClass());
}
} }

View File

@ -23,7 +23,7 @@ import java.net.URL;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import dorkbox.systemTray.gnomeShell.Extension; import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.OSUtil; import dorkbox.util.OSUtil;
// This is public ONLY so that it is in the scope for SwingUI and NativeUI system tray components // This is public ONLY so that it is in the scope for SwingUI and NativeUI system tray components
@ -53,7 +53,7 @@ class Tray extends Menu {
} }
} }
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus) // appindicators DO NOT support anything other than PLAIN gtk-menus
// they ALSO do not support tooltips! // they ALSO do not support tooltips!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12 // https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
@ -142,72 +142,78 @@ class Tray extends Menu {
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageFile the file of the image to use * @param imageFile the file of the image to use
*/ */
@Override
public public
void setImage(final File imageFile) { void setImage(final File imageFile) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageFile)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageFile));
} }
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imagePath the full path of the image to use * @param imagePath the full path of the image to use
*/ */
@Override
public public
void setImage(final String imagePath) { void setImage(final String imagePath) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imagePath)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, imagePath));
} }
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageUrl the URL of the image to use * @param imageUrl the URL of the image to use
*/ */
@Override
public public
void setImage(final URL imageUrl) { void setImage(final URL imageUrl) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageUrl)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageUrl));
} }
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageStream the InputStream of the image to use * @param imageStream the InputStream of the image to use
*/ */
@Override
public public
void setImage(final InputStream imageStream) { void setImage(final InputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageStream)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageStream));
} }
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param image the image of the image to use * @param image the image of the image to use
*/ */
@Override
public public
void setImage(final Image image) { void setImage(final Image image) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, image)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, image));
} }
/** /**
* Specifies the new image to set for the tray icon. * Specifies the new image to set for the tray icon.
* <p> * <p>
* This method will cache the image if it needs to be resized to fit. * If AUTO_SIZE, then this method resize the image (best guess), otherwise the image "as-is" will be used
* *
* @param imageStream the ImageInputStream of the image to use * @param imageStream the ImageInputStream of the image to use
*/ */
@Override
public public
void setImage(final ImageInputStream imageStream) { void setImage(final ImageInputStream imageStream) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.TRAY_SIZE, imageStream)); setImage_(ImageResizeUtil.shouldResizeOrCache(true, imageStream));
} }
} }