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+ 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: 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 Note: Gnome-shell users will experience an extension install to support this
functionality. Additionally, a shell restart is necessary for the extension 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, to be registered by the shell. You can disable the restart behavior if you
and the 'system tray' functionality will be picked up on log out/in, or a like, and the 'system tray' functionality will be picked up on log out/in,
system restart. or a system restart.
Also, screw you gnome-project leads, for making it such a pain-in-the-ass Also, screw you gnome-project leads, for making it such a pain-in-the-ass
to do something so incredibly simple and basic. to do something so incredibly simple and basic.
Note: Some desktop environments might use a dated version of libappindicator, when 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. 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 This library will try to load a GTK indicator instead when it can, or will
to load libappindicator1 first. Thank you RedHat for putting it back. 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)) { 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 { try {
trayType = GtkSystemTray.class; trayType = GtkSystemTray.class;
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -107,6 +107,12 @@ class SystemTray {
} catch (Throwable ignored) { } catch (Throwable ignored) {
} }
} }
else if ("KDE".equalsIgnoreCase(XDG)) {
try {
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
}
}
else if ("GNOME".equalsIgnoreCase(XDG)) { else if ("GNOME".equalsIgnoreCase(XDG)) {
// check other DE // check other DE
String GDM = System.getenv("GDMSESSION"); String GDM = System.getenv("GDMSESSION");
@ -331,17 +337,15 @@ class SystemTray {
* @param origMenuText the original menu text * @param origMenuText the original menu text
* @param newMenuText the new menu text (this will replace 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) { void updateMenuEntry_Text(String origMenuText, String newMenuText) {
synchronized (this.menuEntries) { MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) { if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'"); throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
} }
else { else {
menuEntry.setText(newMenuText); menuEntry.setText(newMenuText);
}
} }
} }
@ -352,17 +356,15 @@ class SystemTray {
* @param origMenuText the original menu text * @param origMenuText the original menu text
* @param imagePath the new path for the image to use. Null to remove an image. * @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) { void updateMenuEntry_Image(String origMenuText, String imagePath) {
synchronized (this.menuEntries) { MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) { if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'"); throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
} }
else { else {
menuEntry.setImage(imagePath); menuEntry.setImage(imagePath);
}
} }
} }
@ -373,17 +375,15 @@ class SystemTray {
* @param origMenuText the original menu text * @param origMenuText the original menu text
* @param newCallback the new callback (this will replace the original callback) * @param newCallback the new callback (this will replace the original callback)
*/ */
public final public final synchronized
void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) { void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) {
synchronized (this.menuEntries) { MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry != null) { if (menuEntry != null) {
menuEntry.setCallback(newCallback); menuEntry.setCallback(newCallback);
} }
else { else {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'"); 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 newMenuText the new menu text (this will replace the original menu text)
* @param newCallback the new callback (this will replace the original callback) * @param newCallback the new callback (this will replace the original callback)
*/ */
public final public final synchronized
void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) { void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) {
synchronized (this.menuEntries) { MenuEntry menuEntry = getMenuEntry(origMenuText);
MenuEntry menuEntry = getMenuEntry(origMenuText);
if (menuEntry == null) { if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'"); throw new NullPointerException("No menu entry exists for string '" + origMenuText + "'");
} }
else { else {
menuEntry.setText(newMenuText); menuEntry.setText(newMenuText);
menuEntry.setCallback(newCallback); menuEntry.setCallback(newCallback);
}
} }
} }
@ -416,27 +414,26 @@ class SystemTray {
* *
* @param menuEntry This is the menu entry to remove * @param menuEntry This is the menu entry to remove
*/ */
public final public final synchronized
void removeMenuEntry(final MenuEntry menuEntry) { void removeMenuEntry(final MenuEntry menuEntry) {
if (menuEntry == null) { if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for menuEntry"); 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(); ) { for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
final MenuEntry entry = iterator.next(); final MenuEntry entry = iterator.next();
if (entry.getText() if (entry.getText()
.equals(label)) { .equals(label)) {
iterator.remove(); iterator.remove();
menuEntry.remove(); // this will also reset the menu
return; 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 * @param menuText This is the label for the menu entry to remove
*/ */
public final public final synchronized
void removeMenuEntry(final String menuText) { void removeMenuEntry(final String menuText) {
synchronized (this.menuEntries) { MenuEntry menuEntry = getMenuEntry(menuText);
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry == null) { if (menuEntry == null) {
throw new NullPointerException("No menu entry exists for string '" + menuText + "'"); throw new NullPointerException("No menu entry exists for string '" + menuText + "'");
} }
else { else {
removeMenuEntry(menuEntry); removeMenuEntry(menuEntry);
}
} }
} }
/** /**
* UNSAFE. must be called inside sync
*
* appIndicator/gtk require strings (which is the path) * appIndicator/gtk require strings (which is the path)
* swing version loads as an image (which can be stream or path, we use 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 { class AppIndicatorTray extends GtkTypeSystemTray {
private static final AppIndicator libappindicator = AppIndicator.INSTANCE; private static final AppIndicator libappindicator = AppIndicator.INSTANCE;
private volatile AppIndicator.AppIndicatorInstanceStruct appIndicator; private AppIndicator.AppIndicatorInstanceStruct appIndicator;
public public
AppIndicatorTray(String iconName) { AppIndicatorTray(String iconName) {
libgtk.gdk_threads_enter(); 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); 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); libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE);
libgtk.gdk_threads_leave(); libgtk.gdk_threads_leave();
@ -52,13 +48,12 @@ class AppIndicatorTray extends GtkTypeSystemTray {
} }
@Override @Override
public public synchronized
void shutdown() { void shutdown() {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
// this hides the indicator // this hides the indicator
libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE); libappindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE);
this.appIndicator.write();
Pointer p = this.appIndicator.getPointer(); Pointer p = this.appIndicator.getPointer();
libgobject.g_object_unref(p); libgobject.g_object_unref(p);
@ -70,10 +65,19 @@ class AppIndicatorTray extends GtkTypeSystemTray {
} }
@Override @Override
public public synchronized
void setIcon(final String iconName) { void setIcon(final String iconName) {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
libappindicator.app_indicator_set_icon(this.appIndicator, iconPath(iconName)); libappindicator.app_indicator_set_icon(this.appIndicator, iconPath(iconName));
libgtk.gdk_threads_leave(); 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.Gobject.GCallback;
import dorkbox.util.jna.linux.Gtk; import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.tray.MenuEntry; import dorkbox.util.tray.MenuEntry;
import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction; import dorkbox.util.tray.SystemTrayMenuAction;
class GtkMenuEntry implements MenuEntry { class GtkMenuEntry implements MenuEntry {
@ -29,17 +28,18 @@ class GtkMenuEntry implements MenuEntry {
private static final Gobject libgobject = Gobject.INSTANCE; private static final Gobject libgobject = Gobject.INSTANCE;
private final GCallback gtkCallback; private final GCallback gtkCallback;
private final Pointer menuItem; final Pointer menuItem;
private final Pointer parentMenu; private final Pointer parentMenu;
private final SystemTray systemTray; final GtkTypeSystemTray systemTray;
private final NativeLong nativeLong; private final NativeLong nativeLong;
// these have to be volatile, because they can be changed from any thread
private volatile String text; private volatile String text;
private volatile SystemTrayMenuAction callback; private volatile SystemTrayMenuAction callback;
private volatile Pointer image; private volatile Pointer image;
GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback, GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final SystemTray systemTray) { final GtkTypeSystemTray systemTray) {
this.parentMenu = parentMenu; this.parentMenu = parentMenu;
this.text = label; this.text = label;
this.callback = callback; this.callback = callback;
@ -55,7 +55,6 @@ class GtkMenuEntry implements MenuEntry {
} }
}; };
menuItem = libgtk.gtk_image_menu_item_new_with_label(label); menuItem = libgtk.gtk_image_menu_item_new_with_label(label);
if (imagePath != null && !imagePath.isEmpty()) { 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); 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 private
@ -117,7 +113,10 @@ class GtkMenuEntry implements MenuEntry {
if (image != null) { if (image != null) {
libgtk.gtk_widget_destroy(image); 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); image = libgtk.gtk_image_new_from_file(imagePath);
libgtk.gtk_image_menu_item_set_image(menuItem, image); 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 // must always re-set always-show after setting the image
libgtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); libgtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
} }
libgtk.gtk_widget_show_all(parentMenu); libgtk.gtk_widget_show_all(parentMenu);
libgtk.gdk_threads_leave(); libgtk.gdk_threads_leave();
@ -137,10 +135,23 @@ class GtkMenuEntry implements MenuEntry {
this.callback = callback; this.callback = callback;
} }
/**
* This is ONLY called via systray.menuEntry.remove() !!
*/
public public
void remove() { void remove() {
libgtk.gdk_threads_enter(); 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); libgobject.g_signal_handler_disconnect(menuItem, nativeLong);
libgtk.gtk_menu_shell_deactivate(parentMenu, menuItem); libgtk.gtk_menu_shell_deactivate(parentMenu, menuItem);
@ -148,8 +159,6 @@ class GtkMenuEntry implements MenuEntry {
libgtk.gtk_widget_destroy(image); libgtk.gtk_widget_destroy(image);
} }
libgtk.gtk_widget_destroy(menuItem); libgtk.gtk_widget_destroy(menuItem);
libgtk.gdk_threads_leave();
} }
@Override @Override

View File

@ -15,6 +15,7 @@
*/ */
package dorkbox.util.tray.linux; package dorkbox.util.tray.linux;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.util.jna.linux.Gobject; import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.Gtk; import dorkbox.util.jna.linux.Gtk;
@ -27,11 +28,13 @@ import dorkbox.util.jna.linux.GtkSupport;
*/ */
public public
class GtkSystemTray extends GtkTypeSystemTray { class GtkSystemTray extends GtkTypeSystemTray {
private volatile Pointer trayIcon; private Pointer trayIcon;
// have to make this a field, to prevent GC on this object // have to make this a field, to prevent GC on this object
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private Gobject.GEventCallback gtkCallback; private final Gobject.GEventCallback gtkCallback;
private NativeLong button_press_event;
private volatile Pointer menu;
public public
GtkSystemTray(String iconName) { GtkSystemTray(String iconName) {
@ -41,23 +44,24 @@ class GtkSystemTray extends GtkTypeSystemTray {
final Pointer trayIcon = libgtk.gtk_status_icon_new(); final Pointer trayIcon = libgtk.gtk_status_icon_new();
libgtk.gtk_status_icon_set_title(trayIcon, "SystemTray@Dorkbox"); libgtk.gtk_status_icon_set_title(trayIcon, "SystemTray@Dorkbox");
libgtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
this.trayIcon = trayIcon; this.trayIcon = trayIcon;
libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName)); libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName));
this.menu = libgtk.gtk_menu_new();
this.gtkCallback = new Gobject.GEventCallback() { this.gtkCallback = new Gobject.GEventCallback() {
@Override @Override
public public
void callback(Pointer system_tray, final Gtk.GdkEventButton event) { void callback(Pointer notUsed, final Gtk.GdkEventButton event) {
// BUTTON_PRESS only (any mouse click) // BUTTON_PRESS only (any mouse click)
if (event.type == 4) { 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.gtk_status_icon_set_visible(trayIcon, true);
libgtk.gdk_threads_leave(); libgtk.gdk_threads_leave();
@ -65,9 +69,17 @@ class GtkSystemTray extends GtkTypeSystemTray {
GtkSupport.startGui(); GtkSupport.startGui();
} }
/**
* Called inside the gdk_threads block
*/
protected
void onMenuAdded(final Pointer menu) {
this.menu = menu;
}
@SuppressWarnings("FieldRepeatedlyAccessedInMethod") @SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@Override @Override
public public synchronized
void shutdown() { void shutdown() {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
@ -83,7 +95,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
} }
@Override @Override
public public synchronized
void setIcon(final String iconName) { void setIcon(final String iconName) {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
libgtk.gtk_status_icon_set_from_file(trayIcon, iconPath(iconName)); 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.Gobject;
import dorkbox.util.jna.linux.Gtk; import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.GtkSupport; import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.tray.MenuEntry;
import dorkbox.util.tray.SystemTray; import dorkbox.util.tray.SystemTray;
import dorkbox.util.tray.SystemTrayMenuAction; import dorkbox.util.tray.SystemTrayMenuAction;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -37,38 +34,14 @@ class GtkTypeSystemTray extends SystemTray {
final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false));
protected volatile Pointer menu; private Pointer menu;
private Pointer connectionStatusItem;
private volatile Pointer connectionStatusItem; @Override synchronized
// need to hang on to these to prevent gc
private final List<Pointer> widgets = new ArrayList<Pointer>(4);
@Override
public public
void shutdown() { void shutdown() {
// libgtk.gdk_threads_enter(); called by implementation // libgtk.gdk_threads_enter(); called by implementation
for (Pointer widget : this.widgets) { obliterateMenu();
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;
GtkSupport.shutdownGui(); GtkSupport.shutdownGui();
libgtk.gdk_threads_leave(); libgtk.gdk_threads_leave();
@ -77,70 +50,171 @@ class GtkTypeSystemTray extends SystemTray {
} }
@Override @Override
public public synchronized
void setStatus(String infoString) { void setStatus(String infoString) {
synchronized (this.menuEntries) { libgtk.gdk_threads_enter();
libgtk.gdk_threads_enter();
if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) { if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
this.connectionStatusItem = libgtk.gtk_menu_item_new_with_label(""); 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... // evil hacks abound...
Pointer label = libgtk.gtk_bin_get_child(connectionStatusItem); Pointer label = libgtk.gtk_bin_get_child(connectionStatusItem);
libgtk.gtk_label_set_use_markup(label, Gtk.TRUE); libgtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = libgobject.g_markup_printf_escaped ("<b>%s</b>", infoString); Pointer markup = libgobject.g_markup_printf_escaped("<b>%s</b>", infoString);
libgtk.gtk_label_set_markup (label, markup); libgtk.gtk_label_set_markup(label, markup);
libgobject.g_free (markup); libgobject.g_free(markup);
libgtk.gtk_widget_show_all(menu);
this.widgets.add(this.connectionStatusItem);
libgtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE);
libgtk.gtk_menu_shell_prepend(this.menu, this.connectionStatusItem);
} }
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... libgtk.gdk_threads_leave();
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); * Called inside the gdk_threads block
libgobject.g_free (markup); */
} 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); // have to remove all other menu entries
libgtk.gdk_threads_leave(); 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 @Override
public public synchronized
void addMenuEntry(String menuText, final String imagePath, final SystemTrayMenuAction callback) { 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) { if (menuText == null) {
throw new NullPointerException("Menu text cannot be null"); throw new NullPointerException("Menu text cannot be null");
} }
synchronized (this.menuEntries) { GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText);
MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry != null) { if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'"); throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
} }
else { else {
libgtk.gdk_threads_enter(); libgtk.gdk_threads_enter();
menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, this);
libgtk.gdk_threads_leave();
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 @Override
public public
void run() { 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 : tray.menuEntries) {
for (MenuEntry menuEntry : SwingSystemTray.this.menuEntries) { menuEntry.remove();
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 @Override
public public
void run() { void run() {
if (SwingSystemTray.this.connectionStatusItem == null) { SwingSystemTray tray = SwingSystemTray.this;
final JMenuItem connectionStatusItem = new JMenuItem(infoString); synchronized (tray) {
Font font = connectionStatusItem.getFont(); if (tray.connectionStatusItem == null) {
Font font1 = font.deriveFont(Font.BOLD); final JMenuItem connectionStatusItem = new JMenuItem(infoString);
connectionStatusItem.setFont(font1); Font font = connectionStatusItem.getFont();
Font font1 = font.deriveFont(Font.BOLD);
connectionStatusItem.setFont(font1);
connectionStatusItem.setEnabled(false); connectionStatusItem.setEnabled(false);
SwingSystemTray.this.menu.add(connectionStatusItem); tray.menu.add(connectionStatusItem);
SwingSystemTray.this.connectionStatusItem = connectionStatusItem; tray.connectionStatusItem = connectionStatusItem;
} }
else { else {
SwingSystemTray.this.connectionStatusItem.setText(infoString); tray.connectionStatusItem.setText(infoString);
}
} }
} }
}); });
@ -175,11 +179,14 @@ class SwingSystemTray extends dorkbox.util.tray.SystemTray {
@Override @Override
public public
void run() { void run() {
String iconPath = iconPath(iconName); SwingSystemTray tray = SwingSystemTray.this;
Image trayImage = new ImageIcon(iconPath).getImage() synchronized (tray) {
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH); String iconPath = iconPath(iconName);
trayImage.flush(); Image trayImage = new ImageIcon(iconPath).getImage()
SwingSystemTray.this.trayIcon.setImage(trayImage); .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 @Override
public public
void run() { void run() {
synchronized (menuEntries) { SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) {
MenuEntry menuEntry = getMenuEntry(menuText); MenuEntry menuEntry = getMenuEntry(menuText);
if (menuEntry != null) { if (menuEntry != null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'"); throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'");
} }
else { else {
menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, SwingSystemTray.this); menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, tray);
menuEntries.add(menuEntry); menuEntries.add(menuEntry);
} }
} }