Cleaned up memory leaks. Added SystemTray.getStatus(). Cleaned up support for SWT.

This commit is contained in:
nathan 2016-02-15 13:27:33 +01:00
parent e129fd4e75
commit a68c79182d
12 changed files with 253 additions and 192 deletions

View File

@ -31,6 +31,11 @@ class ImageUtil {
private static final Map<String, String> resourceToFilePath = new HashMap<String, String>(); private static final Map<String, String> resourceToFilePath = new HashMap<String, String>();
private static final long runtimeRandom = new SecureRandom().nextLong(); private static final long runtimeRandom = new SecureRandom().nextLong();
public static synchronized
void init() throws NoSuchAlgorithmException {
ImageUtil.digest = MessageDigest.getInstance("MD5");
}
/** /**
* 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)
@ -198,6 +203,7 @@ class ImageUtil {
return newFile.getAbsolutePath(); return newFile.getAbsolutePath();
} }
// must be called from synchronized block
private static private static
String hashName(byte[] nameChars) { String hashName(byte[] nameChars) {
digest.reset(); digest.reset();
@ -214,9 +220,4 @@ class ImageUtil {
// convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36 // convert to alpha-numeric. see https://stackoverflow.com/questions/29183818/why-use-tostring32-and-not-tostring36
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US); return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US);
} }
public static synchronized
void init() throws NoSuchAlgorithmException {
ImageUtil.digest = MessageDigest.getInstance("MD5");
}
} }

View File

@ -95,10 +95,10 @@ class SystemTray {
} }
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be GTK2!! // maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be GTK2!!
COMPATIBILITY_MODE = isJavaFxLoaded || isSwtLoaded; COMPATIBILITY_MODE = OS.isLinux() && (isJavaFxLoaded || isSwtLoaded);
// kablooie if SWT is not configured in a way that works with us. // kablooie if SWT is not configured in a way that works with us.
if (isSwtLoaded) { if (OS.isLinux() && isSwtLoaded) {
// Necessary for us to work with SWT // Necessary for us to work with SWT
// System.setProperty("SWT_GTK3", "0"); // Necessary for us to work with SWT // System.setProperty("SWT_GTK3", "0"); // Necessary for us to work with SWT
@ -442,14 +442,19 @@ class SystemTray {
public abstract public abstract
void shutdown(); void shutdown();
/**
* Gets the 'status' string assigned to the system tray
*/
public abstract
String getStatus();
/** /**
* Sets a 'status' string at the first position in the popup menu. This 'status' string appears as a disabled menu entry. * Sets a 'status' string at the first position in the popup menu. This 'status' string appears as a disabled menu entry.
* *
* @param infoString 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' string
*/ */
public abstract public abstract
void setStatus(String infoString); void setStatus(String statusText);
protected abstract protected abstract
void setIcon_(String iconPath); void setIcon_(String iconPath);

View File

@ -19,6 +19,8 @@ import com.sun.jna.Pointer;
import dorkbox.systemTray.linux.jna.AppIndicator; import dorkbox.systemTray.linux.jna.AppIndicator;
import dorkbox.systemTray.linux.jna.GtkSupport; import dorkbox.systemTray.linux.jna.GtkSupport;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Class for handling all system tray interactions. * Class for handling all system tray interactions.
* <p/> * <p/>
@ -34,6 +36,9 @@ class AppIndicatorTray extends GtkTypeSystemTray {
private AppIndicator.AppIndicatorInstanceStruct appIndicator; private AppIndicator.AppIndicatorInstanceStruct appIndicator;
private boolean isActive = false; private boolean isActive = false;
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
private AtomicBoolean shuttingDown = new AtomicBoolean();
public public
AppIndicatorTray() { AppIndicatorTray() {
GtkSupport.startGui(); GtkSupport.startGui();
@ -51,21 +56,22 @@ class AppIndicatorTray extends GtkTypeSystemTray {
@Override @Override
public public
void shutdown() { void shutdown() {
GtkSupport.dispatch(new Runnable() { if (!shuttingDown.getAndSet(true)) {
@Override GtkSupport.dispatch(new Runnable() {
public @Override
void run() { public
// this hides the indicator void run() {
appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); // STATUS_PASSIVE hides the indicator
Pointer p = appIndicator.getPointer(); appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
gobject.g_object_unref(p); Pointer p = appIndicator.getPointer();
gobject.g_object_unref(p);
// GC it appIndicator = null;
appIndicator = null; }
} });
});
super.shutdown(); super.shutdown();
}
} }
@Override @Override
@ -91,12 +97,7 @@ class AppIndicatorTray extends GtkTypeSystemTray {
*/ */
protected protected
void onMenuAdded(final Pointer menu) { void onMenuAdded(final Pointer menu) {
GtkSupport.dispatch(new Runnable() { // see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
@Override appindicator.app_indicator_set_menu(appIndicator, menu);
public
void run() {
appindicator.app_indicator_set_menu(appIndicator, menu);
}
});
} }
} }

View File

@ -27,17 +27,19 @@ import dorkbox.systemTray.linux.jna.GtkSupport;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
class GtkMenuEntry implements MenuEntry, GCallback {
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
private final int id = ID_COUNTER.getAndIncrement();
class GtkMenuEntry implements MenuEntry {
private static final Gtk gtk = Gtk.INSTANCE; private static final Gtk gtk = Gtk.INSTANCE;
private static final Gobject gobject = Gobject.INSTANCE; private static final Gobject gobject = Gobject.INSTANCE;
@SuppressWarnings("FieldCanBeLocal")
private final GCallback gtkCallback;
final Pointer menuItem; final Pointer menuItem;
private final Pointer parentMenu; final GtkTypeSystemTray parent;
final GtkTypeSystemTray systemTray;
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final NativeLong nativeLong; private final NativeLong nativeLong;
// these have to be volatile, because they can be changed from any thread // these have to be volatile, because they can be changed from any thread
@ -45,23 +47,14 @@ class GtkMenuEntry implements MenuEntry {
private volatile SystemTrayMenuAction callback; private volatile SystemTrayMenuAction callback;
private volatile Pointer image; private volatile Pointer image;
// called from inside dispatch thread /**
GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback, * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
final GtkTypeSystemTray systemTray) { * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
this.parentMenu = parentMenu; */
GtkMenuEntry(final String label, final String imagePath, final SystemTrayMenuAction callback, final GtkTypeSystemTray parent) {
this.parent = parent;
this.text = label; this.text = label;
this.callback = callback; this.callback = callback;
this.systemTray = systemTray;
// have to watch out! This can get garbage collected (so it MUST be a field)!
gtkCallback = new Gobject.GCallback() {
@Override
public
int callback(Pointer instance, Pointer data) {
handle();
return Gtk.TRUE;
}
};
menuItem = gtk.gtk_image_menu_item_new_with_label(label); menuItem = gtk.gtk_image_menu_item_new_with_label(label);
@ -76,21 +69,26 @@ class GtkMenuEntry implements MenuEntry {
gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
} }
nativeLong = gobject.g_signal_connect_data(menuItem, "activate", gtkCallback, null, null, 0); nativeLong = gobject.g_signal_connect_object(menuItem, "activate", this, null, 0);
} }
private
void handle() { // called by native code
@Override
public
int callback(final Pointer instance, final Pointer data) {
final SystemTrayMenuAction cb = this.callback; final SystemTrayMenuAction cb = this.callback;
if (cb != null) { if (cb != null) {
GtkTypeSystemTray.callbackExecutor.execute(new Runnable() { GtkTypeSystemTray.callbackExecutor.execute(new Runnable() {
@Override @Override
public public
void run() { void run() {
cb.onClick(systemTray, GtkMenuEntry.this); cb.onClick(parent, GtkMenuEntry.this);
} }
}); });
} }
return Gtk.TRUE;
} }
@Override @Override
@ -109,7 +107,7 @@ class GtkMenuEntry implements MenuEntry {
text = newText; text = newText;
gtk.gtk_menu_item_set_label(menuItem, newText); gtk.gtk_menu_item_set_label(menuItem, newText);
gtk.gtk_widget_show_all(parentMenu); gtk.gtk_widget_show_all(menuItem);
} }
}); });
} }
@ -120,21 +118,23 @@ class GtkMenuEntry implements MenuEntry {
@Override @Override
public public
void run() { void run() {
if (image != null) {
gtk.gtk_widget_destroy(image);
image = null;
}
gtk.gtk_widget_show_all(menuItem);
if (imagePath != null && !imagePath.isEmpty()) { if (imagePath != null && !imagePath.isEmpty()) {
if (image != null) {
gtk.gtk_widget_destroy(image);
}
gtk.gtk_widget_show_all(parentMenu);
image = gtk.gtk_image_new_from_file(imagePath); image = gtk.gtk_image_new_from_file(imagePath);
gtk.gtk_image_menu_item_set_image(menuItem, image); gtk.gtk_image_menu_item_set_image(menuItem, image);
gobject.g_object_ref_sink(image);
// must always re-set always-show after setting the image // must always re-set always-show after setting the image
gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
} }
gtk.gtk_widget_show_all(parentMenu); gtk.gtk_widget_show_all(menuItem);
} }
}); });
} }
@ -202,26 +202,27 @@ class GtkMenuEntry implements MenuEntry {
removePrivate(); removePrivate();
// have to rebuild the menu now... // have to rebuild the menu now...
systemTray.deleteMenu(); parent.deleteMenu();
systemTray.createMenu(); parent.createMenu();
} }
}); });
} }
void removePrivate() { void removePrivate() {
gobject.g_signal_handler_disconnect(menuItem, nativeLong); callback = null;
gtk.gtk_menu_shell_deactivate(parentMenu, menuItem); gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem);
if (image != null) { if (image != null) {
gtk.gtk_widget_destroy(image); gtk.gtk_widget_destroy(image);
} }
gtk.gtk_widget_destroy(menuItem); gtk.gtk_widget_destroy(menuItem);
} }
@Override @Override
public public
int hashCode() { int hashCode() {
return 0; return id;
} }
@ -237,7 +238,8 @@ class GtkMenuEntry implements MenuEntry {
if (getClass() != obj.getClass()) { if (getClass() != obj.getClass()) {
return false; return false;
} }
GtkMenuEntry other = (GtkMenuEntry) obj; GtkMenuEntry other = (GtkMenuEntry) obj;
return this.text.equals(other.text); return this.id == other.id;
} }
} }

