From 3c2413954472b1f9ced7a7114b244ff251b41c9e Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 30 Jan 2017 01:33:19 +0100 Subject: [PATCH] Removing logic to choose between swing/native menus (Swing w/ appindicators is broken) --- src/dorkbox/systemTray/SystemTray.java | 170 +++------ .../swingUI/_AppIndicatorSwingTray.java | 337 ------------------ .../swingUI/_GtkStatusIconSwingTray.java | 259 -------------- src/dorkbox/systemTray/util/ImageUtils.java | 6 +- test/dorkbox/TestTray.java | 3 +- test/dorkbox/TestTrayJavaFX.java | 3 +- test/dorkbox/TestTraySwt.java | 5 +- 7 files changed, 66 insertions(+), 717 deletions(-) delete mode 100644 src/dorkbox/systemTray/swingUI/_AppIndicatorSwingTray.java delete mode 100644 src/dorkbox/systemTray/swingUI/_GtkStatusIconSwingTray.java diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index dfb689d..73ea99a 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -46,8 +46,6 @@ import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray; import dorkbox.systemTray.nativeUI._AwtTray; import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray; import dorkbox.systemTray.swingUI.SwingUI; -import dorkbox.systemTray.swingUI._AppIndicatorSwingTray; -import dorkbox.systemTray.swingUI._GtkStatusIconSwingTray; import dorkbox.systemTray.swingUI._SwingTray; import dorkbox.systemTray.util.ImageUtils; import dorkbox.systemTray.util.JavaFX; @@ -81,14 +79,12 @@ 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 */ + /** Will choose as a 'best guess' which tray type to use */ 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 + Swing, + AWT } @Property @@ -128,7 +124,7 @@ class SystemTray { @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. *

* This is an advanced feature, and it is recommended to leave at AutoDetect. */ @@ -194,45 +190,36 @@ class SystemTray { private static boolean isTrayType(final Class tray, final TrayType trayType) { switch (trayType) { - case GtkStatusIcon: return (tray == _GtkStatusIconSwingTray.class || tray == _GtkStatusIconNativeTray.class); - case AppIndicator: return (tray == _AppIndicatorSwingTray.class || tray == _AppIndicatorNativeTray.class); - case Swing: return (tray == _SwingTray.class || tray == _AwtTray.class); + case GtkStatusIcon: return tray == _GtkStatusIconNativeTray.class; + case AppIndicator: return tray == _AppIndicatorNativeTray.class; + case Swing: return tray == _SwingTray.class; + case AWT: return tray == _AwtTray.class; } return false; } private static - Class selectType(final boolean useNativeMenus, final TrayType trayType) throws Exception { + Class selectType(final TrayType trayType) throws Exception { if (trayType == TrayType.GtkStatusIcon) { - if (useNativeMenus) { - return _GtkStatusIconNativeTray.class; - } else { - return _GtkStatusIconSwingTray.class; - } - } else if (trayType == TrayType.AppIndicator) { - if (useNativeMenus) { - return _AppIndicatorNativeTray.class; - } - else { - return _AppIndicatorSwingTray.class; - } + return _GtkStatusIconNativeTray.class; + } + else if (trayType == TrayType.AppIndicator) { + return _AppIndicatorNativeTray.class; } else if (trayType == TrayType.Swing) { - if (useNativeMenus) { - return _AwtTray.class; - } - else { - return _SwingTray.class; - } + return _SwingTray.class; + } + else if (trayType == TrayType.AWT) { + return _AwtTray.class; } return null; } private static - Class selectTypeQuietly(final boolean useNativeMenus, final TrayType trayType) { + Class selectTypeQuietly(final TrayType trayType) { try { - return selectType(useNativeMenus, trayType); + return selectType(trayType); } catch (Throwable t) { if (DEBUG) { logger.error("Cannot initialize {}", trayType.name(), t); @@ -243,7 +230,7 @@ class SystemTray { } @SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"}) - private static void init(boolean useNativeMenus) { + private static void init() { if (systemTray != null) { 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 // 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 @@ -298,28 +281,17 @@ class SystemTray { // 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 (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"); + logger.warn("Windows cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Swing"); } } 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 { - 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; + logger.warn("MacOS cannot use the '" + FORCE_TRAY_TYPE + "' SystemTray type, defaulting to Awt"); } } else if (OS.isLinux() || OS.isUnix()) { @@ -399,7 +371,6 @@ class SystemTray { 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); } @@ -413,10 +384,22 @@ class SystemTray { if (OS.isWindows()) { // windows is funky, and is hardcoded to 16x16. We fix that. 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) 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) { // 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 // don't check for SWING type at this spot, it is done elsewhere. 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) { // Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell". if ("unity".equalsIgnoreCase(XDG)) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.AppIndicator); + trayType = selectTypeQuietly(TrayType.AppIndicator); } else if ("xfce".equalsIgnoreCase(XDG)) { // 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 // 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)) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else if ("kde".equalsIgnoreCase(XDG)) { if (OSUtil.Linux.isFedora()) { // Fedora KDE requires GtkStatusIcon - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else { // 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 @@ -489,14 +472,9 @@ class SystemTray { // 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 DE. Forcing native menus."); - useNativeMenus = true; - } - // 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(useNativeMenus, TrayType.AppIndicator); + trayType = selectTypeQuietly(TrayType.AppIndicator); } else if ("gnome".equalsIgnoreCase(XDG)) { // 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) - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else if (OSUtil.Linux.isUbuntu()) { // 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()) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else { // 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)) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else if ("default".equalsIgnoreCase(GDM)) { // 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."); } - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else if ("gnome-classic".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } else if ("gnome-fallback".equalsIgnoreCase(GDM)) { - trayType = selectTypeQuietly(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectTypeQuietly(TrayType.GtkStatusIcon); } 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")) { // make sure we can also load the library (it might be the wrong version) try { - trayType = selectType(useNativeMenus, TrayType.AppIndicator); + trayType = selectType(TrayType.AppIndicator); } catch (Exception e) { if (DEBUG) { logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK", e); @@ -609,7 +587,7 @@ class SystemTray { // fallback... 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."); } @@ -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) { // unsupported tray, or unknown type @@ -681,7 +647,7 @@ class SystemTray { return; } - ImageUtils.determineIconSize(!useNativeMenus); + ImageUtils.determineIconSize(); final AtomicReference reference = new AtomicReference(); @@ -732,7 +698,7 @@ class SystemTray { if (isTrayType(trayType, TrayType.AppIndicator)) { if (Gtk.isGtk2 && AppIndicator.isVersion3) { try { - trayType = selectType(useNativeMenus, TrayType.GtkStatusIcon); + trayType = selectType(TrayType.GtkStatusIcon); 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'"); } catch (Throwable e) { @@ -753,7 +719,7 @@ class SystemTray { if (!AppIndicator.isLoaded) { // YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load. 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. if (OS.isWindows() && systemTrayMenu instanceof SwingUI) { // this configuration is OK. - } else if (useNativeMenus && systemTrayMenu instanceof NativeUI) { - // this configuration is OK. - } else if (!useNativeMenus && systemTrayMenu instanceof SwingUI) { + } else if (systemTrayMenu instanceof NativeUI) { // this configuration is OK. } else { 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"; } - /** - * 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. - *

- * 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. - *

- * 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 * 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. */ public static - SystemTray getNative() { - init(true); + SystemTray get() { + init(); return systemTray; } diff --git a/src/dorkbox/systemTray/swingUI/_AppIndicatorSwingTray.java b/src/dorkbox/systemTray/swingUI/_AppIndicatorSwingTray.java deleted file mode 100644 index a7ae489..0000000 --- a/src/dorkbox/systemTray/swingUI/_AppIndicatorSwingTray.java +++ /dev/null @@ -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; - } -} diff --git a/src/dorkbox/systemTray/swingUI/_GtkStatusIconSwingTray.java b/src/dorkbox/systemTray/swingUI/_GtkStatusIconSwingTray.java deleted file mode 100644 index e1aa7e4..0000000 --- a/src/dorkbox/systemTray/swingUI/_GtkStatusIconSwingTray.java +++ /dev/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. - *

- * 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; - } -} diff --git a/src/dorkbox/systemTray/util/ImageUtils.java b/src/dorkbox/systemTray/util/ImageUtils.java index d5a64ce..c1c7890 100644 --- a/src/dorkbox/systemTray/util/ImageUtils.java +++ b/src/dorkbox/systemTray/util/ImageUtils.java @@ -75,7 +75,7 @@ class ImageUtils { public static volatile Font ENTRY_FONT = null; public static - void determineIconSize(boolean trayHasSwingMenus) { + void determineIconSize() { double trayScalingFactor = 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 is only important to do if we are a swing tray type - if (trayHasSwingMenus) { + // this is only important to do if we are a swing tray type, which ONLY happens in Windows + if (OS.isWindows()) { // must be a plain style font Font font = new JMenuItem().getFont().deriveFont(Font.PLAIN); diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index c093786..0bca60e 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -56,8 +56,7 @@ class TestTray { public TestTray() { - this.systemTray = SystemTray.getSwing(); - // this.systemTray = SystemTray.getNative(); + this.systemTray = SystemTray.get(); if (systemTray == null) { throw new RuntimeException("Unable to load SystemTray!"); } diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index c669f91..2c91f05 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -114,8 +114,7 @@ class TestTrayJavaFX { primaryStage.show(); - this.systemTray = SystemTray.getSwing(); - // this.systemTray = SystemTray.getNative(); + this.systemTray = SystemTray.get(); if (systemTray == null) { throw new RuntimeException("Unable to load SystemTray!"); } diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index 563bbc9..a93f685 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -73,13 +73,12 @@ class TestTraySwt { helloWorldTest.pack(); - systemTray.setTooltip("Mail Checker"); - this.systemTray = SystemTray.getSwing(); - // this.systemTray = SystemTray.getNative(); + this.systemTray = SystemTray.get(); if (systemTray == null) { throw new RuntimeException("Unable to load SystemTray!"); } + systemTray.setTooltip("Mail Checker"); systemTray.setImage(LT_GRAY_TRAIN); systemTray.setStatus("No Mail");