Code cleanup, GTK.dispatchAndWait refactor, fixed issue with hiding

the tray icon in MacOSx.
This commit is contained in:
nathan 2016-10-16 15:58:02 +02:00
parent e52591a388
commit a26522411e
9 changed files with 117 additions and 59 deletions

View File

@ -672,12 +672,13 @@ class SystemTray implements Menu {
} }
/** /**
* Shuts-down the SystemTray, by removing the menus + tray icon. * Shuts-down the SystemTray, by removing the menus + tray icon. After calling this method, you MUST call `get()` or `getNative()`
* again to obtain a new reference to the SystemTray.
*/ */
public public
void shutdown() { void shutdown() {
// this will call "dispatchAndWait()" behind the scenes, so it is thread-safe
final Menu menu = systemTrayMenu; final Menu menu = systemTrayMenu;
if (menu instanceof _AppIndicatorTray) { if (menu instanceof _AppIndicatorTray) {
((_AppIndicatorTray) menu).shutdown(); ((_AppIndicatorTray) menu).shutdown();
} }
@ -693,7 +694,7 @@ class SystemTray implements Menu {
else if (menu instanceof _AwtTray) { else if (menu instanceof _AwtTray) {
((_AwtTray) menu).shutdown(); ((_AwtTray) menu).shutdown();
} }
else { else if (menu instanceof _SwingTray) {
((_SwingTray) menu).shutdown(); ((_SwingTray) menu).shutdown();
} }
systemTrayMenu = null; systemTrayMenu = null;
@ -721,9 +722,12 @@ class SystemTray implements Menu {
else if (menu instanceof _AwtTray) { else if (menu instanceof _AwtTray) {
return ((_AwtTray) menu).getStatus(); return ((_AwtTray) menu).getStatus();
} }
else { else if (menu instanceof _SwingTray) {
return ((_SwingTray) menu).getStatus(); return ((_SwingTray) menu).getStatus();
} }
else {
return "";
}
} }
/** /**
@ -750,7 +754,7 @@ class SystemTray implements Menu {
else if (menu instanceof _AwtTray) { else if (menu instanceof _AwtTray) {
((_AwtTray) menu).setStatus(statusText); ((_AwtTray) menu).setStatus(statusText);
} }
else { else if (menu instanceof _SwingTray) {
((_SwingTray) menu).setStatus(statusText); ((_SwingTray) menu).setStatus(statusText);
} }
} }

View File

@ -57,6 +57,8 @@ class Gtk {
// there is ONLY a single thread EVER setting this value!! // there is ONLY a single thread EVER setting this value!!
private static volatile boolean isDispatch = false; private static volatile boolean isDispatch = false;
private static final int TIMEOUT = 2;
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
@ -363,9 +365,44 @@ class Gtk {
} }
} }
public static
void dispatchAndWait(final Runnable runnable) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
try {
runnable.run();
} finally {
countDownLatch.countDown();
}
}
});
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
// thread occur in REASONABLE time-frames, and alert the user if not.
try {
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
} else {
throw new RuntimeException("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
}
}
} catch (InterruptedException e) {
SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception());
}
}
public static public static
void shutdownGui() { void shutdownGui() {
dispatch(new Runnable() { dispatchAndWait(new Runnable() {
@Override @Override
public public
void run() { void run() {

View File

@ -19,8 +19,6 @@ package dorkbox.systemTray.nativeUI;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
@ -34,8 +32,6 @@ import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.util.MenuBase; import dorkbox.systemTray.util.MenuBase;
class GtkMenu extends MenuBase implements NativeUI { class GtkMenu extends MenuBase implements NativeUI {
static int TIMEOUT = 2;
// menu entry that this menu is attached to. Will be NULL when it's the system tray // menu entry that this menu is attached to. Will be NULL when it's the system tray
private final GtkEntryItem menuEntry; private final GtkEntryItem menuEntry;
@ -79,35 +75,7 @@ class GtkMenu extends MenuBase implements NativeUI {
*/ */
protected protected
void dispatchAndWait(final Runnable runnable) { void dispatchAndWait(final Runnable runnable) {
final CountDownLatch countDownLatch = new CountDownLatch(1); Gtk.dispatchAndWait(runnable);
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
try {
runnable.run();
} finally {
countDownLatch.countDown();
}
}
});
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
// thread occur in REASONABLE time-frames, and alert the user if not.
try {
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
} else {
throw new RuntimeException("Event dispatch queue took longer than " + TIMEOUT + " seconds to complete. Please adjust " +
"`SystemTray.TIMEOUT` to a value which better suites your environment.");
}
}
} catch (InterruptedException e) {
SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception());
}
} }
public public
@ -117,10 +85,11 @@ class GtkMenu extends MenuBase implements NativeUI {
public public
void run() { void run() {
obliterateMenu(); obliterateMenu();
Gtk.shutdownGui();
} }
}); });
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui();
} }
// public here so that Swing/Gtk/AppIndicator can access this // public here so that Swing/Gtk/AppIndicator can access this
@ -314,7 +283,7 @@ class GtkMenu extends MenuBase implements NativeUI {
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu' // will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
Gtk.gtk_menu_shell_append(this._native, entry._native); Gtk.gtk_menu_shell_append(this._native, entry._native);
Gobject.g_object_ref_sink(entry._native); // undoes "floating" Gobject.g_object_ref_sink(entry._native); // undoes "floating"
Gtk.gtk_widget_show_all(entry._native); Gtk.gtk_widget_show_all(entry._native); // necessary to guarantee widget is visible
} }
else if (menuEntry__ instanceof GtkMenu) { else if (menuEntry__ instanceof GtkMenu) {
GtkMenu subMenu = (GtkMenu) menuEntry__; GtkMenu subMenu = (GtkMenu) menuEntry__;
@ -322,7 +291,7 @@ class GtkMenu extends MenuBase implements NativeUI {
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu' // will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
Gtk.gtk_menu_shell_append(this._native, subMenu.menuEntry._native); Gtk.gtk_menu_shell_append(this._native, subMenu.menuEntry._native);
Gobject.g_object_ref_sink(subMenu.menuEntry._native); // undoes "floating" Gobject.g_object_ref_sink(subMenu.menuEntry._native); // undoes "floating"
Gtk.gtk_widget_show_all(subMenu.menuEntry._native); Gtk.gtk_widget_show_all(subMenu.menuEntry._native); // necessary to guarantee widget is visible
if (subMenu.getParent() != GtkMenu.this) { if (subMenu.getParent() != GtkMenu.this) {
// we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it // we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it
@ -332,7 +301,7 @@ class GtkMenu extends MenuBase implements NativeUI {
} }
onMenuAdded(_native); onMenuAdded(_native);
Gtk.gtk_widget_show_all(_native); Gtk.gtk_widget_show_all(_native); // necessary to guarantee widget is visible (doesn't always show_all for all children)
} }
} }

