Fixed issues with menu's not showing up with certain versions of libappindicator. (Any time the menu is edited, it is recreated).

This commit is contained in:
nathan 2015-11-15 14:51:47 +01:00
parent 60fbd6306d
commit c1933e6763
7 changed files with 318 additions and 192 deletions

View File

@ -18,6 +18,10 @@ There are a number of problems on Linux with the Swing (and SWT) system-tray ico
This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windows 32/64. Java 6+
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.
See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
```
Customization parameters:
@ -88,16 +92,34 @@ Note: This project was heavily influenced by the excellent Lantern project,
```
Note: Gnome-shell users will experience an extension install to support this
functionality. Additionally, a shell restart is necessary for the extension
to be registered by the shell. You can disable the restart behavior if you like,
and the 'system tray' functionality will be picked up on log out/in, or a
system restart.
to be registered by the shell. You can disable the restart behavior if you
like, and the 'system tray' functionality will be picked up on log out/in,
or a system restart.
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.
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.
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.
ISSUES:
'Trying to remove a child that doesn't believe we're it's parent.'
This is a known appindicator bug, and is rather old. Some distributions use
an OLD version of libappindicator, and will see this error.
See: https://github.com/ValveSoftware/steam-for-linux/issues/1077
'gsignal.c: signal 'child-added' is invalid for instance 'xyz' of type 'GtkMenu''
This is a known appindicator bug, and is rather old. Some distributions use an
OLD version of libappindicator, and will see this error.
The fallout from this issue (ie: menu entries not displaying) has been
*worked around*, so the menus should still show correctly.
See: https://askubuntu.com/questions/364594/has-the-appindicator-or-gtkmenu-api-changed-in-saucy
```

View File

@ -95,7 +95,7 @@ class SystemTray {
}
}
else if ("XFCE".equalsIgnoreCase(XDG)) {
// XFCE uses a OLD version of appindicators, which DO NOT support images in the menu.
// XFCE uses a BAD version of libappindicator by default, which DOES NOT support images in the menu.
try {
trayType = GtkSystemTray.class;
} catch (Throwable ignored) {
@ -107,6 +107,12 @@ class SystemTray {
} catch (Throwable ignored) {
}
}
else if ("KDE".equalsIgnoreCase(XDG)) {
try {
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
}
}
else if ("GNOME".equalsIgnoreCase(XDG)) {
// check other DE
String GDM = System.getenv("GDMSESSION");
@ -331,17 +337,15 @@ class SystemTray {
* @param origMenuText the original menu text
* @param newMenuText the new menu text (this will replace the original menu text)
*/
public final
public final synchronized
void updateMenuEntry_Text(String origMenuText, String newMenuText) {
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setText(newMenuText);
}
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setText(newMenuText);
}
}
@ -352,17 +356,15 @@ class SystemTray {
* @param origMenuText the original menu text
* @param imagePath the new path for the image to use. Null to remove an image.
*/
public final
public final synchronized
void updateMenuEntry_Image(String origMenuText, String imagePath) {
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setImage(imagePath);
}
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setImage(imagePath);
}
}
@ -373,17 +375,15 @@ class SystemTray {
* @param origMenuText the original menu text
* @param newCallback the new callback (this will replace the original callback)
*/
public final
public final synchronized
void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) {
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry != null) {
menuEntry.setCallback(newCallback);
}
else {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
if (menuEntry != null) {
menuEntry.setCallback(newCallback);
}
else {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
}
@ -395,18 +395,16 @@ 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
public final synchronized
void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) {
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setText(newMenuText);
menuEntry.setCallback(newCallback);
}
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
}
else {
menuEntry.setText(newMenuText);
menuEntry.setCallback(newCallback);
}
}
@ -416,27 +414,26 @@ class SystemTray {
*
* @param menuEntry This is the menu entry to remove
*/
public final
public final synchronized
void removeMenuEntry(final MenuEntry menuEntry) {
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for menuEntry");
}
synchronized (this.menuEntries) {
final String label = menuEntry.getText();
final String label = menuEntry.getText();
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next();
if (entry.getText()
.equals(label)) {
iterator.remove();
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next();
if (entry.getText()
.equals(label)) {
iterator.remove();
menuEntry.remove();
return;
}
// this will also reset the menu
menuEntry.remove();
return;
}
throw new NullPointerException("Menu entry '" + label + "'not found in list while trying to remove it.");
}
throw new NullPointerException("Menu entry '" + label + "'not found in list while trying to remove it.");
}
@ -445,22 +442,22 @@ class SystemTray {
*
* @param menuText This is the label for the menu entry to remove
*/
public final
public final synchronized
void removeMenuEntry(final String menuText) {
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(menuText);
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + menuText + "'");
}
else {
removeMenuEntry(menuEntry);
}
if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + menuText + "'");
}
else {
removeMenuEntry(menuEntry);
}
}
/**
* UNSAFE. must be called inside sync
*
* appIndicator/gtk require strings (which is the path)
* swing version loads as an image (which can be stream or path, we use path)
*/

View File

@ -32,18 +32,14 @@ public
class AppIndicatorTray extends GtkTypeSystemTray {
private static final AppIndicator libappindicator = AppIndicator.INSTANCE;
private volatile AppIndicator.AppIndicatorInstanceStruct appIndicator;
private AppIndicator.AppIndicatorInstanceStruct appIndicator;
public
AppIndicatorTray(String iconName) {
libgtk.gdk_threads_enter();
this.appIndicator = libappindicator.app_indicator_new(System.nanoTime() + "DBST", "indicator-messages-new",
String icon_name = iconPath(iconName);
this.appIndicator = libappindicator.app_indicator_new(System.nanoTime() + "DBST", icon_name,
AppIndicator.CATEGORY_APPLICATION_STATUS);
this.menu = libgtk.gtk_menu_new();
libappindicator.app_indicator_set_menu(this.appIndicator, this.menu);
libappindicator.app_indicator_set_icon(this.appIndicator, iconPath(iconName));
libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE);
libgtk.gdk_threads_leave();
@ -52,13 +48,12 @@ class AppIndicatorTray extends GtkTypeSystemTray {
}
@Override
public
public synchronized
void shutdown() {
libgtk.gdk_threads_enter();
// this hides the indicator
libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE);
this.appIndicator.write();
Pointer p = this.appIndicator.getPointer();
libgobject.g_object_unref(p);
@ -70,10 +65,19 @@ class AppIndicatorTray extends GtkTypeSystemTray {
}
@Override
public
public synchronized
void setIcon(final String iconName) {
libgtk.gdk_threads_enter();
libappindicator.app_indicator_set_icon(this.appIndicator, iconPath(iconName));
libgtk.gdk_threads_leave();
}
/**
* Called inside the gdk_threads block. MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
*/
protected
void onMenuAdded(final Pointer menu) {
libappindicator.app_indicator_set_menu(this.appIndicator, menu);
}
}

View File

@ -21,7 +21,6 @@ import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gobject.GCallback;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.tray.MenuEntry;
import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction;
class GtkMenuEntry implements MenuEntry {
@ -29,17 +28,18 @@ class GtkMenuEntry implements MenuEntry {
private static final Gobject libgobject = Gobject.INSTANCE;
private final GCallback gtkCallback;
private final Pointer menuItem;
final Pointer menuItem;
private final Pointer parentMenu;
private final SystemTray systemTray;
final GtkTypeSystemTray systemTray;
private final NativeLong nativeLong;
// these have to be volatile, because they can be changed from any thread
private volatile String text;
private volatile SystemTrayMenuAction callback;
private volatile Pointer image;
GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final SystemTray systemTray) {
final GtkTypeSystemTray systemTray) {
this.parentMenu = parentMenu;
this.text = label;
this.callback = callback;
@ -55,7 +55,6 @@ class GtkMenuEntry implements MenuEntry {
}
};
menuItem = libgtk.gtk_image_menu_item_new_with_label(label);
if (imagePath != null && !imagePath.isEmpty()) {
@ -70,9 +69,6 @@ class GtkMenuEntry implements MenuEntry {
}
nativeLong = libgobject.g_signal_connect_data(menuItem, "activate", gtkCallback, null, null, 0);
libgtk.gtk_menu_shell_append(parentMenu, menuItem);
libgtk.gtk_widget_show_all(menuItem);
}
private
@ -117,7 +113,10 @@ class GtkMenuEntry implements MenuEntry {
if (image != null) {
libgtk.gtk_widget_destroy(image);
}
libgtk.gtk_widget_show_all(parentMenu);
libgtk.gdk_threads_leave();
libgtk.gdk_threads_enter();
image = libgtk.gtk_image_new_from_file(imagePath);
libgtk.gtk_image_menu_item_set_image(menuItem, image);
@ -125,7 +124,6 @@ class GtkMenuEntry implements MenuEntry {
// must always re-set always-show after setting the image
libgtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
libgtk.gtk_widget_show_all(parentMenu);
libgtk.gdk_threads_leave();
@ -137,10 +135,23 @@ class GtkMenuEntry implements MenuEntry {
this.callback = callback;
}
/**
* This is ONLY called via systray.menuEntry.remove() !!
*/
public
void remove() {
libgtk.gdk_threads_enter();
removePrivate();
// have to rebuild the menu now...
systemTray.deleteMenu();
systemTray.createMenu();
libgtk.gdk_threads_leave();
}
void removePrivate() {
libgobject.g_signal_handler_disconnect(menuItem, nativeLong);
libgtk.gtk_menu_shell_deactivate(parentMenu, menuItem);
@ -148,8 +159,6 @@ class GtkMenuEntry implements MenuEntry {
libgtk.gtk_widget_destroy(image);
}
libgtk.gtk_widget_destroy(menuItem);
libgtk.gdk_threads_leave();
}
@Override

View File

@ -15,6 +15,7 @@
*/
package dorkbox.util.tray.linux;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk;
@ -27,11 +28,13 @@ import dorkbox.util.jna.linux.GtkSupport;
*/
public
class GtkSystemTray extends GtkTypeSystemTray {
private volatile Pointer trayIcon;
private Pointer trayIcon;
// have to make this a field, to prevent GC on this object
@SuppressWarnings("FieldCanBeLocal")
private Gobject.GEventCallback gtkCallback;
private final Gobject.GEventCallback gtkCallback;
private NativeLong button_press_event;
private volatile Pointer menu;
public
GtkSystemTray(String iconName) {
@ -41,23 +44,24 @@ class GtkSystemTray extends GtkTypeSystemTray {
final Pointer trayIcon = libgtk.gtk_status_icon_new();
libgtk.gtk_status_icon_set_title(trayIcon, "SystemTray@Dorkbox");
libgtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
this.trayIcon = trayIcon;
libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName));
this.menu = libgtk.gtk_menu_new();
this.gtkCallback = new Gobject.GEventCallback() {
@Override
public
void callback(Pointer system_tray, final Gtk.GdkEventButton event) {
void callback(Pointer notUsed, final Gtk.GdkEventButton event) {
// BUTTON_PRESS only (any mouse click)
if (event.type == 4) {
libgtk.gtk_menu_popup(menu, null, null, Gtk.gtk_status_icon_position_menu, system_tray, 0, event.time);
libgtk.gtk_menu_popup(menu, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time);
}
}
};
libgobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, menu, null, 0);
button_press_event = libgobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, null, null, 0);
libgtk.gtk_status_icon_set_visible(trayIcon, true);
libgtk.gdk_threads_leave();
@ -65,9 +69,17 @@ class GtkSystemTray extends GtkTypeSystemTray {
GtkSupport.startGui();
}
/**
* Called inside the gdk_threads block
*/
protected
void onMenuAdded(final Pointer menu) {
this.menu = menu;
}
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@Override
public
public synchronized
void shutdown() {
libgtk.gdk_threads_enter();
@ -83,7 +95,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
}
@Override
public
public synchronized
void setIcon(final String iconName) {
libgtk.gdk_threads_enter();
libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName));

View File

@ -21,12 +21,9 @@ import dorkbox.util.NamedThreadFactory;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.MenuEntry;
import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -37,38 +34,14 @@ class GtkTypeSystemTray extends SystemTray {
final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false));
protected volatile Pointer menu;
private Pointer menu;
private Pointer connectionStatusItem;
private volatile Pointer connectionStatusItem;
// need to hang on to these to prevent gc
private final List<Pointer> widgets = new ArrayList<Pointer>(4);
@Override
@Override synchronized
public
void shutdown() {
// libgtk.gdk_threads_enter(); called by implementation
for (Pointer widget : this.widgets) {
libgtk.gtk_widget_destroy(widget);
}
// GC it
this.widgets.clear();
// unrefs the children too
// GTK menu needs a "ref_sink"
libgobject.g_object_ref_sink(this.menu);
this.menu = null;
synchronized (this.menuEntries) {
for (MenuEntry menuEntry : this.menuEntries) {
menuEntry.remove();
}
this.menuEntries.clear();
}
this.connectionStatusItem = null;
obliterateMenu();
GtkSupport.shutdownGui();
libgtk.gdk_threads_leave();
@ -77,70 +50,171 @@ class GtkTypeSystemTray extends SystemTray {
}
@Override
public
public synchronized
void setStatus(String infoString) {
synchronized (this.menuEntries) {
libgtk.gdk_threads_enter();
libgtk.gdk_threads_enter();
if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label("");
if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
deleteMenu();
this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label("");
libgobject.g_object_ref(connectionStatusItem); // so it matches with 'createMenu'
// evil hacks abound...
Pointer label = libgtk.gtk_bin_get_child(connectionStatusItem);
libgtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = libgobject.g_markup_printf_escaped("<b>%s</b>", infoString);
libgtk.gtk_label_set_markup(label, markup);
libgobject.g_free(markup);
libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE);
createMenu();
}
else {
if (infoString == null || infoString.isEmpty()) {
deleteMenu();
libgtk.gtk_widget_destroy(connectionStatusItem);
connectionStatusItem = null;
createMenu();
}
else {
// set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString);
// evil hacks abound...
Pointer label = libgtk.gtk_bin_get_child(connectionStatusItem);
libgtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = libgobject.g_markup_printf_escaped ("<b>%s</b>", infoString);
libgtk.gtk_label_set_markup (label, markup);
libgobject.g_free (markup);
Pointer markup = libgobject.g_markup_printf_escaped("<b>%s</b>", infoString);
libgtk.gtk_label_set_markup(label, markup);
libgobject.g_free(markup);
this.widgets.add(this.connectionStatusItem);
libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE);
libgtk.gtk_menu_shell_prepend(this.menu, this.connectionStatusItem);
libgtk.gtk_widget_show_all(menu);
}
else {
if (infoString == null || infoString.isEmpty()) {
libgtk.gtk_menu_shell_deactivate(menu, connectionStatusItem);
libgtk.gtk_widget_destroy(connectionStatusItem);
}
else {
// set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString);
}
// evil hacks abound...
Pointer label = libgtk.gtk_bin_get_child(connectionStatusItem);
libgtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = libgobject.g_markup_printf_escaped ("<b>%s</b>", infoString);
libgtk.gtk_label_set_markup (label, markup);
libgobject.g_free (markup);
}
libgtk.gdk_threads_leave();
}
/**
* Called inside the gdk_threads block
*/
protected abstract
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.
*/
private
void obliterateMenu() {
if (menu != null) {
// have to remove status from menu
if (connectionStatusItem != null) {
libgtk.gtk_widget_destroy(connectionStatusItem);
connectionStatusItem = null;
}
libgtk.gtk_widget_show_all(menu);
libgtk.gdk_threads_leave();
// have to remove all other menu entries
for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
menuEntry__.removePrivate();
}
menuEntries.clear();
// GTK menu needs a "ref_sink"
libgobject.g_object_ref_sink(menu);
}
}
/**
* Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object.
*/
void deleteMenu() {
if (menu != null) {
// have to remove status from menu
if (connectionStatusItem != null) {
libgobject.g_object_ref(connectionStatusItem);
libgtk.gtk_container_remove(menu, connectionStatusItem);
}
// have to remove all other menu entries
for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
libgobject.g_object_ref(menuEntry__.menuItem);
libgtk.gtk_container_remove(menu, menuEntry__.menuItem);
}
// GTK menu needs a "ref_sink"
libgobject.g_object_ref_sink(menu);
// have to 'blip' the thread, so that the state can catch up. Stupid, i know, and I am open to suggestions to fixing bizzare
// race conditions with GTK...
libgtk.gdk_threads_leave();
libgtk.gdk_threads_enter();
}
menu = libgtk.gtk_menu_new();
}
// UNSAFE. must be protected inside synchronized, and inside threads_enter/exit
void createMenu() {
// now add status
if (connectionStatusItem != null) {
libgtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
libgobject.g_object_unref(connectionStatusItem);
}
// now add back other menu entries
for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
libgtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem);
libgobject.g_object_unref(menuEntry__.menuItem);
}
onMenuAdded(menu);
libgtk.gtk_widget_show_all(menu);
}
@Override
public
public synchronized
void addMenuEntry(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
if (menuText == null) {
throw new NullPointerException("Menu text cannot be null");
}
synchronized (this.menuEntries) {
MenuEntry menuEntry = getMenuEntry(menuText);
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText);
if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
}
else {
libgtk.gdk_threads_enter();
menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, this);
libgtk.gdk_threads_leave();
if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
}
else {
libgtk.gdk_threads_enter();
this.menuEntries.add(menuEntry);
}
// 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);
libgobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu'
this.menuEntries.add(menuEntry);
createMenu();
libgtk.gdk_threads_leave();
}
}
}

View File

@ -129,16 +129,17 @@ class SwingSystemTray extends dorkbox.util.tray.SystemTray {
@Override
public
void run() {
SwingSystemTray.this.tray.remove(SwingSystemTray.this.trayIcon);
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
tray.tray.remove(tray.trayIcon);
synchronized (SwingSystemTray.this.menuEntries) {
for (MenuEntry menuEntry : SwingSystemTray.this.menuEntries) {
menuEntry.remove();
for (MenuEntry menuEntry : tray.menuEntries) {
menuEntry.remove();
}
tray.menuEntries.clear();
tray.connectionStatusItem = null;
}
SwingSystemTray.this.menuEntries.clear();
}
SwingSystemTray.this.connectionStatusItem = null;
}
});
}
@ -150,19 +151,22 @@ class SwingSystemTray extends dorkbox.util.tray.SystemTray {
@Override
public
void run() {
if (SwingSystemTray.this.connectionStatusItem == null) {
final JMenuItem connectionStatusItem = new JMenuItem(infoString);
Font font = connectionStatusItem.getFont();
Font font1 = font.deriveFont(Font.BOLD);
connectionStatusItem.setFont(font1);
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
if (tray.connectionStatusItem == null) {
final JMenuItem connectionStatusItem = new JMenuItem(infoString);
Font font = connectionStatusItem.getFont();
Font font1 = font.deriveFont(Font.BOLD);
connectionStatusItem.setFont(font1);
connectionStatusItem.setEnabled(false);
SwingSystemTray.this.menu.add(connectionStatusItem);
connectionStatusItem.setEnabled(false);
tray.menu.add(connectionStatusItem);
SwingSystemTray.this.connectionStatusItem = connectionStatusItem;
}
else {
SwingSystemTray.this.connectionStatusItem.setText(infoString);
tray.connectionStatusItem = connectionStatusItem;
}
else {
tray.connectionStatusItem.setText(infoString);
}
}
}
});
@ -175,11 +179,14 @@ class SwingSystemTray extends dorkbox.util.tray.SystemTray {
@Override
public
void run() {
String iconPath = iconPath(iconName);
Image trayImage = new ImageIcon(iconPath).getImage()
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
trayImage.flush();
SwingSystemTray.this.trayIcon.setImage(trayImage);
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
String iconPath = iconPath(iconName);
Image trayImage = new ImageIcon(iconPath).getImage()
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
trayImage.flush();
tray.trayIcon.setImage(trayImage);
}
}
});
}
@ -198,14 +205,15 @@ class SwingSystemTray extends dorkbox.util.tray.SystemTray {
@Override
public
void run() {
synchronized (menuEntries) {
SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
}
else {
menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, SwingSystemTray.this);
menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, tray);
menuEntries.add(menuEntry);
}
}