Added comments for TrayType. Added "AUTO_FIX_INCONSISTENCIES" to

automatically try to resolve issues with various OS configurations.
Cleaned up exceptions (now just error log and NULL menu value)
This commit is contained in:
nathan 2016-12-23 11:42:46 +01:00
parent 1308b5f602
commit 4fe1ebf14f

View File

@ -17,7 +17,6 @@ package dorkbox.systemTray;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
@ -81,9 +80,13 @@ class SystemTray {
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
public enum TrayType {
/** Will choose as a 'best guess' which tray type to use based on if native is requested or not */
AutoDetect,
/** if native, will have Gtk Menus. Non-native will have Swing menus */
GtkStatusIcon,
/** if native, will have Gtk Menus. Non-native will have Swing menus */
AppIndicator,
/** if native, will have AWT Menus. Non-native will have Swing menus */
Swing
}
@ -140,6 +143,14 @@ class SystemTray {
*/
public static boolean ENABLE_SHUTDOWN_HOOK = true;
@Property
/**
* Allows the SystemTray logic to resolve OS inconsistencies for the SystemTray.
* <p>
* This is an advanced feature, and it is recommended to leave as true
*/
public static boolean AUTO_FIX_INCONSISTENCIES = true;
@Property
/**
* This property is provided for debugging any errors in the logic used to determine the system-tray type.
@ -197,8 +208,7 @@ class SystemTray {
}
}
else if (trayType == TrayType.Swing) {
if (useNativeMenus && !OS.isWindows()) {
// AWT on windows looks like crap
if (useNativeMenus) {
return _AwtTray.class;
}
else {
@ -251,54 +261,58 @@ class SystemTray {
" - Set the system property via 'System.setProperty(\"javafx.macosx.embedded\", \"true\");' before JavaFX is" +
"initialized, used, or accessed. NOTE: You may need to change the class (that your main method is in) so it does" +
" NOT extend the JavaFX 'Application' class.");
throw new RuntimeException();
systemTrayMenu = null;
return;
}
// cannot mix Swing and SWT on MacOSX (for all version sof java) -- so we force native menus instead, which work just fine
// with SWT
// 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) {
useNativeMenus = true;
logger.info("MacOSX does not support SWT + Swing at the same time. Forcing Native Menus instead.");
logger.warn("MacOSX does not support SWT + Swing at the same time. Forcing Native menus instead.");
}
}
// no tray in a headless environment
if (GraphicsEnvironment.isHeadless()) {
logger.error("Cannot use the SystemTray in a headless environment");
throw new HeadlessException();
systemTrayMenu = null;
return;
}
Class<? extends Tray> trayType = null;
// Windows can ONLY use Swing (non-native) - AWT/native looks absolutely horrid
// OSx can use Swing (non-native) or AWT (native) .
// Linux can use Swing (non-native) menus + (native Icon via GTK or AppIndicator), GtkStatusIcon (native), or AppIndicator (native)
if (OS.isWindows()) {
if (useNativeMenus && AUTO_FIX_INCONSISTENCIES) {
// windows MUST use swing non-native only. AWT (native) looks terrible!
useNativeMenus = false;
logger.warn("Windows cannot use a 'native' SystemTray, defaulting to non-native SwingUI");
}
if (DEBUG) {
logger.debug("OS: {}", System.getProperty("os.name"));
logger.debug("Arch: {}", System.getProperty("os.arch"));
String jvmName = System.getProperty("java.vm.name", "");
String jvmVersion = System.getProperty("java.version", "");
String jvmVendor = System.getProperty("java.vm.specification.vendor", "");
logger.debug("{} {} {}", jvmVendor, jvmName, jvmVersion);
logger.debug("is AutoTraySize? {}", AUTO_TRAY_SIZE);
logger.debug("is JavaFX detected? {}", isJavaFxLoaded);
logger.debug("is SWT detected? {}", isSwtLoaded);
logger.debug("is using native menus? {}", useNativeMenus);
if (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 SwingUI");
}
}
else if (OS.isMacOsX()) {
if (FORCE_TRAY_TYPE != TrayType.Swing ) {
if (useNativeMenus) {
logger.warn("MacOsX cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to SwingUI");
} else {
// windows and mac ONLY support the Swing SystemTray.
// Linux CAN support Swing SystemTray, but it looks like crap (so we wrote our own GtkStatusIcon/AppIndicator)
if (OS.isWindows() && FORCE_TRAY_TYPE != TrayType.Swing) {
throw new RuntimeException("Windows is incompatible with the specified option for FORCE_TRAY_TYPE: " + FORCE_TRAY_TYPE);
} else if (OS.isMacOsX() && FORCE_TRAY_TYPE != TrayType.Swing) {
throw new RuntimeException("MacOSx is incompatible with the specified option for FORCE_TRAY_TYPE: " + FORCE_TRAY_TYPE);
}
logger.warn("MacOsX cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to AWT Native UI");
}
// MacOsX MUST use swing (and AWT) only!
FORCE_TRAY_TYPE = TrayType.AutoDetect;
}
}
else if (OS.isLinux()) {
// kablooie if SWT/JavaFX is not configured in a way that works with us.
if (FORCE_TRAY_TYPE != TrayType.Swing && OS.isLinux()) {
if (FORCE_TRAY_TYPE != TrayType.Swing) {
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
// System.setProperty("SWT_GTK3", "0");
@ -317,12 +331,11 @@ class SystemTray {
"GTK2. Please configure SWT to use GTK2, via `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is " +
"initialized, or set `SystemTray.FORCE_GTK2=false;`");
throw new RuntimeException("SWT configured to use GTK3 and is incompatible with the SystemTray GTK2.");
} else if (!isSwt_GTK3 && !FORCE_GTK2) {
systemTrayMenu = null;
return;
} else if (!isSwt_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2, because SWT is GTK2
if (DEBUG) {
logger.debug("Forcing GTK2 because SWT is GTK2");
}
logger.warn("Forcing GTK2 because SWT is GTK2");
FORCE_GTK2 = true;
}
}
@ -335,34 +348,45 @@ class SystemTray {
boolean isJFX_GTK3 = System.getProperty("jdk.gtk.version", "2").equals("3");
if (isJFX_GTK3 && FORCE_GTK2) {
// if we are java9, then we can change it -- otherwise we cannot.
if (OS.javaVersion == 9) {
logger.error("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;`");
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 " +
"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`.");
throw new RuntimeException("JavaFX configured to use GTK3 and is incompatible with the SystemTray 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.");
throw new RuntimeException("JavaFX configured to use GTK3 and is incompatible with the SystemTray GTK2.");
systemTrayMenu = null;
return;
}
} else if (!isJFX_GTK3 && !FORCE_GTK2) {
} else if (!isJFX_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2, because JavaFX is GTK2
if (DEBUG) {
logger.debug("Forcing GTK2 because JavaFX is GTK2");
}
logger.warn("Forcing GTK2 because JavaFX is GTK2");
FORCE_GTK2 = true;
}
}
}
}
Class<? extends Tray> trayType = null;
if (DEBUG) {
if (FORCE_TRAY_TYPE == TrayType.AutoDetect) {
logger.debug("Auto-detecting tray type");
} else {
logger.debug("OS: {}", System.getProperty("os.name"));
logger.debug("Arch: {}", System.getProperty("os.arch"));
String jvmName = System.getProperty("java.vm.name", "");
String jvmVersion = System.getProperty("java.version", "");
String jvmVendor = System.getProperty("java.vm.specification.vendor", "");
logger.debug("{} {} {}", jvmVendor, jvmName, jvmVersion);
logger.debug("Is AutoTraySize? {}", AUTO_TRAY_SIZE);
logger.debug("Is JavaFX detected? {}", isJavaFxLoaded);
logger.debug("Is SWT detected? {}", isSwtLoaded);
logger.debug("Is using native menus? {}", useNativeMenus);
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
}
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
}
@ -370,7 +394,17 @@ class SystemTray {
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all. There is no mouse-over event.
if (FORCE_TRAY_TYPE != TrayType.Swing && OS.isLinux()) {
// this has to happen BEFORE any sort of swing system tray stuff is accessed
if (OS.isWindows()) {
// windows is funky, and is hardcoded to 16x16. We fix that.
SystemTrayFixes.fixWindows();
}
else if (OS.isMacOsX() && useNativeMenus) {
// macosx doesn't respond to all buttons (but should)
SystemTrayFixes.fixMacOS();
}
else if (OS.isLinux() && FORCE_TRAY_TYPE != TrayType.Swing) {
// 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.
@ -463,6 +497,12 @@ class SystemTray {
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
if (!useNativeMenus && AUTO_FIX_INCONSISTENCIES) {
logger.warn("Cannot use non-native menus with pantheon (elementaryOS). Forcing native menus.");
useNativeMenus = true;
}
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
}
else if ("gnome".equalsIgnoreCase(XDG)) {
@ -542,13 +582,16 @@ class SystemTray {
// fallback...
if (trayType == null) {
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
logger.info("Unable to determine the system window manager type.Falling back to GtkStatusIcon.");
logger.warn("Unable to determine the system window manager type. Falling back to GtkStatusIcon.");
}
// this is bad...
if (trayType == null) {
logger.error("Unable to load the system tray native libraries. Please write an issue and include your OS type and configuration");
throw new RuntimeException("SystemTray initialization failed. Something is seriously wrong.");
logger.error("SystemTray initialization failed. Unable to load the system tray native libraries. Please write an issue " +
"and include your OS type and configuration");
systemTrayMenu = null;
return;
}
if (trayType == _AppIndicatorNativeTray.class || trayType == _AppIndicatorTray.class) {
@ -558,9 +601,9 @@ class SystemTray {
String sudoUser = System.getenv("SUDO_USER");
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 {
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);
@ -587,19 +630,6 @@ class SystemTray {
}
}
// this has to happen BEFORE any sort of swing system tray stuff is accessed
if (OS.isWindows()) {
// windows is funky, and is hardcoded to 16x16. We fix that.
SystemTrayFixes.fixWindows();
}
else if (OS.isMacOsX() && useNativeMenus) {
// macos doesn't respond to all buttons (but should)
SystemTrayFixes.fixMacOS();
}
ImageUtils.determineIconSize();
// this is likely windows OR mac
if (trayType == null) {
try {
@ -614,11 +644,15 @@ class SystemTray {
}
if (trayType == null) {
// unsupported tray
logger.error("Unable to discover what tray implementation to use!");
// unsupported tray, or unknown type
logger.error("SystemTray initialization failed. (Unable to discover which implementation to use). Something is seriously wrong.");
systemTrayMenu = null;
return;
}
else {
ImageUtils.determineIconSize();
final AtomicReference<Tray> reference = new AtomicReference<Tray>();
/*
@ -645,6 +679,9 @@ class SystemTray {
"AppIndicator3 requires GTK3 to be fully functional, and while this will work -- " +
"the menu icons WILL NOT be visible." +
" Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
systemTrayMenu = null;
return;
}
}
@ -659,7 +696,11 @@ class SystemTray {
if ((isJavaFxLoaded || isSwtLoaded) && SwingUtilities.isEventDispatchThread()) {
// oh boy! This WILL NOT WORK. Let the dev know
throw new RuntimeException("SystemTray initialization can not occur on the Swing Event Dispatch Thread (EDT)");
logger.error("SystemTray initialization for JavaFX or SWT **CAN NOT** occur on the Swing Event Dispatch Thread " +
"(EDT). Something is seriously wrong.");
systemTrayMenu = null;
return;
}
// javaFX and SWT should not start on the EDT!!
@ -709,10 +750,13 @@ class SystemTray {
} else if (!useNativeMenus && systemTrayMenu instanceof SwingUI) {
// this configuration is OK.
} else {
throw new RuntimeException("Unable to correctly initialize the System Tray. Please write an issue and include your " +
logger.error("Unable to correctly initialize the System Tray. Please write an issue and include your " +
"OS type and configuration");
systemTrayMenu = null;
return;
}
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
if (ENABLE_SHUTDOWN_HOOK) {
if (isJavaFxLoaded) {
@ -743,7 +787,6 @@ class SystemTray {
}
}
}
}
/**
@ -793,9 +836,9 @@ class SystemTray {
public
void shutdown() {
// this will call "dispatchAndWait()" behind the scenes, so it is thread-safe
final Menu tray = systemTrayMenu;
if (tray != null) {
tray.remove();
final Menu menu = systemTrayMenu;
if (menu != null) {
menu.remove();
}
systemTrayMenu = null;
@ -844,12 +887,13 @@ class SystemTray {
*/
public
Menu setMenu(final JMenu jMenu) {
Menu menu = systemTrayMenu;
if (menu != null) {
Icon icon = jMenu.getIcon();
BufferedImage bimage = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
setImage(bimage);
Menu menu = getMenu();
Component[] menuComponents = jMenu.getMenuComponents();
for (int i = 0, menuComponentsLength = menuComponents.length; i < menuComponentsLength; i++) {
final Component c = menuComponents[i];
@ -867,6 +911,7 @@ class SystemTray {
menu.add((JSeparator) c);
}
}
}
return menu;
}