diff --git a/src/dorkbox/systemTray/ImageUtil.java b/src/dorkbox/systemTray/ImageUtil.java index 95c2f57..3a29af2 100644 --- a/src/dorkbox/systemTray/ImageUtil.java +++ b/src/dorkbox/systemTray/ImageUtil.java @@ -31,6 +31,11 @@ class ImageUtil { private static final Map resourceToFilePath = new HashMap(); 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) * swing version loads as an image (which can be stream or path, we use path) @@ -198,6 +203,7 @@ class ImageUtil { return newFile.getAbsolutePath(); } + // must be called from synchronized block private static String hashName(byte[] nameChars) { digest.reset(); @@ -214,9 +220,4 @@ class ImageUtil { // 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); } - - public static synchronized - void init() throws NoSuchAlgorithmException { - ImageUtil.digest = MessageDigest.getInstance("MD5"); - } } diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 57688f5..155f8f0 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -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!! - COMPATIBILITY_MODE = isJavaFxLoaded || isSwtLoaded; + COMPATIBILITY_MODE = OS.isLinux() && (isJavaFxLoaded || isSwtLoaded); // 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 // System.setProperty("SWT_GTK3", "0"); // Necessary for us to work with SWT @@ -442,14 +442,19 @@ class SystemTray { public abstract 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. * - * @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 - void setStatus(String infoString); + void setStatus(String statusText); protected abstract void setIcon_(String iconPath); diff --git a/src/dorkbox/systemTray/linux/AppIndicatorTray.java b/src/dorkbox/systemTray/linux/AppIndicatorTray.java index ffb740c..ddb9b0b 100644 --- a/src/dorkbox/systemTray/linux/AppIndicatorTray.java +++ b/src/dorkbox/systemTray/linux/AppIndicatorTray.java @@ -19,6 +19,8 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.linux.jna.AppIndicator; import dorkbox.systemTray.linux.jna.GtkSupport; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Class for handling all system tray interactions. *

