Added GTK3 support (now libappindicator3 works correctly with regards to showing menu icons). Added support for setting icons via URL and InputStream. Moved all GTK operations to a single thread. Moved error logs from System.err -> logger. Made SystemTray a singleton (it will no longer have the java-side get accidentally garbage collected). Update documentation

This commit is contained in:
nathan 2016-02-13 15:06:19 +01:00
parent 8b109f6d1c
commit c1b6c1a723
11 changed files with 465 additions and 394 deletions

View File

@ -19,7 +19,7 @@ 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/indicator functionality, and there are some features that we don't support.
Specifically, **tooltips**. Rather a stupid decision, IMHO, but for more information why ask Mark Shuttleworth.
Specifically: **tooltips**. Rather a stupid decision, IMHO, but for more information why, ask Mark Shuttleworth.
See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
```
@ -44,6 +44,11 @@ GnomeShellExtension.SHELL_RESTART_COMMAND (type String, default value 'gnome-s
SystemTray.TRAY_SIZE (type int, default value '24')
- Size of the tray, so that the icon can properly scale based on OS. (if it's not exact). This only applies for Swing tray icons.
- NOTE: Must be set after any other customization options, as a static call to SystemTray will cause initialization of the library.
GtkSupport.CREATE_EVENT_LOOP (type boolean, default value 'true')
- Enables/Disables the creation of a native GTK event loop. Useful if you are already creating one via SWT/etc.
```
@ -73,17 +78,6 @@ The test application is [on GitHub](https://github.com/dorkbox/SystemTray/blob/m
```
```
Note: This library does NOT use SWT for system-tray support, only for the purpose
of lessening the jar dependencies. Changing it to be SWT-based is not
difficult, just remember that SWT on linux *already* starts up the GTK main
event loop.
```
```
Note: If you use the attached JNA libraries, you **MUST** load the respective
native libraries yourself, especially with JNA (as the loading logic has
been removed from the jar)
```
```
Note: This project was heavily influenced by the excellent Lantern project,
*Many* thanks to them for figuring out AppIndicators via JNA.
@ -99,8 +93,8 @@ Note: Gnome-shell users will experience an extension install to support this
Also, screw you gnome-project leads, for making it such a pain-in-the-ass
to do something so incredibly simple and basic.
Note: Some desktop environments might use a dated version of libappindicator, when
icon support in menus was removed, then put back. This happened in version 3.
Note: Some desktop environments might use a dated version of libappindicator3, when
icon support in menus was removed, then put back.
This library will try to load a GTK indicator instead when it can, or will
try to load libappindicator1 first. Thank you RedHat for putting it back.
@ -133,7 +127,7 @@ This project is **kept in sync** with the utilities library, so "jar hell" is no
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>SystemTray</artifactId>
<version>2.0</version>
<version>2.1</version>
</dependency>
```

View File

@ -22,6 +22,7 @@
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
<orderEntry type="library" name="logging slf4j-api (1.7.5)" level="application" />
<orderEntry type="library" name="logging logback (1.0.13)" level="application" />
<orderEntry type="module" module-name="Dorkbox-Util" />
<orderEntry type="module" module-name="JavaLauncher-Util" />
<orderEntry type="library" name="jna (4.2.1)" level="application" />

View File

@ -209,7 +209,7 @@ class ImageUtil {
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US);
}
public static
public static synchronized
void init() throws NoSuchAlgorithmException {
ImageUtil.digest = MessageDigest.getInstance("MD5");
}

View File

@ -22,7 +22,6 @@ import dorkbox.systemTray.swing.SwingSystemTray;
import dorkbox.util.OS;
import dorkbox.util.Property;
import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.AppIndicatorQuery;
import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.process.ShellProcessBuilder;
import org.slf4j.Logger;
@ -44,7 +43,7 @@ import java.util.Iterator;
/**
* Factory and base-class for system tray implementations.
*/
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "Duplicates"})
public abstract
class SystemTray {
protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
@ -70,7 +69,6 @@ class SystemTray {
}
if (OS.isLinux()) {
if (GtkSupport.isSupported) {
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
@ -82,15 +80,15 @@ class SystemTray {
}
}
else if ("XFCE".equalsIgnoreCase(XDG)) {
// XFCE uses a BAD version of libappindicator by default, which DOES NOT support images in the menu.
// if we have libappindicator1, we are OK. if we don't, fallback to GTKSystemTray
try {
if (AppIndicatorQuery.get_v1() != null) {
trayType = AppIndicatorTray.class;
} else {
trayType = GtkSystemTray.class;
}
} catch (Throwable ignored) {
// we can fail on AppIndicator, so this is the fallback
//noinspection EmptyCatchBlock
try {
trayType = GtkSystemTray.class;
} catch (Throwable i) {
}
}
}
else if ("LXDE".equalsIgnoreCase(XDG)) {
@ -188,6 +186,10 @@ class SystemTray {
//noinspection unused
final AppIndicator instance = AppIndicator.INSTANCE;
trayType = AppIndicatorTray.class;
if (AppIndicator.IS_VERSION_3) {
}
} catch (Throwable e) {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
e.printStackTrace();
@ -221,7 +223,6 @@ class SystemTray {
"configuration");
}
}
}
// this is windows OR mac
if (trayType == null && java.awt.SystemTray.isSupported()) {
@ -242,11 +243,30 @@ class SystemTray {
SystemTray systemTray_ = null;
try {
ImageUtil.init();
if (AppIndicator.IS_VERSION_3 && GtkSupport.isGtk2) {
// NOTE:
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
// appindicator3 doesn't support menu icons via GTK2. AT THIS POINT, we DO NOT have GTK3
try {
trayType = GtkSystemTray.class;
logger.warn("AppIndicator3 detected with GTK2 only, falling back to GTK2 system tray type. " +
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
} catch (Throwable ignored) {
logger.error("AppIndicator3 detected with GTK2 only and unable to fallback to using GTK2 system tray type." +
"AppIndicator3 requires GTK3 to be fully functional, and while this will work -- the menu icons WILL " +
"NOT be visible." +
" Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
}
}
systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance();
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
} catch (NoSuchAlgorithmException e) {
logger.error("Unsupported hashing algorithm!");
} catch (Exception e) {
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'");
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
}
systemTray = systemTray_;
}
@ -278,6 +298,10 @@ class SystemTray {
SystemTray() {
}
/**
* Must be wrapped in a synchronized block for object visibility
*/
protected
MenuEntry getMenuEntry(String menuText) {
for (MenuEntry entry : menuEntries) {
@ -436,8 +460,9 @@ class SystemTray {
* @param origMenuText the original menu text
* @param newMenuText the new menu text (this will replace the original menu text)
*/
public final synchronized
public final
void updateMenuEntry_Text(String origMenuText, String newMenuText) {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -447,6 +472,7 @@ class SystemTray {
menuEntry.setText(newMenuText);
}
}
}
/**
* Updates (or changes) the menu entry's text.
@ -454,8 +480,9 @@ class SystemTray {
* @param origMenuText the original menu text
* @param imagePath the new path for the image to use or null to delete the image
*/
public final synchronized
public final
void updateMenuEntry_Image(String origMenuText, String imagePath) throws IOException {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -465,6 +492,7 @@ class SystemTray {
menuEntry.setImage(imagePath);
}
}
}
/**
* Updates (or changes) the menu entry's text.
@ -472,8 +500,9 @@ class SystemTray {
* @param origMenuText the original menu text
* @param imageUrl the new URL for the image to use or null to delete the image
*/
public final synchronized
public final
void updateMenuEntry_Image(String origMenuText, URL imageUrl) throws IOException {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -483,6 +512,7 @@ class SystemTray {
menuEntry.setImage(imageUrl);
}
}
}
/**
* Updates (or changes) the menu entry's text.
@ -490,8 +520,9 @@ class SystemTray {
* @param cacheName the name to use for lookup in the cache for the imageStream
* @param imageStream the InputStream of the image to use or null to delete the image
*/
public final synchronized
public final
void updateMenuEntry_Image(String origMenuText, String cacheName, InputStream imageStream) throws IOException {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -501,6 +532,7 @@ class SystemTray {
menuEntry.setImage(cacheName, imageStream);
}
}
}
/**
* Updates (or changes) the menu entry's text.
@ -512,8 +544,9 @@ class SystemTray {
* @param imageStream the new path for the image to use or null to delete the image
*/
@Deprecated
public final synchronized
public final
void updateMenuEntry_Image(String origMenuText, InputStream imageStream) throws IOException {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -523,6 +556,7 @@ class SystemTray {
menuEntry.setImage(imageStream);
}
}
}
/**
* Updates (or changes) the menu entry's callback.
@ -530,8 +564,9 @@ class SystemTray {
* @param origMenuText the original menu text
* @param newCallback the new callback (this will replace the original callback)
*/
public final synchronized
public final
void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry != null) {
@ -541,6 +576,7 @@ class SystemTray {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
}
}
/**
@ -550,8 +586,9 @@ class SystemTray {
* @param newMenuText the new menu text (this will replace the original menu text)
* @param newCallback the new callback (this will replace the original callback)
*/
public final synchronized
public final
void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
@ -562,6 +599,7 @@ class SystemTray {
menuEntry.setCallback(newCallback);
}
}
}
/**
@ -569,7 +607,7 @@ class SystemTray {
*
* @param menuEntry This is the menu entry to remove
*/
public final synchronized
public final
void removeMenuEntry(final MenuEntry menuEntry) {
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for menuEntry");
@ -577,6 +615,7 @@ class SystemTray {
final String label = menuEntry.getText();
synchronized (menuEntries) {
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next();
if (entry.getText()
@ -588,6 +627,7 @@ class SystemTray {
return;
}
}
}
throw new NullPointerException("Menu entry '" + label + "'not found in list while trying to remove it.");
}
@ -597,8 +637,9 @@ class SystemTray {
*
* @param menuText This is the label for the menu entry to remove
*/
public final synchronized
public final
void removeMenuEntry(final String menuText) {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry == null) {
@ -609,4 +650,5 @@ class SystemTray {
}
}
}
}

View File

@ -24,8 +24,7 @@ import dorkbox.util.jna.linux.GtkSupport;
* <p/>
* specialization for using app indicators in ubuntu unity
* <p/>
* Heavily modified from
* <p/>
* Derived from
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*/
public
@ -33,57 +32,71 @@ class AppIndicatorTray extends GtkTypeSystemTray {
private static final AppIndicator appindicator = AppIndicator.INSTANCE;
private AppIndicator.AppIndicatorInstanceStruct appIndicator;
private volatile boolean isActive = false;
private boolean isActive = false;
public
AppIndicatorTray() {
gtk.gdk_threads_enter();
this.appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "",
AppIndicator.CATEGORY_APPLICATION_STATUS);
gtk.gdk_threads_leave();
GtkSupport.startGui();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "",
AppIndicator.CATEGORY_APPLICATION_STATUS);
}
});
}
@Override
public synchronized
public
void shutdown() {
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
// this hides the indicator
appindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE);
Pointer p = this.appIndicator.getPointer();
appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
Pointer p = appIndicator.getPointer();
gobject.g_object_unref(p);
// GC it
this.appIndicator = null;
appIndicator = null;
}
});
// libgtk.gdk_threads_leave(); called by super class
super.shutdown();
}
@Override
protected synchronized
protected
void setIcon_(final String iconPath) {
gtk.gdk_threads_enter();
appindicator.app_indicator_set_icon(this.appIndicator, iconPath);
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
appindicator.app_indicator_set_icon(appIndicator, iconPath);
if (!isActive) {
isActive = true;
appindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE);
appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
}
gtk.gdk_threads_leave();
}
});
}
/**
* Called inside the gdk_threads block. MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
* MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
*/
protected
void onMenuAdded(final Pointer menu) {
appindicator.app_indicator_set_menu(this.appIndicator, menu);
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
appindicator.app_indicator_set_menu(appIndicator, menu);
}
});
}
}

View File

@ -23,6 +23,7 @@ import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gobject.GCallback;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.GtkSupport;
import java.io.IOException;
import java.io.InputStream;
@ -43,6 +44,7 @@ class GtkMenuEntry implements MenuEntry {
private volatile SystemTrayMenuAction callback;
private volatile Pointer image;
// called from inside dispatch thread
GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final GtkTypeSystemTray systemTray) {
this.parentMenu = parentMenu;
@ -99,20 +101,24 @@ class GtkMenuEntry implements MenuEntry {
@Override
public
void setText(final String newText) {
this.text = newText;
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
text = newText;
gtk.gtk_menu_item_set_label(menuItem, newText);
gtk.gtk_widget_show_all(parentMenu);
gtk.gdk_threads_leave();
}
});
}
private
void setImage_(final String imagePath) {
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
if (imagePath != null && !imagePath.isEmpty()) {
if (image != null) {
gtk.gtk_widget_destroy(image);
@ -127,9 +133,10 @@ class GtkMenuEntry implements MenuEntry {
gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
gtk.gdk_threads_leave();
gtk.gtk_widget_show_all(parentMenu);
}
});
}
@Override
public
@ -187,15 +194,17 @@ class GtkMenuEntry implements MenuEntry {
*/
public
void remove() {
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
removePrivate();
// have to rebuild the menu now...
systemTray.deleteMenu();
systemTray.createMenu();
gtk.gdk_threads_leave();
}
});
}
void removePrivate() {

View File

@ -32,7 +32,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
// have to make this a field, to prevent GC on this object
@SuppressWarnings("FieldCanBeLocal")
private final Gobject.GEventCallback gtkCallback;
private Gobject.GEventCallback gtkCallback;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private NativeLong button_press_event;
@ -42,16 +42,19 @@ class GtkSystemTray extends GtkTypeSystemTray {
public
GtkSystemTray() {
super();
GtkSupport.startGui();
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
final Pointer trayIcon_ = gtk.gtk_status_icon_new();
gtk.gtk_status_icon_set_title(trayIcon_, "SystemTray@Dorkbox");
gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray");
final Pointer trayIcon = gtk.gtk_status_icon_new();
gtk.gtk_status_icon_set_title(trayIcon, "SystemTray@Dorkbox");
gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
trayIcon = trayIcon_;
this.trayIcon = trayIcon;
this.gtkCallback = new Gobject.GEventCallback() {
gtkCallback = new Gobject.GEventCallback() {
@Override
public
void callback(Pointer notUsed, final Gtk.GdkEventButton event) {
@ -62,10 +65,8 @@ class GtkSystemTray extends GtkTypeSystemTray {
}
};
button_press_event = gobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, null, null, 0);
gtk.gdk_threads_leave();
GtkSupport.startGui();
}
});
}
/**
@ -78,32 +79,38 @@ class GtkSystemTray extends GtkTypeSystemTray {
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@Override
public synchronized
public
void shutdown() {
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
// this hides the indicator
gtk.gtk_status_icon_set_visible(this.trayIcon, false);
gobject.g_object_unref(this.trayIcon);
gtk.gtk_status_icon_set_visible(trayIcon, false);
gobject.g_object_unref(trayIcon);
// GC it
this.trayIcon = null;
trayIcon = null;
}
});
// libgtk.gdk_threads_leave(); called by parent class
super.shutdown();
}
@Override
protected synchronized
protected
void setIcon_(final String iconPath) {
gtk.gdk_threads_enter();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
if (!isActive) {
isActive = true;
gtk.gtk_status_icon_set_visible(trayIcon, true);
}
gtk.gdk_threads_leave();
}
});
}
}

View File

@ -31,38 +31,46 @@ import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Derived from
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*/
public abstract
class GtkTypeSystemTray extends SystemTray {
protected static final Gobject gobject = Gobject.INSTANCE;
protected static final Gtk gtk = Gtk.INSTANCE;
// protected static final GLib glib = GLib.INSTANCE;
final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false));
private Pointer menu;
private Pointer connectionStatusItem;
@Override synchronized
@Override
public
void shutdown() {
// libgtk.gdk_threads_enter(); called by implementation
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
obliterateMenu();
GtkSupport.shutdownGui();
gtk.gdk_threads_leave();
callbackExecutor.shutdown();
}
});
}
@Override
public synchronized
public
void setStatus(final String infoString) {
gtk.gdk_threads_enter();
if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
if (connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
deleteMenu();
this.connectionStatusItem = gtk.gtk_menu_item_new_with_label("");
connectionStatusItem = gtk.gtk_menu_item_new_with_label("");
gobject.g_object_ref(connectionStatusItem); // so it matches with 'createMenu'
// evil hacks abound...
@ -73,7 +81,7 @@ class GtkTypeSystemTray extends SystemTray {
gobject.g_free(markup);
gtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE);
gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE);
createMenu();
}
@ -99,8 +107,8 @@ class GtkTypeSystemTray extends SystemTray {
gtk.gtk_widget_show_all(menu);
}
}
gtk.gdk_threads_leave();
}
});
}
/**
@ -110,8 +118,6 @@ class GtkTypeSystemTray extends SystemTray {
void onMenuAdded(final Pointer menu);
// UNSAFE. must be protected inside synchronized, and inside threads_enter/exit
/**
* Completely obliterates the menu, no possible way to reconstruct it.
*/
@ -164,7 +170,7 @@ class GtkTypeSystemTray extends SystemTray {
menu = gtk.gtk_menu_new();
}
// UNSAFE. must be protected inside synchronized, and inside threads_enter/exit
// UNSAFE. must be protected inside dispatch
void createMenu() {
// now add status
if (connectionStatusItem != null) {
@ -185,8 +191,8 @@ class GtkTypeSystemTray extends SystemTray {
gtk.gtk_widget_show_all(menu);
}
private synchronized
void addMenuEntry_(String menuText, final String imagePath, final SystemTrayMenuAction callback) {
private
void addMenuEntry_(final String menuText, final String imagePath, final SystemTrayMenuAction callback) {
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
// see: https://bugs.launchpad.net/glipper/+bug/1203888
@ -194,28 +200,29 @@ class GtkTypeSystemTray extends SystemTray {
throw new NullPointerException("Menu text cannot be null");
}
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText);
if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
}
else {
gtk.gdk_threads_enter();
if (menuEntry == null) {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
// To work around this issue, we destroy then recreate the menu every time one is added.
deleteMenu();
menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, this);
menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, GtkTypeSystemTray.this);
gobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu'
this.menuEntries.add(menuEntry);
menuEntries.add(menuEntry);
createMenu();
gtk.gdk_threads_leave();
}
}
}
});
}
@Override
public

View File

@ -20,7 +20,6 @@ import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.SystemTrayMenuPopup;
import dorkbox.util.SwingUtil;
import javax.swing.ImageIcon;
@ -32,7 +31,7 @@ import java.io.InputStream;
import java.net.URL;
class SwingMenuEntry implements MenuEntry {
private final SystemTrayMenuPopup parent;
private final SwingSystemTrayMenuPopup parent;
private final SystemTray systemTray;
private final JMenuItem menuItem;
private final ActionListener swingCallback;
@ -40,7 +39,7 @@ class SwingMenuEntry implements MenuEntry {
private volatile String text;
private volatile SystemTrayMenuAction callback;
SwingMenuEntry(final SystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final SystemTray systemTray) {
this.parent = parentMenu;
this.text = label;

View File

@ -18,7 +18,6 @@ package dorkbox.systemTray.swing;
import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.SystemTrayMenuPopup;
import dorkbox.util.ScreenUtil;
import dorkbox.util.SwingUtil;
@ -43,7 +42,7 @@ import java.net.URL;
*/
public
class SwingSystemTray extends dorkbox.systemTray.SystemTray {
volatile SystemTrayMenuPopup menu;
volatile SwingSystemTrayMenuPopup menu;
volatile JMenuItem connectionStatusItem;
volatile SystemTray tray;
@ -132,7 +131,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
if (!isActive) {
isActive = true;
SwingSystemTray.this.menu = new SystemTrayMenuPopup();
SwingSystemTray.this.menu = new SwingSystemTrayMenuPopup();
Image trayImage = new ImageIcon(iconPath).getImage()
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
@ -147,7 +146,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
@Override
public
void mousePressed(MouseEvent e) {
final SystemTrayMenuPopup menu = SwingSystemTray.this.menu;
final SwingSystemTrayMenuPopup menu = SwingSystemTray.this.menu;
Dimension size = menu.getPreferredSize();
Point point = e.getPoint();
@ -213,6 +212,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
void run() {
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
synchronized (menuEntries) {
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry != null) {
@ -224,6 +224,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
}
}
}
}
});
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray;
package dorkbox.systemTray.swing;
import dorkbox.util.DelayTimer;
import dorkbox.util.SwingUtil;
@ -26,8 +26,7 @@ import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public
class SystemTrayMenuPopup extends JPopupMenu {
class SwingSystemTrayMenuPopup extends JPopupMenu {
private static final long serialVersionUID = 1L;
@Property
@ -39,8 +38,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
// protected boolean mouseStillOnMenu;
// private JDialog hiddenDialog;
public
SystemTrayMenuPopup() {
SwingSystemTrayMenuPopup() {
super();
setFocusable(true);
@ -59,7 +57,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width &&
location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) {
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay());
SwingSystemTrayMenuPopup.this.timer.delay(SwingSystemTrayMenuPopup.this.timer.getDelay());
}
else {
setVisible(false);
@ -74,7 +72,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
public
void mouseExited(MouseEvent event) {
// wait before checking if mouse is still on the menu
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay());
SwingSystemTrayMenuPopup.this.timer.delay(SwingSystemTrayMenuPopup.this.timer.getDelay());
}
});