Added support for tooltips. note: AppIndicators DO NOT support tooltips

This commit is contained in:
nathan 2017-01-30 00:23:52 +01:00
parent 9e3af1a299
commit ec9499c1bb
13 changed files with 200 additions and 53 deletions

View File

@ -33,16 +33,6 @@ This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windo
 
We also cater to the *lowest-common-denominator* when it comes to system-tray functionality, and there are some features that we don't support.
````
Specifically: mouse-over tooltips
````
Rather a stupid decision, IMHO, but for more information why, [ask Mark Shuttleworth](https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12). Also, some linux systems only support right-click to display the menu, and it is not possible to change that behavior.
 
Problems and Restrictions
---------
- **JavaFX** uses *GTK2* for Java <8, and *GTK2* or *GTK3* for Java 9+. We try to autodetect this, and are mostly successful. In *some* situations where it doesn't work. Please set `SystemTray.FORCE_GTK2=true;`, or to change JavaFX (9+), use `-Djdk.gtk.version=3` to solve this.
@ -54,6 +44,10 @@ Problems and Restrictions
- **MacOSX** is a *special snowflake* in how it handles GUI events, and so there are some bizzaro combinations of SWT, JavaFX, and Swing that do not work together (see the `Notes` below for the details.)
- **Gnome3** (Fedora, Manjaro, Arch, etc) environments by default **do not** allow the SystemTray icon to be shown. This has been worked around (it will be placed next to the clock) for most Gnome environments, except for Arch linux. Another workaround is to install the [Top Icons plugin](https://extensions.gnome.org/extension/1031/topicons/) plugin which moves icons from the *notification drawer* (it is normally collapsed) at the bottom left corner of the screen to the menu panel next to the clock.
- **ToolTips** The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. Please note that **Ubuntu** does not always support this!
- **Linux/Unix Menus** Some linux environments only support right-click to display the menu, and it is not possible to change the behavior.
Compatibility Matrix
------------------

View File

@ -39,7 +39,6 @@ import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.jna.linux.AppIndicator;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.nativeUI.NativeUI;
@ -508,20 +507,7 @@ class SystemTray {
}
if ("gnome".equalsIgnoreCase(GDM)) {
if (OSUtil.Linux.isArch()) {
if (DEBUG) {
logger.debug("Running Arch Linux.");
}
if (!Extension.isInstalled()) {
logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " +
"the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " +
"icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " +
"of the screen to the menu panel next to the clock.");
}
} else {
// Automatically install the extension for everyone except Arch. It's bonkers.
Extension.install();
}
Tray.usingGnome = true;
// are we fedora? If so, what version?
// now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon
@ -776,11 +762,6 @@ class SystemTray {
if (isJavaFxLoaded) {
if (isTrayType(trayType, TrayType.GtkStatusIcon)) {
// set a property so that GTK (if necessary) can set the name of the system tray icon
System.setProperty("SystemTray_SET_NAME", "true");
}
// This will initialize javaFX dispatch methods
JavaFX.init();
}
@ -790,7 +771,6 @@ class SystemTray {
}
if ((isJavaFxLoaded || isSwtLoaded) && SwingUtilities.isEventDispatchThread()) {
// oh boy! This WILL NOT WORK. Let the dev know
logger.error("SystemTray initialization for JavaFX or SWT **CAN NOT** occur on the Swing Event Dispatch Thread " +
@ -1028,6 +1008,25 @@ class SystemTray {
}
}
/**
* Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name.
* <p>
* The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop
* Environments.
* <p>
* For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12.
*
* @param tooltipText the text to use as tooltip for the tray icon, null to remove
*/
public
void setTooltip(final String tooltipText) {
final Tray tray = systemTrayMenu;
if (tray != null) {
tray.setTooltip(tooltipText);
}
}
/**
* Specifies the new image to set for a menu entry, NULL to delete the image
* <p>

View File

@ -22,13 +22,40 @@ import java.net.URL;
import javax.imageio.stream.ImageInputStream;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.util.OSUtil;
// This is public ONLY so that it is in the scope for SwingUI and NativeUI system tray components
public
class Tray extends Menu {
// true if we are using gnome (and things depend on it) or false
public static volatile boolean usingGnome = false;
protected static
void installExtension() {
// do we need to install the GNOME extension??
if (Tray.usingGnome) {
if (OSUtil.Linux.isArch()) {
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Running Arch Linux.");
}
if (!Extension.isInstalled()) {
SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " +
"the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " +
"icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " +
"of the screen to the menu panel next to the clock.");
}
} else {
// Automatically install the extension for everyone except Arch. It's bonkers.
Extension.install();
}
}
}
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// they ALSO do not support tooltips, so we cater to the lowest common denominator
// they ALSO do not support tooltips!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
private volatile String statusText;
@ -48,7 +75,7 @@ class Tray extends Menu {
/**
* Sets a 'status' string at the first position in the popup menu. This 'status' string appears as a disabled menu entry.
*
* @param statusText the text you want displayed, null if you want to remove the 'status' string
* @param statusText the text you want displayed, null if you want to remove the 'status' text
*/
public
void setStatus(final String statusText) {
@ -86,6 +113,32 @@ class Tray extends Menu {
}
}
// method that is meant to be overridden by the tray implementations
protected
void setTooltip_(final String tooltipText) {
// default is NO OP
}
/**
* Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name.
* <p>
* The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop
* Environments.
* <p>
* For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12.
*
* @param tooltipText the text to use as tooltip for the tray icon, null to remove
*/
final
void setTooltip(final String tooltipText) {
// this is a safety precaution, since the behavior of really long text is undefined.
if (tooltipText.length() > 64) {
throw new RuntimeException("Tooltip text cannot be longer than 64 characters.");
}
setTooltip_(tooltipText);
}
/**
* Specifies the new image to set for the tray icon.
* <p>

View File

@ -540,8 +540,8 @@ class Gtk {
public static native void gtk_status_icon_set_visible(Pointer widget, boolean visible);
// app indicators don't support this, and we cater to the lowest common denominator
// public static native void gtk_status_icon_set_tooltip(Pointer widget, String tooltipText);
// app indicators don't support this
public static native void gtk_status_icon_set_tooltip_text(Pointer widget, String tooltipText);
public static native void gtk_status_icon_set_title(Pointer widget, String titleText);

View File

@ -23,6 +23,7 @@ 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.AppIndicator;
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
import dorkbox.systemTray.jna.linux.Gobject;
@ -90,10 +91,9 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
// 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, so we cater to the lowest common denominator
// trayIcon.setToolTip("app name");
// they ALSO do not support tooltips!!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
public
_AppIndicatorNativeTray(final SystemTray systemTray) {
@ -116,7 +116,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
if (!setName) {
setName = true;
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
// 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)
@ -127,7 +127,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
// 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, "SystemTray");
AppIndicator.app_indicator_set_title(appIndicator, Extension.DEFAULT_NAME);
}
}
@ -232,6 +232,12 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
bind(gtkMenu, null, systemTray);
}
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
@Override
protected
void setTooltip_(final String tooltipText) {
}
@Override
public final
boolean hasImage() {

View File

@ -49,6 +49,7 @@ class _AwtTray extends Tray implements NativeUI {
// is the system tray visible or not.
private volatile boolean visible = true;
private volatile File imageFile;
private volatile String tooltipText = null;
private final Object keepAliveLock = new Object[0];
private Thread keepAliveThread;
@ -113,6 +114,10 @@ class _AwtTray extends Tray implements NativeUI {
try {
tray.add(trayIcon);
visible = true;
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
trayIcon.setToolTip(tooltipText);
} catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
}
@ -151,6 +156,10 @@ class _AwtTray extends Tray implements NativeUI {
} else {
trayIcon.setImage(trayImage);
}
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
trayIcon.setToolTip(tooltipText);
}
});
}
@ -191,6 +200,24 @@ class _AwtTray extends Tray implements NativeUI {
bind(awtMenu, null, systemTray);
}
@Override
protected
void setTooltip_(final String tooltipText) {
this.tooltipText = tooltipText;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(tooltipText);
}
}
});
}
@Override
public
boolean hasImage() {

View File

@ -23,6 +23,7 @@ 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;
@ -174,12 +175,12 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
@Override
public
void run() {
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
// 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, "SystemTray");
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
@ -189,13 +190,28 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
// 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 (System.getProperty("SystemTray_SET_NAME", "false").equals("true")) {
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
if (SystemTray.isJavaFxLoaded || Tray.usingGnome) {
Gtk.gtk_status_icon_set_name(trayIcon, Extension.DEFAULT_NAME);
}
}
});
bind(gtkMenu, 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

View File

@ -26,6 +26,7 @@ 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;
@ -106,8 +107,8 @@ class _AppIndicatorSwingTray extends Tray implements SwingUI {
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, so we cater to the lowest common denominator
// trayIcon.setToolTip("app name");
// they ALSO do not support tooltips!!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
public
_AppIndicatorSwingTray(final SystemTray systemTray) {
@ -307,7 +308,7 @@ class _AppIndicatorSwingTray extends Tray implements SwingUI {
if (!setName) {
setName = true;
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
// 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)
@ -318,10 +319,16 @@ class _AppIndicatorSwingTray extends Tray implements SwingUI {
// 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, "SystemTray");
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() {

View File

@ -27,6 +27,7 @@ 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;
@ -96,12 +97,12 @@ class _GtkStatusIconSwingTray extends Tray implements SwingUI {
@Override
public
void run() {
// by default, the title/name of the tray icon is "java". We are the only java-based tray icon, so we just use that.
// 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, "SystemTray");
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
@ -111,8 +112,8 @@ class _GtkStatusIconSwingTray extends Tray implements SwingUI {
// 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 (System.getProperty("SystemTray_SET_NAME", "false").equals("true")) {
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
if (SystemTray.isJavaFxLoaded || Tray.usingGnome) {
Gtk.gtk_status_icon_set_name(trayIcon, Extension.DEFAULT_NAME);
}
}
});
@ -230,10 +231,24 @@ class _GtkStatusIconSwingTray extends Tray implements SwingUI {
}
};
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

View File

@ -47,6 +47,7 @@ class _SwingTray extends Tray implements SwingUI {
// is the system tray visible or not.
private volatile boolean visible = true;
private volatile File imageFile;
private volatile String tooltipText = null;
// Called in the EDT
public
@ -79,6 +80,10 @@ class _SwingTray extends Tray implements SwingUI {
try {
tray.add(trayIcon);
visible = true;
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
trayIcon.setToolTip(tooltipText);
} catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
}
@ -133,6 +138,10 @@ class _SwingTray extends Tray implements SwingUI {
trayIcon.setImage(trayImage);
}
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
trayIcon.setToolTip(tooltipText);
((TrayPopup) _native).setTitleBarImage(imageFile);
}
});
@ -173,6 +182,24 @@ class _SwingTray extends Tray implements SwingUI {
bind(swingMenu, null, systemTray);
}
@Override
protected
void setTooltip_(final String tooltipText) {
this.tooltipText = tooltipText;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(tooltipText);
}
}
});
}
@Override
public
boolean hasImage() {

View File

@ -62,6 +62,7 @@ class TestTray {
throw new RuntimeException("Unable to load SystemTray!");
}
systemTray.setTooltip("Mail Checker");
systemTray.setImage(LT_GRAY_TRAIN);
systemTray.setStatus("No Mail");

View File

@ -120,6 +120,7 @@ class TestTrayJavaFX {
throw new RuntimeException("Unable to load SystemTray!");
}
systemTray.setTooltip("Mail Checker");
systemTray.setImage(LT_GRAY_TRAIN);
systemTray.setStatus("No Mail");

View File

@ -73,6 +73,7 @@ class TestTraySwt {
helloWorldTest.pack();
systemTray.setTooltip("Mail Checker");
this.systemTray = SystemTray.getSwing();
// this.systemTray = SystemTray.getNative();
if (systemTray == null) {