View File

@ -21,6 +21,8 @@ import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.linux.jna.GtkSupport; import dorkbox.systemTray.linux.jna.GtkSupport;
import java.util.concurrent.atomic.AtomicBoolean;
/** /**
* Class for handling all system tray interactions via GTK. * Class for handling all system tray interactions via GTK.
* <p/> * <p/>
@ -36,8 +38,10 @@ class GtkSystemTray extends GtkTypeSystemTray {
@SuppressWarnings({"FieldCanBeLocal", "unused"}) @SuppressWarnings({"FieldCanBeLocal", "unused"})
private NativeLong button_press_event; private NativeLong button_press_event;
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
private AtomicBoolean shuttingDown = new AtomicBoolean();
private volatile boolean isActive = false; private volatile boolean isActive = false;
private volatile Pointer menu;
public public
GtkSystemTray() { GtkSystemTray() {
@ -60,41 +64,36 @@ class GtkSystemTray extends GtkTypeSystemTray {
void callback(Pointer notUsed, 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) {
gtk.gtk_menu_popup(menu, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); gtk.gtk_menu_popup(getMenu(), null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time);
} }
} }
}; };
button_press_event = gobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, null, null, 0); button_press_event = gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, null, 0);
} }
}); });
} }
/**
* Called inside the gdk_threads block
*/
protected
void onMenuAdded(final Pointer menu) {
this.menu = menu;
}
@SuppressWarnings("FieldRepeatedlyAccessedInMethod") @SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@Override @Override
public public
void shutdown() { void shutdown() {
GtkSupport.dispatch(new Runnable() { if (!shuttingDown.getAndSet(true)) {
@Override GtkSupport.dispatch(new Runnable() {
public @Override
void run() { public
// this hides the indicator void run() {
gtk.gtk_status_icon_set_visible(trayIcon, false); // this hides the indicator
gobject.g_object_unref(trayIcon); gtk.gtk_status_icon_set_visible(trayIcon, false);
gobject.g_object_unref(trayIcon);
// GC it // GC it
trayIcon = null; trayIcon = null;
} gtkCallback = null;
}); }
});
super.shutdown(); super.shutdown();
}
} }
@Override @Override