View File

@ -157,8 +157,6 @@ class _AppIndicatorNativeTray extends GtkMenu {
@Override @Override
public final public final
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
@ -166,9 +164,11 @@ class _AppIndicatorNativeTray extends GtkMenu {
if (visible && !setEnabled) { if (visible && !setEnabled) {
// STATUS_PASSIVE hides the indicator // STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
visible = false;
} }
else if (!visible && setEnabled) { else if (!visible && setEnabled) {
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
visible = true;
} }
} }
}); });

View File

@ -24,8 +24,11 @@ import java.io.File;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import dorkbox.util.OS;
/** /**
* Class for handling all system tray interaction, via AWT. * Class for handling all system tray interaction, via AWT. Pretty much EXCLUSIVELY for on MacOS, because that is the only time this
* looks good
* *
* It doesn't work well on linux. See bugs: * It doesn't work well on linux. See bugs:
* http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6267936 * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6267936
@ -41,6 +44,9 @@ class _AwtTray extends AwtMenu {
// is the system tray visible or not. // is the system tray visible or not.
private volatile boolean visible = true; private volatile boolean visible = true;
private final Object keepAliveLock = new Object[0];
private Thread keepAliveThread;
// Called in the EDT // Called in the EDT
public public
_AwtTray(final dorkbox.systemTray.SystemTray systemTray) { _AwtTray(final dorkbox.systemTray.SystemTray systemTray) {
@ -56,7 +62,7 @@ class _AwtTray extends AwtMenu {
public public
void shutdown() { void shutdown() {
dispatch(new Runnable() { dispatchAndWait(new Runnable() {
@Override @Override
public public
void run() { void run() {
@ -103,7 +109,42 @@ class _AwtTray extends AwtMenu {
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public public
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled; if (OS.isMacOsX()) {
if (keepAliveThread != null) {
synchronized (keepAliveLock) {
keepAliveLock.notifyAll();
}
}
keepAliveThread = null;
if (visible && !setEnabled) {
// THIS WILL NOT keep the app running, so we use a "keep-alive" thread so this behavior is THE SAME across
// all platforms. This was only noticed on MacOS (where the app would quit after calling setEnabled(false);
keepAliveThread = new Thread(new Runnable() {
@Override
public
void run() {
synchronized (keepAliveLock) {
keepAliveLock.notifyAll();
try {
keepAliveLock.wait();
} catch (InterruptedException ignored) {
}
}
}
}, "KeepAliveThread");
keepAliveThread.start();
}
synchronized (keepAliveLock) {
try {
keepAliveLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
@ -111,10 +152,12 @@ class _AwtTray extends AwtMenu {
void run() { void run() {
if (visible && !setEnabled) { if (visible && !setEnabled) {
tray.remove(trayIcon); tray.remove(trayIcon);
visible = false;
} }
else if (!visible && setEnabled) { else if (!visible && setEnabled) {
try { try {
tray.add(trayIcon); tray.add(trayIcon);
visible = true;
} catch (AWTException e) { } catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray"); dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray");
} }

View File

@ -169,16 +169,16 @@ class _GtkStatusIconNativeTray extends GtkMenu {
@Override @Override
public final public final
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
if (visible && !setEnabled) { if (visible && !setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled); Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = false;
} else if (!visible && setEnabled) { } else if (!visible && setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled); Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = true;
} }
} }
}); });

View File

@ -213,6 +213,7 @@ class _AppIndicatorTray extends SwingMenu {
} }
}); });
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui(); Gtk.shutdownGui();
// uses EDT // uses EDT
@ -230,7 +231,7 @@ class _AppIndicatorTray extends SwingMenu {
@Override @Override
public final public final
void setImage_(final File imageFile) { void setImage_(final File imageFile) {
dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
@ -247,6 +248,8 @@ class _AppIndicatorTray extends SwingMenu {
} }
}); });
// needs to be on EDT
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
@ -259,8 +262,6 @@ class _AppIndicatorTray extends SwingMenu {
@Override @Override
public final public final
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
@ -268,9 +269,11 @@ class _AppIndicatorTray extends SwingMenu {
if (visible && !setEnabled) { if (visible && !setEnabled) {
// STATUS_PASSIVE hides the indicator // STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
visible = false;
} }
else if (!visible && setEnabled) { else if (!visible && setEnabled) {
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
visible = true;
} }
} }
}); });

View File

@ -162,6 +162,7 @@ class _GtkStatusIconTray extends SwingMenu {
} }
}); });
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui(); Gtk.shutdownGui();
// uses EDT // uses EDT
@ -185,6 +186,7 @@ class _GtkStatusIconTray extends SwingMenu {
} }
}); });
// needs to be on EDT
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
@ -196,16 +198,16 @@ class _GtkStatusIconTray extends SwingMenu {
public public
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled;
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
if (visible && !setEnabled) { if (visible && !setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled); Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = false;
} else if (!visible && setEnabled) { } else if (!visible && setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled); Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = true;
} }
} }
}); });

View File

@ -118,20 +118,20 @@ class _SwingTray extends SwingMenu {
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public public
void setEnabled(final boolean setEnabled) { void setEnabled(final boolean setEnabled) {
visible = !setEnabled;
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
if (visible && !setEnabled) { if (visible && !setEnabled) {
tray.remove(trayIcon); tray.remove(trayIcon);
visible = false;
} }
else if (!visible && setEnabled) { else if (!visible && setEnabled) {
try { try {
tray.add(trayIcon); tray.add(trayIcon);
visible = true;
} catch (AWTException e) { } catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray"); dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
} }
} }
} }