forked from dorkbox/SystemTray
Removing logic to choose between swing/native menus (Swing w/
appindicators is broken)
This commit is contained in:
parent
ec9499c1bb
commit
3c24139544
@ -46,8 +46,6 @@ import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray;
|
|||||||
import dorkbox.systemTray.nativeUI._AwtTray;
|
import dorkbox.systemTray.nativeUI._AwtTray;
|
||||||
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
||||||
import dorkbox.systemTray.swingUI.SwingUI;
|
import dorkbox.systemTray.swingUI.SwingUI;
|
||||||
import dorkbox.systemTray.swingUI._AppIndicatorSwingTray;
|
|
||||||
import dorkbox.systemTray.swingUI._GtkStatusIconSwingTray;
|
|
||||||
import dorkbox.systemTray.swingUI._SwingTray;
|
import dorkbox.systemTray.swingUI._SwingTray;
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
import dorkbox.systemTray.util.JavaFX;
|
import dorkbox.systemTray.util.JavaFX;
|
||||||
@ -81,14 +79,12 @@ class SystemTray {
|
|||||||
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
||||||
|
|
||||||
public enum TrayType {
|
public enum TrayType {
|
||||||
/** Will choose as a 'best guess' which tray type to use based on if native is requested or not */
|
/** Will choose as a 'best guess' which tray type to use */
|
||||||
AutoDetect,
|
AutoDetect,
|
||||||
/** if native, will have Gtk Menus. Non-native will have Swing menus */
|
|
||||||
GtkStatusIcon,
|
GtkStatusIcon,
|
||||||
/** if native, will have Gtk Menus. Non-native will have Swing menus */
|
|
||||||
AppIndicator,
|
AppIndicator,
|
||||||
/** if native, will have AWT Menus. Non-native will have Swing menus */
|
Swing,
|
||||||
Swing
|
AWT
|
||||||
}
|
}
|
||||||
|
|
||||||
@Property
|
@Property
|
||||||
@ -128,7 +124,7 @@ class SystemTray {
|
|||||||
|
|
||||||
@Property
|
@Property
|
||||||
/**
|
/**
|
||||||
* Forces the system tray detection to be AutoDetect, GtkStatusIcon, AppIndicator, or Swing.
|
* Forces the system tray detection to be AutoDetect, GtkStatusIcon, AppIndicator, Swing, or AWT.
|
||||||
* <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.
|
||||||
*/
|
*/
|
||||||
@ -194,45 +190,36 @@ class SystemTray {
|
|||||||
private static
|
private static
|
||||||
boolean isTrayType(final Class<? extends Tray> tray, final TrayType trayType) {
|
boolean isTrayType(final Class<? extends Tray> tray, final TrayType trayType) {
|
||||||
switch (trayType) {
|
switch (trayType) {
|
||||||
case GtkStatusIcon: return (tray == _GtkStatusIconSwingTray.class || tray == _GtkStatusIconNativeTray.class);
|
case GtkStatusIcon: return tray == _GtkStatusIconNativeTray.class;
|
||||||
case AppIndicator: return (tray == _AppIndicatorSwingTray.class || tray == _AppIndicatorNativeTray.class);
|
case AppIndicator: return tray == _AppIndicatorNativeTray.class;
|
||||||
case Swing: return (tray == _SwingTray.class || tray == _AwtTray.class);
|
case Swing: return tray == _SwingTray.class;
|
||||||
|
case AWT: return tray == _AwtTray.class;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
private static
|
||||||
Class<? extends Tray> selectType(final boolean useNativeMenus, final TrayType trayType) throws Exception {
|
Class<? extends Tray> selectType(final TrayType trayType) throws Exception {
|
||||||
if (trayType == TrayType.GtkStatusIcon) {
|
if (trayType == TrayType.GtkStatusIcon) {
|
||||||
if (useNativeMenus) {
|
return _GtkStatusIconNativeTray.class;
|
||||||
return _GtkStatusIconNativeTray.class;
|
}
|
||||||
} else {
|
else if (trayType == TrayType.AppIndicator) {
|
||||||
return _GtkStatusIconSwingTray.class;
|
return _AppIndicatorNativeTray.class;
|
||||||
}
|
|
||||||
} else if (trayType == TrayType.AppIndicator) {
|
|
||||||
if (useNativeMenus) {
|
|
||||||
return _AppIndicatorNativeTray.class;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return _AppIndicatorSwingTray.class;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (trayType == TrayType.Swing) {
|
else if (trayType == TrayType.Swing) {
|
||||||
if (useNativeMenus) {
|
return _SwingTray.class;
|
||||||
return _AwtTray.class;
|
}
|
||||||
}
|
else if (trayType == TrayType.AWT) {
|
||||||
else {
|
return _AwtTray.class;
|
||||||
return _SwingTray.class;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static
|
private static
|
||||||
Class<? extends Tray> selectTypeQuietly(final boolean useNativeMenus, final TrayType trayType) {
|
Class<? extends Tray> selectTypeQuietly(final TrayType trayType) {
|
||||||
try {
|
try {
|
||||||
return selectType(useNativeMenus, trayType);
|
return selectType(trayType);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
logger.error("Cannot initialize {}", trayType.name(), t);
|
logger.error("Cannot initialize {}", trayType.name(), t);
|
||||||
@ -243,7 +230,7 @@ class SystemTray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"})
|
@SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"})
|
||||||
private static void init(boolean useNativeMenus) {
|
private static void init() {
|
||||||
if (systemTray != null) {
|
if (systemTray != null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -279,10 +266,6 @@ 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
|
// 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
|
// http://mail.openjdk.java.net/pipermail/bsd-port-dev/2008-December/000173.html
|
||||||
if (isSwtLoaded) {
|
|
||||||
useNativeMenus = true;
|
|
||||||
logger.warn("MacOSX does not support SWT + Swing at the same time. Forcing Native menus instead.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// no tray in a headless environment
|
// no tray in a headless environment
|
||||||
@ -298,28 +281,17 @@ class SystemTray {
|
|||||||
// OSx can use Swing (non-native) or AWT (native) .
|
// 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)
|
// Linux can use Swing (non-native) menus + (native Icon via GTK or AppIndicator), GtkStatusIcon (native), or AppIndicator (native)
|
||||||
if (OS.isWindows()) {
|
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 (FORCE_TRAY_TYPE != TrayType.Swing) {
|
if (FORCE_TRAY_TYPE != TrayType.Swing) {
|
||||||
// windows MUST use swing only!
|
// windows MUST use swing only!
|
||||||
FORCE_TRAY_TYPE = TrayType.AutoDetect;
|
FORCE_TRAY_TYPE = TrayType.AutoDetect;
|
||||||
logger.warn("Windows cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to SwingUI");
|
logger.warn("Windows cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Swing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (OS.isMacOsX()) {
|
else if (OS.isMacOsX()) {
|
||||||
if (FORCE_TRAY_TYPE != TrayType.Swing ) {
|
if (FORCE_TRAY_TYPE != TrayType.Swing ) {
|
||||||
if (useNativeMenus) {
|
|
||||||
logger.warn("MacOsX cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to SwingUI");
|
|
||||||
} else {
|
|
||||||
logger.warn("MacOsX cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to AWT Native UI");
|
|
||||||
}
|
|
||||||
|
|
||||||
// MacOsX MUST use swing (and AWT) only!
|
// MacOsX MUST use swing (and AWT) only!
|
||||||
FORCE_TRAY_TYPE = TrayType.AutoDetect;
|
FORCE_TRAY_TYPE = TrayType.AutoDetect;
|
||||||
|
logger.warn("MacOS cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Awt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (OS.isLinux() || OS.isUnix()) {
|
else if (OS.isLinux() || OS.isUnix()) {
|
||||||
@ -399,7 +371,6 @@ class SystemTray {
|
|||||||
logger.debug("Is AutoTraySize? {}", AUTO_TRAY_SIZE);
|
logger.debug("Is AutoTraySize? {}", AUTO_TRAY_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("Is using native menus? {}", useNativeMenus);
|
|
||||||
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
|
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
|
||||||
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
|
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
|
||||||
}
|
}
|
||||||
@ -413,10 +384,22 @@ class SystemTray {
|
|||||||
if (OS.isWindows()) {
|
if (OS.isWindows()) {
|
||||||
// windows is funky, and is hardcoded to 16x16. We fix that.
|
// windows is funky, and is hardcoded to 16x16. We fix that.
|
||||||
SystemTrayFixes.fixWindows();
|
SystemTrayFixes.fixWindows();
|
||||||
|
|
||||||
|
try {
|
||||||
|
trayType = selectType(TrayType.Swing);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (OS.isMacOsX() && useNativeMenus) {
|
else if (OS.isMacOsX()) {
|
||||||
// macosx doesn't respond to all buttons (but should)
|
// macosx doesn't respond to all buttons (but should)
|
||||||
SystemTrayFixes.fixMacOS();
|
SystemTrayFixes.fixMacOS();
|
||||||
|
|
||||||
|
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()) && FORCE_TRAY_TYPE != TrayType.Swing) {
|
else if ((OS.isLinux() || OS.isUnix()) && FORCE_TRAY_TYPE != TrayType.Swing) {
|
||||||
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
||||||
@ -427,7 +410,7 @@ class SystemTray {
|
|||||||
// this can never be swing
|
// this can never be swing
|
||||||
// don't check for SWING type at this spot, it is done elsewhere.
|
// don't check for SWING type at this spot, it is done elsewhere.
|
||||||
if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) {
|
if (SystemTray.FORCE_TRAY_TYPE != TrayType.AutoDetect) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, SystemTray.FORCE_TRAY_TYPE);
|
trayType = selectTypeQuietly(SystemTray.FORCE_TRAY_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -461,7 +444,7 @@ class SystemTray {
|
|||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
// Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
|
// Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
|
||||||
if ("unity".equalsIgnoreCase(XDG)) {
|
if ("unity".equalsIgnoreCase(XDG)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||||
}
|
}
|
||||||
else if ("xfce".equalsIgnoreCase(XDG)) {
|
else if ("xfce".equalsIgnoreCase(XDG)) {
|
||||||
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
||||||
@ -469,18 +452,18 @@ class SystemTray {
|
|||||||
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
|
||||||
|
|
||||||
// so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
|
// so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("lxde".equalsIgnoreCase(XDG)) {
|
else if ("lxde".equalsIgnoreCase(XDG)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("kde".equalsIgnoreCase(XDG)) {
|
else if ("kde".equalsIgnoreCase(XDG)) {
|
||||||
if (OSUtil.Linux.isFedora()) {
|
if (OSUtil.Linux.isFedora()) {
|
||||||
// Fedora KDE requires GtkStatusIcon
|
// Fedora KDE requires GtkStatusIcon
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
} else {
|
} else {
|
||||||
// kde (at least, plasma 5.5.6) requires appindicator
|
// kde (at least, plasma 5.5.6) requires appindicator
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
// kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
|
// kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
|
||||||
@ -489,14 +472,9 @@ class SystemTray {
|
|||||||
// elementaryOS. It only supports appindicator (not gtkstatusicon)
|
// elementaryOS. It only supports appindicator (not gtkstatusicon)
|
||||||
// http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/view/head:/sample/SampleIndicator.vala
|
// 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 DE. Forcing native menus.");
|
|
||||||
useNativeMenus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ElementaryOS shows the checkbox on the right, everyone else is on the left.
|
// 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.
|
// With eOS, we CANNOT show the spacer image. It does not work.
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||||
}
|
}
|
||||||
else if ("gnome".equalsIgnoreCase(XDG)) {
|
else if ("gnome".equalsIgnoreCase(XDG)) {
|
||||||
// check other DE
|
// check other DE
|
||||||
@ -517,22 +495,22 @@ class SystemTray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this)
|
// 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this)
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if (OSUtil.Linux.isUbuntu()) {
|
else if (OSUtil.Linux.isUbuntu()) {
|
||||||
// so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works.
|
// so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works.
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if (OSUtil.Unix.isFreeBSD()) {
|
else if (OSUtil.Unix.isFreeBSD()) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// arch likely will have problems unless the correct/appropriate libraries are installed.
|
// arch likely will have problems unless the correct/appropriate libraries are installed.
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("cinnamon".equalsIgnoreCase(GDM)) {
|
else if ("cinnamon".equalsIgnoreCase(GDM)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("default".equalsIgnoreCase(GDM)) {
|
else if ("default".equalsIgnoreCase(GDM)) {
|
||||||
// this can be gnome3 on debian
|
// this can be gnome3 on debian
|
||||||
@ -542,16 +520,16 @@ class SystemTray {
|
|||||||
logger.warn("Debian with Gnome detected. SystemTray support is not known to work.");
|
logger.warn("Debian with Gnome detected. SystemTray support is not known to work.");
|
||||||
}
|
}
|
||||||
|
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
else if ("ubuntu".equalsIgnoreCase(GDM)) {
|
else if ("ubuntu".equalsIgnoreCase(GDM)) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectTypeQuietly(TrayType.AppIndicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,7 +562,7 @@ class SystemTray {
|
|||||||
if (readLine != null && readLine.contains("indicator-app")) {
|
if (readLine != null && readLine.contains("indicator-app")) {
|
||||||
// make sure we can also load the library (it might be the wrong version)
|
// make sure we can also load the library (it might be the wrong version)
|
||||||
try {
|
try {
|
||||||
trayType = selectType(useNativeMenus, TrayType.AppIndicator);
|
trayType = selectType(TrayType.AppIndicator);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e);
|
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e);
|
||||||
@ -609,7 +587,7 @@ class SystemTray {
|
|||||||
|
|
||||||
// fallback...
|
// fallback...
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
logger.warn("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.");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,18 +637,6 @@ class SystemTray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is likely windows OR mac
|
|
||||||
if (trayType == null) {
|
|
||||||
try {
|
|
||||||
trayType = selectType(useNativeMenus, TrayType.Swing);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
if (DEBUG) {
|
|
||||||
logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.", e);
|
|
||||||
} else {
|
|
||||||
logger.error("Maybe you should grant the AWTPermission `accessSystemTray` in the SecurityManager.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
// unsupported tray, or unknown type
|
// unsupported tray, or unknown type
|
||||||
@ -681,7 +647,7 @@ class SystemTray {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageUtils.determineIconSize(!useNativeMenus);
|
ImageUtils.determineIconSize();
|
||||||
|
|
||||||
final AtomicReference<Tray> reference = new AtomicReference<Tray>();
|
final AtomicReference<Tray> reference = new AtomicReference<Tray>();
|
||||||
|
|
||||||
@ -732,7 +698,7 @@ class SystemTray {
|
|||||||
if (isTrayType(trayType, TrayType.AppIndicator)) {
|
if (isTrayType(trayType, TrayType.AppIndicator)) {
|
||||||
if (Gtk.isGtk2 && AppIndicator.isVersion3) {
|
if (Gtk.isGtk2 && AppIndicator.isVersion3) {
|
||||||
try {
|
try {
|
||||||
trayType = selectType(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectType(TrayType.GtkStatusIcon);
|
||||||
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
|
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'");
|
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
@ -753,7 +719,7 @@ class SystemTray {
|
|||||||
if (!AppIndicator.isLoaded) {
|
if (!AppIndicator.isLoaded) {
|
||||||
// YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load.
|
// YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load.
|
||||||
logger.warn("Unable to initialize the AppIndicator correctly, falling back to GtkStatusIcon type");
|
logger.warn("Unable to initialize the AppIndicator correctly, falling back to GtkStatusIcon type");
|
||||||
trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon);
|
trayType = selectTypeQuietly(TrayType.GtkStatusIcon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -827,9 +793,7 @@ class SystemTray {
|
|||||||
// verify that we have what we are expecting.
|
// verify that we have what we are expecting.
|
||||||
if (OS.isWindows() && systemTrayMenu instanceof SwingUI) {
|
if (OS.isWindows() && systemTrayMenu instanceof SwingUI) {
|
||||||
// this configuration is OK.
|
// this configuration is OK.
|
||||||
} else if (useNativeMenus && systemTrayMenu instanceof NativeUI) {
|
} else if (systemTrayMenu instanceof NativeUI) {
|
||||||
// this configuration is OK.
|
|
||||||
} else if (!useNativeMenus && systemTrayMenu instanceof SwingUI) {
|
|
||||||
// this configuration is OK.
|
// this configuration is OK.
|
||||||
} else {
|
} else {
|
||||||
logger.error("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 " +
|
||||||
@ -879,22 +843,6 @@ class SystemTray {
|
|||||||
return "2.20";
|
return "2.20";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a SystemTray instance that uses a custom Swing menus, which is more advanced than the native menus. The drawback is that
|
|
||||||
* this menu is not native, and so loses the specific Look and Feel of that platform.
|
|
||||||
* <p>
|
|
||||||
* This always returns the same instance per JVM (it's a singleton), and on some platforms the system tray may not be
|
|
||||||
* supported, in which case this will return NULL.
|
|
||||||
* <p>
|
|
||||||
* If this is using the Swing SystemTray and a SecurityManager is installed, the AWTPermission {@code accessSystemTray} must
|
|
||||||
* be granted in order to get the {@code SystemTray} instance. Otherwise this will return null.
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
SystemTray getSwing() {
|
|
||||||
init(false);
|
|
||||||
return systemTray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables native menus on Linux/OSX instead of the custom swing menu. Windows will always use a custom Swing menu. The drawback is
|
* Enables native menus on Linux/OSX instead of the custom swing menu. Windows will always use a custom Swing menu. The drawback is
|
||||||
* that this menu is native, and sometimes native menus looks absolutely HORRID.
|
* that this menu is native, and sometimes native menus looks absolutely HORRID.
|
||||||
@ -906,8 +854,8 @@ class SystemTray {
|
|||||||
* be granted in order to get the {@code SystemTray} instance. Otherwise this will return null.
|
* be granted in order to get the {@code SystemTray} instance. Otherwise this will return null.
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
SystemTray getNative() {
|
SystemTray get() {
|
||||||
init(true);
|
init();
|
||||||
return systemTray;
|
return systemTray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,337 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014 dorkbox, llc
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package dorkbox.systemTray.swingUI;
|
|
||||||
|
|
||||||
import java.awt.MouseInfo;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
|
||||||
import com.sun.jna.ptr.PointerByReference;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.MenuItem;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
|
||||||
import dorkbox.systemTray.Tray;
|
|
||||||
import dorkbox.systemTray.gnomeShell.Extension;
|
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicator;
|
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
|
||||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
|
||||||
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
|
||||||
import dorkbox.util.SwingUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for handling all system tray interactions.
|
|
||||||
* specialization for using app indicators in ubuntu unity
|
|
||||||
*
|
|
||||||
* Derived from
|
|
||||||
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
|
|
||||||
*
|
|
||||||
* AppIndicators DO NOT support anything other than plain gtk-menus, because of how they use dbus so no tooltips AND no custom widgets
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* As a result of this decision by Canonical, we have to resort to hacks to get it to do what we want. BY NO MEANS IS THIS PERFECT.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* We still cannot have tooltips, but we *CAN* have custom widgets in the menu (because it's our swing menu now...)
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* It would be too much work to re-implement AppIndicators, or even to use LD_PRELOAD + restart service to do what we want.
|
|
||||||
*
|
|
||||||
* As a result, we have some wicked little hacks which are rather effective (but have a small side-effect of very briefly
|
|
||||||
* showing a blank menu)
|
|
||||||
*
|
|
||||||
* // What are AppIndicators?
|
|
||||||
* http://unity.ubuntu.com/projects/appindicators/
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* // Entry-point into appindicators
|
|
||||||
* http://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/services/panel-main.c
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* // The idiocy of appindicators
|
|
||||||
* https://bugs.launchpad.net/screenlets/+bug/522152
|
|
||||||
*
|
|
||||||
* // Code of how the dbus menus work
|
|
||||||
* http://bazaar.launchpad.net/~dbusmenu-team/libdbusmenu/trunk.16.10/view/head:/libdbusmenu-gtk/client.c
|
|
||||||
* https://developer.ubuntu.com/api/devel/ubuntu-12.04/c/dbusmenugtk/index.html
|
|
||||||
*
|
|
||||||
* // more info about trying to put widgets into GTK menus
|
|
||||||
* http://askubuntu.com/questions/16431/putting-an-arbitrary-gtk-widget-into-an-appindicator-indicator
|
|
||||||
*
|
|
||||||
* // possible idea on how to get GTK widgets into GTK menus
|
|
||||||
* https://launchpad.net/ido
|
|
||||||
* http://bazaar.launchpad.net/~canonical-dx-team/ido/trunk/view/head:/src/idoentrymenuitem.c
|
|
||||||
* http://bazaar.launchpad.net/~ubuntu-desktop/ido/gtk3/files
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
class _AppIndicatorSwingTray extends Tray implements SwingUI {
|
|
||||||
private volatile AppIndicatorInstanceStruct appIndicator;
|
|
||||||
private boolean isActive = false;
|
|
||||||
private volatile Runnable popupRunnable;
|
|
||||||
|
|
||||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
|
||||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
|
||||||
|
|
||||||
// necessary to prevent GC on these objects
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
|
||||||
private GEventCallback gtkCallback;
|
|
||||||
|
|
||||||
|
|
||||||
// necessary to provide a menu (which we draw over) so we get the "on open" event when the menu is opened via clicking
|
|
||||||
private Pointer dummyMenu;
|
|
||||||
|
|
||||||
// is the system tray visible or not.
|
|
||||||
private volatile boolean visible = true;
|
|
||||||
private volatile File imageFile;
|
|
||||||
|
|
||||||
// has the name already been set for the indicator?
|
|
||||||
private volatile boolean setName = false;
|
|
||||||
|
|
||||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
|
||||||
// they ALSO do not support tooltips!!
|
|
||||||
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
|
|
||||||
|
|
||||||
public
|
|
||||||
_AppIndicatorSwingTray(final SystemTray systemTray) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
Gtk.startGui();
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// we initialize with a blank image
|
|
||||||
File image = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
|
||||||
String id = System.nanoTime() + "DBST";
|
|
||||||
appIndicator = AppIndicator.app_indicator_new(id, image.getAbsolutePath(), AppIndicator.CATEGORY_APPLICATION_STATUS);
|
|
||||||
|
|
||||||
createAppIndicatorMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Gtk.waitForStartup();
|
|
||||||
|
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
|
||||||
final SwingMenu swingMenu = new SwingMenu(null) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setEnabled(final MenuItem menuItem) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
boolean enabled = menuItem.getEnabled();
|
|
||||||
|
|
||||||
if (visible && !enabled) {
|
|
||||||
// STATUS_PASSIVE hides the indicator
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
else if (!visible && enabled) {
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setImage(final MenuItem menuItem) {
|
|
||||||
imageFile = menuItem.getImage();
|
|
||||||
if (imageFile == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
|
|
||||||
|
|
||||||
if (!isActive) {
|
|
||||||
isActive = true;
|
|
||||||
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
|
||||||
|
|
||||||
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
|
||||||
hookMenuOpen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// needs to be on EDT
|
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((TrayPopup) _native).setTitleBarImage(imageFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setText(final MenuItem menuItem) {
|
|
||||||
// no op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final MenuItem menuItem) {
|
|
||||||
// no op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void remove() {
|
|
||||||
if (!shuttingDown.getAndSet(true)) {
|
|
||||||
// must happen asap, so our hook properly notices we are in shutdown mode
|
|
||||||
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
|
||||||
appIndicator = null;
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// STATUS_PASSIVE hides the indicator
|
|
||||||
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
|
|
||||||
Pointer p = savedAppIndicator.getPointer();
|
|
||||||
Gobject.g_object_unref(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
|
||||||
Gtk.shutdownGui();
|
|
||||||
|
|
||||||
super.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
|
||||||
popupMenu.pack();
|
|
||||||
popupMenu.setFocusable(true);
|
|
||||||
popupMenu.setOnHideRunnable(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
if (appIndicator == null) {
|
|
||||||
// if we are shutting down, don't hook the menu again
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
createAppIndicatorMenu();
|
|
||||||
hookMenuOpen();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
popupRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Point point = MouseInfo.getPointerInfo()
|
|
||||||
.getLocation();
|
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
|
||||||
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bind(swingMenu, null, systemTray);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void hookMenuOpen() {
|
|
||||||
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
|
||||||
PointerByReference menuServer = new PointerByReference();
|
|
||||||
PointerByReference rootMenuItem = new PointerByReference();
|
|
||||||
|
|
||||||
Gobject.g_object_get(appIndicator.getPointer(), "dbus-menu-server", menuServer, null);
|
|
||||||
Gobject.g_object_get(menuServer.getValue(), "root-node", rootMenuItem, null);
|
|
||||||
|
|
||||||
gtkCallback = new GEventCallback() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
|
||||||
Gtk.gtk_menu_shell_deactivate(dummyMenu);
|
|
||||||
SwingUtil.invokeLater(popupRunnable);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Gobject.g_signal_connect_object(rootMenuItem.getValue(), "about-to-show", gtkCallback, null, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void createAppIndicatorMenu() {
|
|
||||||
dummyMenu = Gtk.gtk_menu_new();
|
|
||||||
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("");
|
|
||||||
Gtk.gtk_menu_shell_append(dummyMenu, item);
|
|
||||||
Gtk.gtk_widget_show_all(item);
|
|
||||||
|
|
||||||
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
|
|
||||||
|
|
||||||
if (!setName) {
|
|
||||||
setName = true;
|
|
||||||
|
|
||||||
// in GNOME, by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
|
|
||||||
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
|
|
||||||
|
|
||||||
// can cause (potentially)
|
|
||||||
// GLib-GIO-CRITICAL **: g_dbus_connection_emit_signal: assertion 'object_path != NULL && g_variant_is_object_path (object_path)' failed
|
|
||||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
|
||||||
|
|
||||||
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
|
|
||||||
// in extension.js, so don't change it
|
|
||||||
|
|
||||||
// additionally, this is required to be set HERE (not somewhere else)
|
|
||||||
AppIndicator.app_indicator_set_title(appIndicator, Extension.DEFAULT_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
void setTooltip_(final String tooltipText) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
boolean hasImage() {
|
|
||||||
return imageFile != null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,259 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2014 dorkbox, llc
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package dorkbox.systemTray.swingUI;
|
|
||||||
|
|
||||||
import java.awt.MouseInfo;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.swing.JPopupMenu;
|
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.MenuItem;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
|
||||||
import dorkbox.systemTray.Tray;
|
|
||||||
import dorkbox.systemTray.gnomeShell.Extension;
|
|
||||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
|
||||||
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
|
||||||
import dorkbox.util.SwingUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for handling all system tray interactions via GTK.
|
|
||||||
* <p/>
|
|
||||||
* This is the "old" way to do it, and does not work with some desktop environments. This is a hybrid class, because we want to show the
|
|
||||||
* swing menu popup INSTEAD of GTK menu popups. The "golden standard" is our swing menu popup, since we have 100% control over it.
|
|
||||||
*
|
|
||||||
* http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
|
||||||
* https://github.com/djdeath/glib/blob/master/gobject/gobject.c
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
class _GtkStatusIconSwingTray extends Tray implements SwingUI {
|
|
||||||
private volatile Pointer trayIcon;
|
|
||||||
|
|
||||||
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
|
||||||
// see: https://github.com/java-native-access/jna/blob/master/www/CallbacksAndClosures.md
|
|
||||||
private GEventCallback gtkCallback = null;
|
|
||||||
|
|
||||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
|
||||||
|
|
||||||
private volatile boolean isActive = false;
|
|
||||||
|
|
||||||
// is the system tray visible or not.
|
|
||||||
private volatile boolean visible = true;
|
|
||||||
private volatile File imageFile;
|
|
||||||
private volatile Runnable popupRunnable;
|
|
||||||
|
|
||||||
// called on the EDT
|
|
||||||
public
|
|
||||||
_GtkStatusIconSwingTray(final SystemTray systemTray) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
Gtk.startGui();
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
trayIcon = Gtk.gtk_status_icon_new();
|
|
||||||
|
|
||||||
gtkCallback = new GEventCallback() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void callback(Pointer notUsed, final GdkEventButton event) {
|
|
||||||
// show the swing menu on the EDT
|
|
||||||
// BUTTON_PRESS only (any mouse click)
|
|
||||||
if (event.type == 4) {
|
|
||||||
// show the swing menu on the EDT
|
|
||||||
SwingUtil.invokeLater(popupRunnable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, null, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Gtk.waitForStartup();
|
|
||||||
|
|
||||||
// we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// in GNOME by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
|
|
||||||
// If you change "SystemTray" to something else, make sure to change it in extension.js as well
|
|
||||||
|
|
||||||
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
|
|
||||||
// in extension.js, so don't change it
|
|
||||||
Gtk.gtk_status_icon_set_title(trayIcon, Extension.DEFAULT_NAME);
|
|
||||||
|
|
||||||
// can cause
|
|
||||||
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
|
|
||||||
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
|
|
||||||
|
|
||||||
// ... so, bizzaro things going on here. These errors DO NOT happen if JavaFX or Gnome is dispatching the events.
|
|
||||||
// BUT this is REQUIRED when running JavaFX or Gnome For unknown reasons, the title isn't pushed to GTK, so our
|
|
||||||
// gnome-shell extension cannot see our tray icon -- so naturally, it won't move it to the "top" area and
|
|
||||||
// we appear broken.
|
|
||||||
if (SystemTray.isJavaFxLoaded || Tray.usingGnome) {
|
|
||||||
Gtk.gtk_status_icon_set_name(trayIcon, Extension.DEFAULT_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
final SwingMenu swingMenu = new SwingMenu(null) {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setEnabled(final MenuItem menuItem) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
boolean enabled = menuItem.getEnabled();
|
|
||||||
|
|
||||||
if (visible && !enabled) {
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
else if (!visible && enabled) {
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setImage(final MenuItem menuItem) {
|
|
||||||
imageFile = menuItem.getImage();
|
|
||||||
if (imageFile == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Gtk.gtk_status_icon_set_from_file(trayIcon, imageFile.getAbsolutePath());
|
|
||||||
|
|
||||||
if (!isActive) {
|
|
||||||
isActive = true;
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// needs to be on EDT
|
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((TrayPopup) _native).setTitleBarImage(imageFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setText(final MenuItem menuItem) {
|
|
||||||
// no op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final MenuItem menuItem) {
|
|
||||||
// no op
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void remove() {
|
|
||||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
|
||||||
if (!shuttingDown.getAndSet(true)) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// this hides the indicator
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
|
||||||
Gobject.g_object_unref(trayIcon);
|
|
||||||
|
|
||||||
// mark for GC
|
|
||||||
trayIcon = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
|
||||||
Gtk.shutdownGui();
|
|
||||||
|
|
||||||
super.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
JPopupMenu popupMenu = (JPopupMenu) swingMenu._native;
|
|
||||||
popupMenu.pack();
|
|
||||||
popupMenu.setFocusable(true);
|
|
||||||
|
|
||||||
popupRunnable = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Point point = MouseInfo.getPointerInfo()
|
|
||||||
.getLocation();
|
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
|
||||||
popupMenu.doShow(point, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
bind(swingMenu, null, systemTray);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// do we need to install the GNOME extension??
|
|
||||||
Tray.installExtension();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
void setTooltip_(final String tooltipText) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Gtk.gtk_status_icon_set_tooltip_text(trayIcon, tooltipText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasImage() {
|
|
||||||
return imageFile != null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -75,7 +75,7 @@ class ImageUtils {
|
|||||||
public static volatile Font ENTRY_FONT = null;
|
public static volatile Font ENTRY_FONT = null;
|
||||||
|
|
||||||
public static
|
public static
|
||||||
void determineIconSize(boolean trayHasSwingMenus) {
|
void determineIconSize() {
|
||||||
double trayScalingFactor = 0;
|
double trayScalingFactor = 0;
|
||||||
double menuScalingFactor = 0;
|
double menuScalingFactor = 0;
|
||||||
|
|
||||||
@ -295,8 +295,8 @@ class ImageUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this must be a JMenuItem component, because that is the component we are setting the font on.
|
// this must be a JMenuItem component, because that is the component we are setting the font on.
|
||||||
// this is only important to do if we are a swing tray type
|
// this is only important to do if we are a swing tray type, which ONLY happens in Windows
|
||||||
if (trayHasSwingMenus) {
|
if (OS.isWindows()) {
|
||||||
// must be a plain style font
|
// must be a plain style font
|
||||||
Font font = new JMenuItem().getFont().deriveFont(Font.PLAIN);
|
Font font = new JMenuItem().getFont().deriveFont(Font.PLAIN);
|
||||||
|
|
||||||
|
@ -56,8 +56,7 @@ class TestTray {
|
|||||||
|
|
||||||
public
|
public
|
||||||
TestTray() {
|
TestTray() {
|
||||||
this.systemTray = SystemTray.getSwing();
|
this.systemTray = SystemTray.get();
|
||||||
// this.systemTray = SystemTray.getNative();
|
|
||||||
if (systemTray == null) {
|
if (systemTray == null) {
|
||||||
throw new RuntimeException("Unable to load SystemTray!");
|
throw new RuntimeException("Unable to load SystemTray!");
|
||||||
}
|
}
|
||||||
|
@ -114,8 +114,7 @@ class TestTrayJavaFX {
|
|||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
|
|
||||||
|
|
||||||
this.systemTray = SystemTray.getSwing();
|
this.systemTray = SystemTray.get();
|
||||||
// this.systemTray = SystemTray.getNative();
|
|
||||||
if (systemTray == null) {
|
if (systemTray == null) {
|
||||||
throw new RuntimeException("Unable to load SystemTray!");
|
throw new RuntimeException("Unable to load SystemTray!");
|
||||||
}
|
}
|
||||||
|
@ -73,13 +73,12 @@ class TestTraySwt {
|
|||||||
helloWorldTest.pack();
|
helloWorldTest.pack();
|
||||||
|
|
||||||
|
|
||||||
systemTray.setTooltip("Mail Checker");
|
this.systemTray = SystemTray.get();
|
||||||
this.systemTray = SystemTray.getSwing();
|
|
||||||
// this.systemTray = SystemTray.getNative();
|
|
||||||
if (systemTray == null) {
|
if (systemTray == null) {
|
||||||
throw new RuntimeException("Unable to load SystemTray!");
|
throw new RuntimeException("Unable to load SystemTray!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
systemTray.setTooltip("Mail Checker");
|
||||||
systemTray.setImage(LT_GRAY_TRAIN);
|
systemTray.setImage(LT_GRAY_TRAIN);
|
||||||
systemTray.setStatus("No Mail");
|
systemTray.setStatus("No Mail");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user