View File

@ -20,15 +20,16 @@ import com.sun.jna.Pointer;
import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.NamedThreadFactory;
import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.linux.jna.GtkSupport; import dorkbox.systemTray.linux.jna.GtkSupport;
import dorkbox.util.NamedThreadFactory;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/** /**
* Derived from * Derived from
@ -41,8 +42,11 @@ class GtkTypeSystemTray extends SystemTray {
final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false));
private Pointer menu; private volatile Pointer menu;
private Pointer connectionStatusItem;
private volatile Pointer connectionStatusItem;
private volatile String statusText = null;
@Override @Override
public public
@ -52,54 +56,77 @@ class GtkTypeSystemTray extends SystemTray {
public public
void run() { void run() {
obliterateMenu(); obliterateMenu();
GtkSupport.shutdownGui();
callbackExecutor.shutdown(); boolean terminated = false;
try {
terminated = callbackExecutor.awaitTermination(1, TimeUnit.SECONDS);
} catch (InterruptedException ignored) {
}
if (!terminated) {
callbackExecutor.shutdownNow();
}
GtkSupport.shutdownGui();
} }
}); });
} }
@Override @Override
public public
void setStatus(final String infoString) { String getStatus() {
return statusText;
}
@Override
public
void setStatus(final String statusText) {
this.statusText = statusText;
GtkSupport.dispatch(new Runnable() { GtkSupport.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
if (connectionStatusItem == null && infoString != null && !infoString.isEmpty()) { // 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 something is changed.
if (connectionStatusItem == null && statusText != null && !statusText.isEmpty()) {
deleteMenu(); deleteMenu();
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... // evil hacks abound...
Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); Pointer label = gtk.gtk_bin_get_child(connectionStatusItem);
gtk.gtk_label_set_use_markup(label, Gtk.TRUE); gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", infoString); Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
gtk.gtk_label_set_markup(label, markup); gtk.gtk_label_set_markup(label, markup);
gobject.g_free(markup); gobject.g_free(markup);
gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE); gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE);
createMenu(); createMenu();
} }
else { else {
if (infoString == null || infoString.isEmpty()) { if (statusText == null || statusText.isEmpty()) {
deleteMenu(); // this means the status text already exists, and we are removing it
gtk.gtk_widget_destroy(connectionStatusItem);
connectionStatusItem = null;
gtk.gtk_container_remove(menu, connectionStatusItem);
connectionStatusItem = null; // because we manually delete it
gtk.gtk_widget_show_all(menu);
deleteMenu();
createMenu(); createMenu();
} }
else { else {
// here we set the text only. it already exists
// set bold instead // set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); // libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText);
// evil hacks abound... // evil hacks abound...
Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); Pointer label = gtk.gtk_bin_get_child(connectionStatusItem);
gtk.gtk_label_set_use_markup(label, Gtk.TRUE); gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", infoString); Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
gtk.gtk_label_set_markup(label, markup); gtk.gtk_label_set_markup(label, markup);
gobject.g_free(markup); gobject.g_free(markup);
@ -110,12 +137,59 @@ class GtkTypeSystemTray extends SystemTray {
}); });
} }
// 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 something is changed.
/** /**
* Called inside the gdk_threads block * Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object.
*/ */
protected abstract void deleteMenu() {
void onMenuAdded(final Pointer menu); if (menu != null) {
// have to remove status from menu (but not destroy the object)
if (connectionStatusItem != null) {
gobject.g_object_force_floating(connectionStatusItem);
gtk.gtk_container_remove(menu, connectionStatusItem);
}
// have to remove all other menu entries
synchronized (menuEntries) {
for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
gobject.g_object_force_floating(menuEntry__.menuItem);
gtk.gtk_container_remove(menu, menuEntry__.menuItem);
}
gtk.gtk_widget_destroy(menu);
}
}
// makes a new one
menu = gtk.gtk_menu_new();
}
// 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 something is changed.
void createMenu() {
// now add status
if (connectionStatusItem != null) {
gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
gobject.g_object_ref_sink(connectionStatusItem);
}
// now add back other menu entries
synchronized (menuEntries) {
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'
gtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem);
gobject.g_object_ref_sink(menuEntry__.menuItem);
}
onMenuAdded(menu);
gtk.gtk_widget_show_all(menu);
}
}
/** /**
* Completely obliterates the menu, no possible way to reconstruct it. * Completely obliterates the menu, no possible way to reconstruct it.
@ -130,64 +204,28 @@ class GtkTypeSystemTray extends SystemTray {
} }
// have to remove all other menu entries // have to remove all other menu entries
for (int i = 0; i < menuEntries.size(); i++) { synchronized (menuEntries) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
menuEntry__.removePrivate(); menuEntry__.removePrivate();
}
menuEntries.clear();
gtk.gtk_widget_destroy(menu);
} }
menuEntries.clear();
// GTK menu needs a "ref_sink"
gobject.g_object_ref_sink(menu);
} }
} }
/** /**
* Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object. * Called inside the gdk_threads block
*/ */
void deleteMenu() { protected
if (menu != null) { void onMenuAdded(final Pointer menu) {};
// have to remove status from menu
if (connectionStatusItem != null) {
gobject.g_object_ref(connectionStatusItem);
gtk.gtk_container_remove(menu, connectionStatusItem); protected
} Pointer getMenu() {
return menu;
// have to remove all other menu entries
for (int i = 0; i < menuEntries.size(); i++) {
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
gobject.g_object_ref(menuEntry__.menuItem);
gtk.gtk_container_remove(menu, menuEntry__.menuItem);
}
// GTK menu needs a "ref_sink"
gobject.g_object_ref_sink(menu);
}
menu = gtk.gtk_menu_new();
}
// UNSAFE. must be protected inside dispatch
void createMenu() {
// now add status
if (connectionStatusItem != null) {
gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
gobject.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'
gtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem);
gobject.g_object_unref(menuEntry__.menuItem);
}
onMenuAdded(menu);
gtk.gtk_widget_show_all(menu);
} }
private private
@ -208,12 +246,10 @@ class GtkTypeSystemTray extends SystemTray {
if (menuEntry == null) { if (menuEntry == null) {
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. // 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. // To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu(); deleteMenu();
menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, GtkTypeSystemTray.this); menuEntry = new GtkMenuEntry(menuText, imagePath, callback, GtkTypeSystemTray.this);
gobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu'
menuEntries.add(menuEntry); menuEntries.add(menuEntry);
createMenu(); createMenu();

View File

@ -161,14 +161,12 @@ interface Gobject extends Library {
void g_free(Pointer object); void g_free(Pointer object);
void g_object_ref(Pointer object);
void g_object_unref(Pointer object); void g_object_unref(Pointer object);
void g_object_force_floating(Pointer object);
void g_object_ref_sink(Pointer object); void g_object_ref_sink(Pointer object);
NativeLong g_signal_connect_data(Pointer instance, String detailed_signal, Callback c_handler, Pointer data, Pointer destroy_data, NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags);
int connect_flags);
void g_signal_handler_disconnect(Pointer instance, NativeLong longAddress);
Pointer g_markup_printf_escaped(String pattern, String inputString); Pointer g_markup_printf_escaped(String pattern, String inputString);
} }