@@ -34,6 +36,9 @@ class AppIndicatorTray extends GtkTypeSystemTray { private AppIndicator.AppIndicatorInstanceStruct appIndicator; 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 AppIndicatorTray() { GtkSupport.startGui(); @@ -51,21 +56,22 @@ class AppIndicatorTray extends GtkTypeSystemTray { @Override public void shutdown() { - GtkSupport.dispatch(new Runnable() { - @Override - public - void run() { - // this hides the indicator - appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); - Pointer p = appIndicator.getPointer(); - gobject.g_object_unref(p); + if (!shuttingDown.getAndSet(true)) { + GtkSupport.dispatch(new Runnable() { + @Override + public + void run() { + // STATUS_PASSIVE hides the indicator + appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); + Pointer p = appIndicator.getPointer(); + gobject.g_object_unref(p); - // GC it - appIndicator = null; - } - }); + appIndicator = null; + } + }); - super.shutdown(); + super.shutdown(); + } } @Override @@ -91,12 +97,7 @@ class AppIndicatorTray extends GtkTypeSystemTray { */ protected void onMenuAdded(final Pointer menu) { - GtkSupport.dispatch(new Runnable() { - @Override - public - void run() { - appindicator.app_indicator_set_menu(appIndicator, menu); - } - }); + // see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247 + appindicator.app_indicator_set_menu(appIndicator, menu); } } diff --git a/src/dorkbox/systemTray/linux/GtkMenuEntry.java b/src/dorkbox/systemTray/linux/GtkMenuEntry.java index 4207248..7c1fbad 100644 --- a/src/dorkbox/systemTray/linux/GtkMenuEntry.java +++ b/src/dorkbox/systemTray/linux/GtkMenuEntry.java @@ -27,17 +27,19 @@ import dorkbox.systemTray.linux.jna.GtkSupport; import java.io.InputStream; 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 Gobject gobject = Gobject.INSTANCE; - @SuppressWarnings("FieldCanBeLocal") - private final GCallback gtkCallback; - final Pointer menuItem; - private final Pointer parentMenu; - final GtkTypeSystemTray systemTray; + final GtkTypeSystemTray parent; + + @SuppressWarnings({"FieldCanBeLocal", "unused"}) private final NativeLong nativeLong; // 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 Pointer image; - // called from inside dispatch thread - GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback, - final GtkTypeSystemTray systemTray) { - this.parentMenu = parentMenu; + /** + * called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it! + * this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref + */ + GtkMenuEntry(final String label, final String imagePath, final SystemTrayMenuAction callback, final GtkTypeSystemTray parent) { + this.parent = parent; this.text = label; 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); @@ -76,21 +69,26 @@ class GtkMenuEntry implements MenuEntry { 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; if (cb != null) { GtkTypeSystemTray.callbackExecutor.execute(new Runnable() { @Override public void run() { - cb.onClick(systemTray, GtkMenuEntry.this); + cb.onClick(parent, GtkMenuEntry.this); } }); } + + return Gtk.TRUE; } @Override @@ -109,7 +107,7 @@ class GtkMenuEntry implements MenuEntry { text = 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 public void run() { + if (image != null) { + gtk.gtk_widget_destroy(image); + image = null; + } + + gtk.gtk_widget_show_all(menuItem); + 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); - 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 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(); // have to rebuild the menu now... - systemTray.deleteMenu(); - systemTray.createMenu(); + parent.deleteMenu(); + parent.createMenu(); } }); } void removePrivate() { - gobject.g_signal_handler_disconnect(menuItem, nativeLong); - gtk.gtk_menu_shell_deactivate(parentMenu, menuItem); + callback = null; + gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem); if (image != null) { gtk.gtk_widget_destroy(image); } + gtk.gtk_widget_destroy(menuItem); } @Override public int hashCode() { - return 0; + return id; } @@ -237,7 +238,8 @@ class GtkMenuEntry implements MenuEntry { if (getClass() != obj.getClass()) { return false; } + GtkMenuEntry other = (GtkMenuEntry) obj; - return this.text.equals(other.text); + return this.id == other.id; } } diff --git a/src/dorkbox/systemTray/linux/GtkSystemTray.java b/src/dorkbox/systemTray/linux/GtkSystemTray.java index 84db898..7b31d3c 100644 --- a/src/dorkbox/systemTray/linux/GtkSystemTray.java +++ b/src/dorkbox/systemTray/linux/GtkSystemTray.java @@ -21,6 +21,8 @@ import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.GtkSupport; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Class for handling all system tray interactions via GTK. *

@@ -36,8 +38,10 @@ class GtkSystemTray extends GtkTypeSystemTray { @SuppressWarnings({"FieldCanBeLocal", "unused"}) 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 Pointer menu; public GtkSystemTray() { @@ -60,41 +64,36 @@ class GtkSystemTray extends GtkTypeSystemTray { void callback(Pointer notUsed, final Gtk.GdkEventButton event) { // BUTTON_PRESS only (any mouse click) 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") @Override public void shutdown() { - GtkSupport.dispatch(new Runnable() { - @Override - public - void run() { - // this hides the indicator - gtk.gtk_status_icon_set_visible(trayIcon, false); - gobject.g_object_unref(trayIcon); + if (!shuttingDown.getAndSet(true)) { + GtkSupport.dispatch(new Runnable() { + @Override + public + void run() { + // this hides the indicator + gtk.gtk_status_icon_set_visible(trayIcon, false); + gobject.g_object_unref(trayIcon); - // GC it - trayIcon = null; - } - }); + // GC it + trayIcon = null; + gtkCallback = null; + } + }); - super.shutdown(); + super.shutdown(); + } } @Override diff --git a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java b/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java index 77b84f2..f10c41b 100644 --- a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java +++ b/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java @@ -20,15 +20,16 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTrayMenuAction; -import dorkbox.util.NamedThreadFactory; import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.GtkSupport; +import dorkbox.util.NamedThreadFactory; import java.io.InputStream; import java.net.URL; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; /** * Derived from @@ -41,8 +42,11 @@ class GtkTypeSystemTray extends SystemTray { final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); - private Pointer menu; - private Pointer connectionStatusItem; + private volatile Pointer menu; + + private volatile Pointer connectionStatusItem; + private volatile String statusText = null; + @Override public @@ -52,54 +56,77 @@ class GtkTypeSystemTray extends SystemTray { public void run() { 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 public - void setStatus(final String infoString) { + String getStatus() { + return statusText; + } + + @Override + public + void setStatus(final String statusText) { + this.statusText = statusText; + GtkSupport.dispatch(new Runnable() { @Override public 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(); connectionStatusItem = gtk.gtk_menu_item_new_with_label(""); - gobject.g_object_ref(connectionStatusItem); // so it matches with 'createMenu' // evil hacks abound... Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); gtk.gtk_label_set_use_markup(label, Gtk.TRUE); - Pointer markup = gobject.g_markup_printf_escaped("%s", infoString); + Pointer markup = gobject.g_markup_printf_escaped("%s", statusText); gtk.gtk_label_set_markup(label, markup); gobject.g_free(markup); - gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE); createMenu(); } else { - if (infoString == null || infoString.isEmpty()) { - deleteMenu(); - gtk.gtk_widget_destroy(connectionStatusItem); - connectionStatusItem = null; + if (statusText == null || statusText.isEmpty()) { + // this means the status text already exists, and we are removing it + gtk.gtk_container_remove(menu, connectionStatusItem); + connectionStatusItem = null; // because we manually delete it + + gtk.gtk_widget_show_all(menu); + + deleteMenu(); createMenu(); } else { + // here we set the text only. it already exists + // set bold instead - // libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString); + // libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText); // evil hacks abound... Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); gtk.gtk_label_set_use_markup(label, Gtk.TRUE); - Pointer markup = gobject.g_markup_printf_escaped("%s", infoString); + Pointer markup = gobject.g_markup_printf_escaped("%s", statusText); gtk.gtk_label_set_markup(label, 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 onMenuAdded(final Pointer menu); + void deleteMenu() { + 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. @@ -130,64 +204,28 @@ class GtkTypeSystemTray extends SystemTray { } // have to remove all other menu entries - for (int i = 0; i < menuEntries.size(); i++) { - GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); + synchronized (menuEntries) { + 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() { - if (menu != null) { - // have to remove status from menu - if (connectionStatusItem != null) { - gobject.g_object_ref(connectionStatusItem); + protected + void onMenuAdded(final Pointer menu) {}; - gtk.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); - - 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); + protected + Pointer getMenu() { + return menu; } private @@ -208,12 +246,10 @@ class GtkTypeSystemTray extends SystemTray { if (menuEntry == null) { // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. - // To work around this issue, we destroy then recreate the menu every time one is added. + // To work around this issue, we destroy then recreate the menu every time something is changed. deleteMenu(); - menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, GtkTypeSystemTray.this); - - gobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu' + menuEntry = new GtkMenuEntry(menuText, imagePath, callback, GtkTypeSystemTray.this); menuEntries.add(menuEntry); createMenu(); diff --git a/src/dorkbox/systemTray/linux/jna/Gobject.java b/src/dorkbox/systemTray/linux/jna/Gobject.java index 052aeba..40cf0fe 100644 --- a/src/dorkbox/systemTray/linux/jna/Gobject.java +++ b/src/dorkbox/systemTray/linux/jna/Gobject.java @@ -161,14 +161,12 @@ interface Gobject extends Library { void g_free(Pointer object); - void g_object_ref(Pointer object); void g_object_unref(Pointer object); + + void g_object_force_floating(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, - int connect_flags); - - void g_signal_handler_disconnect(Pointer instance, NativeLong longAddress); + NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags); Pointer g_markup_printf_escaped(String pattern, String inputString); } diff --git a/src/dorkbox/systemTray/linux/jna/GtkSupport.java b/src/dorkbox/systemTray/linux/jna/GtkSupport.java index bc04146..abd8c5b 100644 --- a/src/dorkbox/systemTray/linux/jna/GtkSupport.java +++ b/src/dorkbox/systemTray/linux/jna/GtkSupport.java @@ -47,8 +47,6 @@ class GtkSupport { Gtk library; 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 // 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 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; } } catch (Throwable ignored) { @@ -79,7 +77,7 @@ class GtkSupport { if (library != null) { // 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. - alreadyRunningGTK |= library.gtk_main_level() != 0; + alreadyRunningGTK = library.gtk_main_level() != 0; return library; } } 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 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; } } catch (Throwable ignored) { @@ -111,7 +109,7 @@ class GtkSupport { if (library != null) { // 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. - alreadyRunningGTK |= library.gtk_main_level() != 0; + alreadyRunningGTK = library.gtk_main_level() != 0; return library; } } 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 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; } } catch (Throwable ignored) { @@ -153,7 +151,13 @@ class GtkSupport { final Runnable take = dispatchEvents.take(); gtk.gdk_threads_enter(); - take.run(); + + try { + take.run(); + } catch (Throwable e) { + e.printStackTrace(); + } + gtk.gdk_threads_leave(); } catch (InterruptedException e) { @@ -183,13 +187,17 @@ class GtkSupport { gtk.gdk_threads_enter(); 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 blockUntilStarted.countDown(); - // blocks unit quit - gtk.gtk_main(); + if (!SystemTray.COMPATIBILITY_MODE) { + // blocks unit quit + gtk.gtk_main(); + } gtk.gdk_threads_leave(); } @@ -222,7 +230,7 @@ class GtkSupport { public static void shutdownGui() { // 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(); } diff --git a/src/dorkbox/systemTray/swing/SwingSystemTray.java b/src/dorkbox/systemTray/swing/SwingSystemTray.java index 1274e4d..8e884ef 100644 --- a/src/dorkbox/systemTray/swing/SwingSystemTray.java +++ b/src/dorkbox/systemTray/swing/SwingSystemTray.java @@ -42,7 +42,9 @@ import java.net.URL; public class SwingSystemTray extends dorkbox.systemTray.SystemTray { volatile SwingSystemTrayMenuPopup menu; + volatile JMenuItem connectionStatusItem; + private volatile String statusText = null; volatile SystemTray tray; volatile TrayIcon trayIcon; @@ -92,7 +94,15 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray { @Override 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() { @Override public @@ -100,7 +110,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray { SwingSystemTray tray = SwingSystemTray.this; synchronized (tray) { if (tray.connectionStatusItem == null) { - final JMenuItem connectionStatusItem = new JMenuItem(infoString); + final JMenuItem connectionStatusItem = new JMenuItem(statusText); Font font = connectionStatusItem.getFont(); Font font1 = font.deriveFont(Font.BOLD); connectionStatusItem.setFont(font1); @@ -111,7 +121,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray { tray.connectionStatusItem = connectionStatusItem; } else { - tray.connectionStatusItem.setText(infoString); + tray.connectionStatusItem.setText(statusText); } } } diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index 184a1a5..c81933b 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -29,7 +29,6 @@ import java.net.URL; public class TestTray { - // horribly hacky. ONLY FOR TESTING! 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 LT_GRAY_MAIL = TestTray.class.getResource("mail.999999.24.png"); @@ -65,6 +64,7 @@ class TestTray { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.setStatus("Some Mail!"); systemTray.setIcon(GREEN_MAIL); + menuEntry.setCallback(callbackGray); menuEntry.setImage(BLACK_MAIL); menuEntry.setText("Delete Mail"); @@ -78,6 +78,7 @@ class TestTray { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.setStatus(null); systemTray.setIcon(BLACK_MAIL); + menuEntry.setCallback(null); // systemTray.setStatus("Mail Empty"); systemTray.removeMenuEntry(menuEntry); diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index 0e8b842..acdd860 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -39,7 +39,6 @@ import java.net.URL; public 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 GREEN_MAIL = TestTrayJavaFX.class.getResource("mail.39AC39.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 public void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { - systemTray.setStatus("Some Mail!"); systemTray.setIcon(GREEN_MAIL); + systemTray.setStatus("Some Mail!"); + menuEntry.setCallback(callbackGray); menuEntry.setImage(BLACK_MAIL); menuEntry.setText("Delete Mail"); @@ -112,6 +112,7 @@ class TestTrayJavaFX extends Application { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.setStatus(null); systemTray.setIcon(BLACK_MAIL); + menuEntry.setCallback(null); // systemTray.setStatus("Mail Empty"); systemTray.removeMenuEntry(menuEntry); diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index a34d120..1e3dfe1 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -35,7 +35,6 @@ import java.net.URL; public class TestTraySwt { - // horribly hacky. ONLY FOR TESTING! 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 LT_GRAY_MAIL = TestTraySwt.class.getResource("mail.999999.24.png"); @@ -55,7 +54,7 @@ class TestTraySwt { public TestTraySwt() { - Display display = new Display (); + final Display display = new Display (); final Shell shell = new Shell(display); Text helloWorldTest = new Text(shell, SWT.NONE); @@ -77,8 +76,8 @@ class TestTraySwt { public void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.setStatus("Some Mail!"); - systemTray.setIcon(GREEN_MAIL); + menuEntry.setCallback(callbackGray); menuEntry.setImage(BLACK_MAIL); menuEntry.setText("Delete Mail"); @@ -108,9 +107,9 @@ class TestTraySwt { void onClick(final SystemTray systemTray, final MenuEntry menuEntry) { systemTray.shutdown(); - Display.getDefault().asyncExec(new Runnable() { + display.asyncExec(new Runnable() { public void run() { - shell.close(); // close down SWT shell + shell.dispose(); } });