Removing logic to choose between swing/native menus (Swing w/

appindicators is broken)
This commit is contained in:
nathan 2017-01-30 01:33:19 +01:00
parent ec9499c1bb
commit 3c24139544
7 changed files with 66 additions and 717 deletions

View File

@ -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.
* <p>
* 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<? extends Tray> 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<? 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 (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<? extends Tray> selectTypeQuietly(final boolean useNativeMenus, final TrayType trayType) {
Class<? extends Tray> 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<Tray> reference = new AtomicReference<Tray>();
@ -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.
* <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
* 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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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!");
}

View File

@ -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!");
}

View File

@ -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");