View File

@ -47,8 +47,6 @@ class GtkSupport {
Gtk library; Gtk library;
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE; boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE;
alreadyRunningGTK = SystemTray.COMPATIBILITY_MODE;
// for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm // for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
@ -63,7 +61,7 @@ class GtkSupport {
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK |= library.gtk_main_level() != 0; alreadyRunningGTK = library.gtk_main_level() != 0;
return library; return library;
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -79,7 +77,7 @@ class GtkSupport {
if (library != null) { if (library != null) {
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK |= library.gtk_main_level() != 0; alreadyRunningGTK = library.gtk_main_level() != 0;
return library; return library;
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -94,7 +92,7 @@ class GtkSupport {
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK |= library.gtk_main_level() != 0; alreadyRunningGTK = library.gtk_main_level() != 0;
return library; return library;
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -111,7 +109,7 @@ class GtkSupport {
if (library != null) { if (library != null) {
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK |= library.gtk_main_level() != 0; alreadyRunningGTK = library.gtk_main_level() != 0;
return library; return library;
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -126,7 +124,7 @@ class GtkSupport {
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK |= library.gtk_main_level() != 0; alreadyRunningGTK = library.gtk_main_level() != 0;
return library; return library;
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -153,7 +151,13 @@ class GtkSupport {
final Runnable take = dispatchEvents.take(); final Runnable take = dispatchEvents.take();
gtk.gdk_threads_enter(); gtk.gdk_threads_enter();
take.run();
try {
take.run();
} catch (Throwable e) {
e.printStackTrace();
}
gtk.gdk_threads_leave(); gtk.gdk_threads_leave();
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -183,13 +187,17 @@ class GtkSupport {
gtk.gdk_threads_enter(); gtk.gdk_threads_enter();
GThread.INSTANCE.g_thread_init(null); GThread.INSTANCE.g_thread_init(null);
gtk.gtk_init_check(0, null);
if (!SystemTray.COMPATIBILITY_MODE) {
gtk.gtk_init_check(0, null);
}
// notify our main thread to continue // notify our main thread to continue
blockUntilStarted.countDown(); blockUntilStarted.countDown();
// blocks unit quit if (!SystemTray.COMPATIBILITY_MODE) {
gtk.gtk_main(); // blocks unit quit
gtk.gtk_main();
}
gtk.gdk_threads_leave(); gtk.gdk_threads_leave();
} }
@ -222,7 +230,7 @@ class GtkSupport {
public static public static
void shutdownGui() { void shutdownGui() {
// If JavaFX/SWT is used, this is UNNECESSARY (an will break SWT/JavaFX shutdown) // If JavaFX/SWT is used, this is UNNECESSARY (an will break SWT/JavaFX shutdown)
if (!alreadyRunningGTK) { if (!(alreadyRunningGTK || SystemTray.COMPATIBILITY_MODE)) {
Gtk.INSTANCE.gtk_main_quit(); Gtk.INSTANCE.gtk_main_quit();
} }

View File

@ -42,7 +42,9 @@ import java.net.URL;
public public
class SwingSystemTray extends dorkbox.systemTray.SystemTray { class SwingSystemTray extends dorkbox.systemTray.SystemTray {
volatile SwingSystemTrayMenuPopup menu; volatile SwingSystemTrayMenuPopup menu;
volatile JMenuItem connectionStatusItem; volatile JMenuItem connectionStatusItem;
private volatile String statusText = null;
volatile SystemTray tray; volatile SystemTray tray;
volatile TrayIcon trayIcon; volatile TrayIcon trayIcon;
@ -92,7 +94,15 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
@Override @Override
public public
void setStatus(final String infoString) { String getStatus() {
return this.statusText;
}
@Override
public
void setStatus(final String statusText) {
this.statusText = statusText;
SwingUtil.invokeLater(new Runnable() { SwingUtil.invokeLater(new Runnable() {
@Override @Override
public public
@ -100,7 +110,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
SwingSystemTray tray = SwingSystemTray.this; SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) { synchronized (tray) {
if (tray.connectionStatusItem == null) { if (tray.connectionStatusItem == null) {
final JMenuItem connectionStatusItem = new JMenuItem(infoString); final JMenuItem connectionStatusItem = new JMenuItem(statusText);
Font font = connectionStatusItem.getFont(); Font font = connectionStatusItem.getFont();
Font font1 = font.deriveFont(Font.BOLD); Font font1 = font.deriveFont(Font.BOLD);
connectionStatusItem.setFont(font1); connectionStatusItem.setFont(font1);
@ -111,7 +121,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
tray.connectionStatusItem = connectionStatusItem; tray.connectionStatusItem = connectionStatusItem;
} }
else { else {
tray.connectionStatusItem.setText(infoString); tray.connectionStatusItem.setText(statusText);
} }
} }
} }

View File

@ -29,7 +29,6 @@ import java.net.URL;
public public
class TestTray { class TestTray {
// horribly hacky. ONLY FOR TESTING!
public static final URL BLACK_MAIL = TestTray.class.getResource("mail.000000.24.png"); public static final URL BLACK_MAIL = TestTray.class.getResource("mail.000000.24.png");
public static final URL GREEN_MAIL = TestTray.class.getResource("mail.39AC39.24.png"); public static final URL GREEN_MAIL = TestTray.class.getResource("mail.39AC39.24.png");
public static final URL LT_GRAY_MAIL = TestTray.class.getResource("mail.999999.24.png"); public static final URL LT_GRAY_MAIL = TestTray.class.getResource("mail.999999.24.png");
@ -65,6 +64,7 @@ class TestTray {
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.setStatus("Some Mail!"); systemTray.setStatus("Some Mail!");
systemTray.setIcon(GREEN_MAIL); systemTray.setIcon(GREEN_MAIL);
menuEntry.setCallback(callbackGray); menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); menuEntry.setText("Delete Mail");
@ -78,6 +78,7 @@ class TestTray {
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.setStatus(null); systemTray.setStatus(null);
systemTray.setIcon(BLACK_MAIL); systemTray.setIcon(BLACK_MAIL);
menuEntry.setCallback(null); menuEntry.setCallback(null);
// systemTray.setStatus("Mail Empty"); // systemTray.setStatus("Mail Empty");
systemTray.removeMenuEntry(menuEntry); systemTray.removeMenuEntry(menuEntry);

View File

@ -39,7 +39,6 @@ import java.net.URL;
public public
class TestTrayJavaFX extends Application { class TestTrayJavaFX extends Application {
// horribly hacky. ONLY FOR TESTING!
public static final URL BLACK_MAIL = TestTrayJavaFX.class.getResource("mail.000000.24.png"); public static final URL BLACK_MAIL = TestTrayJavaFX.class.getResource("mail.000000.24.png");
public static final URL GREEN_MAIL = TestTrayJavaFX.class.getResource("mail.39AC39.24.png"); public static final URL GREEN_MAIL = TestTrayJavaFX.class.getResource("mail.39AC39.24.png");
public static final URL LT_GRAY_MAIL = TestTrayJavaFX.class.getResource("mail.999999.24.png"); public static final URL LT_GRAY_MAIL = TestTrayJavaFX.class.getResource("mail.999999.24.png");
@ -97,8 +96,9 @@ class TestTrayJavaFX extends Application {
@Override @Override
public public
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.setStatus("Some Mail!");
systemTray.setIcon(GREEN_MAIL); systemTray.setIcon(GREEN_MAIL);
systemTray.setStatus("Some Mail!");
menuEntry.setCallback(callbackGray); menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); menuEntry.setText("Delete Mail");
@ -112,6 +112,7 @@ class TestTrayJavaFX extends Application {
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.setStatus(null); systemTray.setStatus(null);
systemTray.setIcon(BLACK_MAIL); systemTray.setIcon(BLACK_MAIL);
menuEntry.setCallback(null); menuEntry.setCallback(null);
// systemTray.setStatus("Mail Empty"); // systemTray.setStatus("Mail Empty");
systemTray.removeMenuEntry(menuEntry); systemTray.removeMenuEntry(menuEntry);

View File

@ -35,7 +35,6 @@ import java.net.URL;
public public
class TestTraySwt { class TestTraySwt {
// horribly hacky. ONLY FOR TESTING!
public static final URL BLACK_MAIL = TestTraySwt.class.getResource("mail.000000.24.png"); public static final URL BLACK_MAIL = TestTraySwt.class.getResource("mail.000000.24.png");
public static final URL GREEN_MAIL = TestTraySwt.class.getResource("mail.39AC39.24.png"); public static final URL GREEN_MAIL = TestTraySwt.class.getResource("mail.39AC39.24.png");
public static final URL LT_GRAY_MAIL = TestTraySwt.class.getResource("mail.999999.24.png"); public static final URL LT_GRAY_MAIL = TestTraySwt.class.getResource("mail.999999.24.png");
@ -55,7 +54,7 @@ class TestTraySwt {
public public
TestTraySwt() { TestTraySwt() {
Display display = new Display (); final Display display = new Display ();
final Shell shell = new Shell(display); final Shell shell = new Shell(display);
Text helloWorldTest = new Text(shell, SWT.NONE); Text helloWorldTest = new Text(shell, SWT.NONE);
@ -77,8 +76,8 @@ class TestTraySwt {
public public
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.setStatus("Some Mail!"); systemTray.setStatus("Some Mail!");
systemTray.setIcon(GREEN_MAIL); systemTray.setIcon(GREEN_MAIL);
menuEntry.setCallback(callbackGray); menuEntry.setCallback(callbackGray);
menuEntry.setImage(BLACK_MAIL); menuEntry.setImage(BLACK_MAIL);
menuEntry.setText("Delete Mail"); menuEntry.setText("Delete Mail");
@ -108,9 +107,9 @@ class TestTraySwt {
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.shutdown(); systemTray.shutdown();
Display.getDefault().asyncExec(new Runnable() { display.asyncExec(new Runnable() {
public void run() { public void run() {
shell.close(); // close down SWT shell shell.dispose();
} }
}); });