Added GTK3 support (now libappindicator3 works correctly with regards to showing menu icons). Added support for setting icons via URL and InputStream. Moved all GTK operations to a single thread. Moved error logs from System.err -> logger. Made SystemTray a singleton (it will no longer have the java-side get accidentally garbage collected). Update documentation

This commit is contained in:
nathan 2016-02-13 15:06:19 +01:00
parent 8b109f6d1c
commit c1b6c1a723
11 changed files with 465 additions and 394 deletions

View File

@ -19,7 +19,7 @@ This is for cross-platform use, specifically - linux 32/64, mac 32/64, and windo
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. 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. 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 See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
``` ```
@ -44,6 +44,11 @@ GnomeShellExtension.SHELL_RESTART_COMMAND (type String, default value 'gnome-s
SystemTray.TRAY_SIZE (type int, default value '24') SystemTray.TRAY_SIZE (type int, default value '24')
- Size of the tray, so that the icon can properly scale based on OS. (if it's not exact). This only applies for Swing tray icons. - Size of the tray, so that the icon can properly scale based on OS. (if it's not exact). This only applies for Swing tray icons.
- NOTE: Must be set after any other customization options, as a static call to SystemTray will cause initialization of the library. - NOTE: Must be set after any other customization options, as a static call to SystemTray will cause initialization of the library.
GtkSupport.CREATE_EVENT_LOOP (type boolean, default value 'true')
- Enables/Disables the creation of a native GTK event loop. Useful if you are already creating one via SWT/etc.
``` ```
@ -73,17 +78,6 @@ The test application is [on GitHub](https://github.com/dorkbox/SystemTray/blob/m
``` ```
```
Note: This library does NOT use SWT for system-tray support, only for the purpose
of lessening the jar dependencies. Changing it to be SWT-based is not
difficult, just remember that SWT on linux *already* starts up the GTK main
event loop.
```
```
Note: If you use the attached JNA libraries, you **MUST** load the respective
native libraries yourself, especially with JNA (as the loading logic has
been removed from the jar)
```
``` ```
Note: This project was heavily influenced by the excellent Lantern project, Note: This project was heavily influenced by the excellent Lantern project,
*Many* thanks to them for figuring out AppIndicators via JNA. *Many* thanks to them for figuring out AppIndicators via JNA.
@ -99,8 +93,8 @@ Note: Gnome-shell users will experience an extension install to support this
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 libappindicator3, 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 library will try to load a GTK indicator instead when it can, or will This library will try to load a GTK indicator instead when it can, or will
try to load libappindicator1 first. Thank you RedHat for putting it back. try to load libappindicator1 first. Thank you RedHat for putting it back.
@ -133,7 +127,7 @@ This project is **kept in sync** with the utilities library, so "jar hell" is no
<dependency> <dependency>
<groupId>com.dorkbox</groupId> <groupId>com.dorkbox</groupId>
<artifactId>SystemTray</artifactId> <artifactId>SystemTray</artifactId>
<version>2.0</version> <version>2.1</version>
</dependency> </dependency>
``` ```

View File

@ -22,6 +22,7 @@
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
<orderEntry type="library" name="logging slf4j-api (1.7.5)" level="application" /> <orderEntry type="library" name="logging slf4j-api (1.7.5)" level="application" />
<orderEntry type="library" name="logging logback (1.0.13)" level="application" />
<orderEntry type="module" module-name="Dorkbox-Util" /> <orderEntry type="module" module-name="Dorkbox-Util" />
<orderEntry type="module" module-name="JavaLauncher-Util" /> <orderEntry type="module" module-name="JavaLauncher-Util" />
<orderEntry type="library" name="jna (4.2.1)" level="application" /> <orderEntry type="library" name="jna (4.2.1)" level="application" />

View File

@ -209,7 +209,7 @@ class ImageUtil {
return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US); return new BigInteger(1, digest.digest()).toString(32).toUpperCase(Locale.US);
} }
public static public static synchronized
void init() throws NoSuchAlgorithmException { void init() throws NoSuchAlgorithmException {
ImageUtil.digest = MessageDigest.getInstance("MD5"); ImageUtil.digest = MessageDigest.getInstance("MD5");
} }

View File

@ -22,7 +22,6 @@ import dorkbox.systemTray.swing.SwingSystemTray;
import dorkbox.util.OS; import dorkbox.util.OS;
import dorkbox.util.Property; import dorkbox.util.Property;
import dorkbox.util.jna.linux.AppIndicator; import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.AppIndicatorQuery;
import dorkbox.util.jna.linux.GtkSupport; import dorkbox.util.jna.linux.GtkSupport;
import dorkbox.util.process.ShellProcessBuilder; import dorkbox.util.process.ShellProcessBuilder;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -44,7 +43,7 @@ import java.util.Iterator;
/** /**
* Factory and base-class for system tray implementations. * Factory and base-class for system tray implementations.
*/ */
@SuppressWarnings("unused") @SuppressWarnings({"unused", "Duplicates"})
public abstract public abstract
class SystemTray { class SystemTray {
protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class); protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
@ -70,156 +69,158 @@ class SystemTray {
} }
if (OS.isLinux()) { if (OS.isLinux()) {
if (GtkSupport.isSupported) { // see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
String XDG = System.getenv("XDG_CURRENT_DESKTOP"); String XDG = System.getenv("XDG_CURRENT_DESKTOP");
if ("Unity".equalsIgnoreCase(XDG)) { if ("Unity".equalsIgnoreCase(XDG)) {
try {
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
}
}
else if ("XFCE".equalsIgnoreCase(XDG)) {
try {
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
// we can fail on AppIndicator, so this is the fallback
//noinspection EmptyCatchBlock
try { try {
trayType = AppIndicatorTray.class; trayType = GtkSystemTray.class;
} catch (Throwable ignored) { } catch (Throwable i) {
} }
} }
else if ("XFCE".equalsIgnoreCase(XDG)) { }
// XFCE uses a BAD version of libappindicator by default, which DOES NOT support images in the menu. else if ("LXDE".equalsIgnoreCase(XDG)) {
// if we have libappindicator1, we are OK. if we don't, fallback to GTKSystemTray try {
try { trayType = GtkSystemTray.class;
if (AppIndicatorQuery.get_v1() != null) { } catch (Throwable ignored) {
trayType = AppIndicatorTray.class;
} else {
trayType = GtkSystemTray.class;
}
} catch (Throwable ignored) {
}
} }
else if ("LXDE".equalsIgnoreCase(XDG)) { }
else if ("KDE".equalsIgnoreCase(XDG)) {
isKDE = true;
try {
trayType = AppIndicatorTray.class;
} catch (Throwable ignored) {
}
}
else if ("GNOME".equalsIgnoreCase(XDG)) {
// check other DE
String GDM = System.getenv("GDMSESSION");
if ("cinnamon".equalsIgnoreCase(GDM)) {
try { try {
trayType = GtkSystemTray.class; trayType = GtkSystemTray.class;
} catch (Throwable ignored) { } catch (Throwable ignored) {
} }
} }
else if ("KDE".equalsIgnoreCase(XDG)) { else if ("gnome-classic".equalsIgnoreCase(GDM)) {
isKDE = true;
try { try {
trayType = AppIndicatorTray.class; trayType = GtkSystemTray.class;
} catch (Throwable ignored) { } catch (Throwable ignored) {
} }
} }
else if ("GNOME".equalsIgnoreCase(XDG)) { else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
// check other DE try {
String GDM = System.getenv("GDMSESSION"); trayType = GtkSystemTray.class;
} catch (Throwable ignored) {
if ("cinnamon".equalsIgnoreCase(GDM)) {
try {
trayType = GtkSystemTray.class;
} catch (Throwable ignored) {
}
}
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
try {
trayType = GtkSystemTray.class;
} catch (Throwable ignored) {
}
}
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
try {
trayType = GtkSystemTray.class;
} catch (Throwable ignored) {
}
}
// unknown exactly, install extension and go from there
if (trayType == null) {
// if the "topicons" extension is installed, don't install us (because it will override what we do, where ours
// is more specialized - so it only modified our tray icon (instead of ALL tray icons)
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
// gnome-shell --version
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
shellVersion.setExecutable("gnome-shell");
shellVersion.addArgument("--version");
shellVersion.start();
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
if (!output.isEmpty()) {
GnomeShellExtension.install(logger, output);
trayType = GtkSystemTray.class;
}
} catch (Throwable ignored) {
trayType = null;
}
} }
} }
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
// unknown exactly, install extension and go from there
if (trayType == null) { if (trayType == null) {
BufferedReader bin = null; // if the "topicons" extension is installed, don't install us (because it will override what we do, where ours
// is more specialized - so it only modified our tray icon (instead of ALL tray icons)
try { try {
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator), ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
// is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli PrintStream outputStream = new PrintStream(byteArrayOutputStream);
File proc = new File("/proc");
File[] listFiles = proc.listFiles();
if (listFiles != null) {
for (File procs : listFiles) {
String name = procs.getName();
if (!Character.isDigit(name.charAt(0))) { // gnome-shell --version
continue; final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
} shellVersion.setExecutable("gnome-shell");
shellVersion.addArgument("--version");
shellVersion.start();
File status = new File(procs, "status"); String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
if (!status.canRead()) {
continue;
}
try { if (!output.isEmpty()) {
bin = new BufferedReader(new FileReader(status)); GnomeShellExtension.install(logger, output);
String readLine = bin.readLine(); trayType = GtkSystemTray.class;
if (readLine != null && readLine.contains("indicator-app")) {
// make sure we can also load the library (it might be the wrong version)
try {
//noinspection unused
final AppIndicator instance = AppIndicator.INSTANCE;
trayType = AppIndicatorTray.class;
} catch (Throwable e) {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
e.printStackTrace();
}
break;
}
} finally {
if (bin != null) {
bin.close();
bin = null;
}
}
}
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
} finally { trayType = null;
if (bin != null) { }
}
}
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
if (trayType == null) {
BufferedReader bin = null;
try {
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
// is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli
File proc = new File("/proc");
File[] listFiles = proc.listFiles();
if (listFiles != null) {
for (File procs : listFiles) {
String name = procs.getName();
if (!Character.isDigit(name.charAt(0))) {
continue;
}
File status = new File(procs, "status");
if (!status.canRead()) {
continue;
}
try { try {
bin.close(); bin = new BufferedReader(new FileReader(status));
} catch (IOException ignored) { String readLine = bin.readLine();
if (readLine != null && readLine.contains("indicator-app")) {
// make sure we can also load the library (it might be the wrong version)
try {
//noinspection unused
final AppIndicator instance = AppIndicator.INSTANCE;
trayType = AppIndicatorTray.class;
if (AppIndicator.IS_VERSION_3) {
}
} catch (Throwable e) {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
e.printStackTrace();
}
break;
}
} finally {
if (bin != null) {
bin.close();
bin = null;
}
} }
} }
} }
} catch (Throwable ignored) {
} finally {
if (bin != null) {
try {
bin.close();
} catch (IOException ignored) {
}
}
} }
}
// fallback... // fallback...
if (trayType == null) { if (trayType == null) {
trayType = GtkSystemTray.class; trayType = GtkSystemTray.class;
logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " + logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " +
"configuration"); "configuration");
}
} }
} }
@ -242,11 +243,30 @@ class SystemTray {
SystemTray systemTray_ = null; SystemTray systemTray_ = null;
try { try {
ImageUtil.init(); ImageUtil.init();
if (AppIndicator.IS_VERSION_3 && GtkSupport.isGtk2) {
// NOTE:
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
// appindicator3 doesn't support menu icons via GTK2. AT THIS POINT, we DO NOT have GTK3
try {
trayType = GtkSystemTray.class;
logger.warn("AppIndicator3 detected with GTK2 only, falling back to GTK2 system tray type. " +
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
} catch (Throwable ignored) {
logger.error("AppIndicator3 detected with GTK2 only and unable to fallback to using GTK2 system tray type." +
"AppIndicator3 requires GTK3 to be fully functional, and while this will work -- the menu icons WILL " +
"NOT be visible." +
" Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
}
}
systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance(); systemTray_ = (SystemTray) trayType.getConstructors()[0].newInstance();
logger.info("Successfully Loaded: {}", trayType.getSimpleName());
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
logger.error("Unsupported hashing algorithm!"); logger.error("Unsupported hashing algorithm!");
} catch (Exception e) { } catch (Exception e) {
logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'"); logger.error("Unable to create tray type: '" + trayType.getSimpleName() + "'", e);
} }
systemTray = systemTray_; systemTray = systemTray_;
} }
@ -278,6 +298,10 @@ class SystemTray {
SystemTray() { SystemTray() {
} }
/**
* Must be wrapped in a synchronized block for object visibility
*/
protected protected
MenuEntry getMenuEntry(String menuText) { MenuEntry getMenuEntry(String menuText) {
for (MenuEntry entry : menuEntries) { for (MenuEntry entry : menuEntries) {
@ -436,15 +460,17 @@ 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 synchronized public final
void updateMenuEntry_Text(String origMenuText, String newMenuText) { void updateMenuEntry_Text(String origMenuText, String newMenuText) {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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);
}
} }
} }
@ -454,15 +480,17 @@ class SystemTray {
* @param origMenuText the original menu text * @param origMenuText the original menu text
* @param imagePath the new path for the image to use or null to delete the image * @param imagePath the new path for the image to use or null to delete the image
*/ */
public final synchronized public final
void updateMenuEntry_Image(String origMenuText, String imagePath) throws IOException { void updateMenuEntry_Image(String origMenuText, String imagePath) throws IOException {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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);
}
} }
} }
@ -472,15 +500,17 @@ class SystemTray {
* @param origMenuText the original menu text * @param origMenuText the original menu text
* @param imageUrl the new URL for the image to use or null to delete the image * @param imageUrl the new URL for the image to use or null to delete the image
*/ */
public final synchronized public final
void updateMenuEntry_Image(String origMenuText, URL imageUrl) throws IOException { void updateMenuEntry_Image(String origMenuText, URL imageUrl) throws IOException {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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(imageUrl); menuEntry.setImage(imageUrl);
}
} }
} }
@ -490,15 +520,17 @@ class SystemTray {
* @param cacheName the name to use for lookup in the cache for the imageStream * @param cacheName the name to use for lookup in the cache for the imageStream
* @param imageStream the InputStream of the image to use or null to delete the image * @param imageStream the InputStream of the image to use or null to delete the image
*/ */
public final synchronized public final
void updateMenuEntry_Image(String origMenuText, String cacheName, InputStream imageStream) throws IOException { void updateMenuEntry_Image(String origMenuText, String cacheName, InputStream imageStream) throws IOException {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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(cacheName, imageStream); menuEntry.setImage(cacheName, imageStream);
}
} }
} }
@ -512,15 +544,17 @@ class SystemTray {
* @param imageStream the new path for the image to use or null to delete the image * @param imageStream the new path for the image to use or null to delete the image
*/ */
@Deprecated @Deprecated
public final synchronized public final
void updateMenuEntry_Image(String origMenuText, InputStream imageStream) throws IOException { void updateMenuEntry_Image(String origMenuText, InputStream imageStream) throws IOException {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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(imageStream); menuEntry.setImage(imageStream);
}
} }
} }
@ -530,15 +564,17 @@ 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 synchronized public final
void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) { void updateMenuEntry_Callback(String origMenuText, SystemTrayMenuAction newCallback) {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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 + "'");
}
} }
} }
@ -550,16 +586,18 @@ 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 synchronized public final
void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) { void updateMenuEntry(String origMenuText, String newMenuText, SystemTrayMenuAction newCallback) {
MenuEntry menuEntry = getMenuEntry(origMenuText); synchronized (menuEntries) {
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);
}
} }
} }
@ -569,7 +607,7 @@ class SystemTray {
* *
* @param menuEntry This is the menu entry to remove * @param menuEntry This is the menu entry to remove
*/ */
public final synchronized public final
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");
@ -577,15 +615,17 @@ class SystemTray {
final String label = menuEntry.getText(); final String label = menuEntry.getText();
for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) { synchronized (menuEntries) {
final MenuEntry entry = iterator.next(); for (Iterator<MenuEntry> iterator = menuEntries.iterator(); iterator.hasNext(); ) {
if (entry.getText() final MenuEntry entry = iterator.next();
.equals(label)) { if (entry.getText()
iterator.remove(); .equals(label)) {
iterator.remove();
// this will also reset the menu // this will also reset the menu
menuEntry.remove(); menuEntry.remove();
return; 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.");
@ -597,15 +637,17 @@ 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 synchronized public final
void removeMenuEntry(final String menuText) { void removeMenuEntry(final String menuText) {
MenuEntry menuEntry = getMenuEntry(menuText); synchronized (menuEntries) {
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);
}
} }
} }
} }

View File

@ -24,8 +24,7 @@ import dorkbox.util.jna.linux.GtkSupport;
* <p/> * <p/>
* specialization for using app indicators in ubuntu unity * specialization for using app indicators in ubuntu unity
* <p/> * <p/>
* Heavily modified from * Derived from
* <p/>
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*/ */
public public
@ -33,57 +32,71 @@ class AppIndicatorTray extends GtkTypeSystemTray {
private static final AppIndicator appindicator = AppIndicator.INSTANCE; private static final AppIndicator appindicator = AppIndicator.INSTANCE;
private AppIndicator.AppIndicatorInstanceStruct appIndicator; private AppIndicator.AppIndicatorInstanceStruct appIndicator;
private volatile boolean isActive = false; private boolean isActive = false;
public public
AppIndicatorTray() { AppIndicatorTray() {
gtk.gdk_threads_enter();
this.appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "",
AppIndicator.CATEGORY_APPLICATION_STATUS);
gtk.gdk_threads_leave();
GtkSupport.startGui(); GtkSupport.startGui();
GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "",
AppIndicator.CATEGORY_APPLICATION_STATUS);
}
});
} }
@Override @Override
public synchronized public
void shutdown() { void shutdown() {
gtk.gdk_threads_enter(); 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);
// this hides the indicator // GC it
appindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_PASSIVE); appIndicator = null;
Pointer p = this.appIndicator.getPointer(); }
gobject.g_object_unref(p); });
// GC it
this.appIndicator = null;
// libgtk.gdk_threads_leave(); called by super class
super.shutdown(); super.shutdown();
} }
@Override @Override
protected synchronized protected
void setIcon_(final String iconPath) { void setIcon_(final String iconPath) {
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
appindicator.app_indicator_set_icon(this.appIndicator, iconPath); @Override
public
void run() {
appindicator.app_indicator_set_icon(appIndicator, iconPath);
if (!isActive) { if (!isActive) {
isActive = true; isActive = true;
appindicator.app_indicator_set_status(this.appIndicator, AppIndicator.STATUS_ACTIVE); appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
} }
}
gtk.gdk_threads_leave(); });
} }
/** /**
* Called inside the gdk_threads block. MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu * MUST BE AFTER THE ITEM IS ADDED/CHANGED from the menu
*/ */
protected protected
void onMenuAdded(final Pointer menu) { void onMenuAdded(final Pointer menu) {
appindicator.app_indicator_set_menu(this.appIndicator, menu); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
appindicator.app_indicator_set_menu(appIndicator, menu);
}
});
} }
} }

View File

@ -23,6 +23,7 @@ import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.util.jna.linux.Gobject; 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.jna.linux.GtkSupport;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -43,6 +44,7 @@ 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, GtkMenuEntry(final Pointer parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final GtkTypeSystemTray systemTray) { final GtkTypeSystemTray systemTray) {
this.parentMenu = parentMenu; this.parentMenu = parentMenu;
@ -99,36 +101,41 @@ class GtkMenuEntry implements MenuEntry {
@Override @Override
public public
void setText(final String newText) { void setText(final String newText) {
this.text = newText; GtkSupport.dispatch(new Runnable() {
gtk.gdk_threads_enter(); @Override
public
void run() {
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(parentMenu); });
gtk.gdk_threads_leave();
} }
private private
void setImage_(final String imagePath) { void setImage_(final String imagePath) {
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
if (imagePath != null && !imagePath.isEmpty()) {
if (image != null) {
gtk.gtk_widget_destroy(image);
}
gtk.gtk_widget_show_all(parentMenu);
if (imagePath != null && !imagePath.isEmpty()) { image = gtk.gtk_image_new_from_file(imagePath);
if (image != null) {
gtk.gtk_widget_destroy(image); gtk.gtk_image_menu_item_set_image(menuItem, 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(parentMenu); });
image = gtk.gtk_image_new_from_file(imagePath);
gtk.gtk_image_menu_item_set_image(menuItem, image);
// must always re-set always-show after setting the image
gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE);
}
gtk.gdk_threads_leave();
gtk.gtk_widget_show_all(parentMenu);
} }
@Override @Override
@ -187,15 +194,17 @@ class GtkMenuEntry implements MenuEntry {
*/ */
public public
void remove() { void remove() {
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
removePrivate();
removePrivate(); // have to rebuild the menu now...
systemTray.deleteMenu();
// have to rebuild the menu now... systemTray.createMenu();
systemTray.deleteMenu(); }
systemTray.createMenu(); });
gtk.gdk_threads_leave();
} }
void removePrivate() { void removePrivate() {

View File

@ -32,7 +32,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
// 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 final Gobject.GEventCallback gtkCallback; private Gobject.GEventCallback gtkCallback;
@SuppressWarnings({"FieldCanBeLocal", "unused"}) @SuppressWarnings({"FieldCanBeLocal", "unused"})
private NativeLong button_press_event; private NativeLong button_press_event;
@ -42,30 +42,31 @@ class GtkSystemTray extends GtkTypeSystemTray {
public public
GtkSystemTray() { GtkSystemTray() {
super(); super();
GtkSupport.startGui();
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
final Pointer trayIcon = gtk.gtk_status_icon_new();
gtk.gtk_status_icon_set_title(trayIcon, "SystemTray@Dorkbox");
gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
this.trayIcon = trayIcon;
this.gtkCallback = new Gobject.GEventCallback() {
@Override @Override
public public
void callback(Pointer notUsed, final Gtk.GdkEventButton event) { void run() {
// BUTTON_PRESS only (any mouse click) final Pointer trayIcon_ = gtk.gtk_status_icon_new();
if (event.type == 4) { gtk.gtk_status_icon_set_title(trayIcon_, "SystemTray@Dorkbox");
gtk.gtk_menu_popup(menu, null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray");
}
trayIcon = trayIcon_;
gtkCallback = new Gobject.GEventCallback() {
@Override
public
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);
}
}
};
button_press_event = gobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, null, null, 0);
} }
}; });
button_press_event = gobject.g_signal_connect_data(trayIcon, "button_press_event", gtkCallback, null, null, 0);
gtk.gdk_threads_leave();
GtkSupport.startGui();
} }
/** /**
@ -78,32 +79,38 @@ class GtkSystemTray extends GtkTypeSystemTray {
@SuppressWarnings("FieldRepeatedlyAccessedInMethod") @SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@Override @Override
public synchronized public
void shutdown() { void shutdown() {
gtk.gdk_threads_enter(); 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);
// this hides the indicator // GC it
gtk.gtk_status_icon_set_visible(this.trayIcon, false); trayIcon = null;
gobject.g_object_unref(this.trayIcon); }
});
// GC it
this.trayIcon = null;
// libgtk.gdk_threads_leave(); called by parent class
super.shutdown(); super.shutdown();
} }
@Override @Override
protected synchronized protected
void setIcon_(final String iconPath) { void setIcon_(final String iconPath) {
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
gtk.gtk_status_icon_set_from_file(trayIcon, iconPath); if (!isActive) {
isActive = true;
if (!isActive) { gtk.gtk_status_icon_set_visible(trayIcon, true);
isActive = true; }
gtk.gtk_status_icon_set_visible(trayIcon, true); }
} });
gtk.gdk_threads_leave();
} }
} }

View File

@ -31,76 +31,84 @@ import java.net.URL;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
/**
* Derived from
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.
*/
public abstract public abstract
class GtkTypeSystemTray extends SystemTray { class GtkTypeSystemTray extends SystemTray {
protected static final Gobject gobject = Gobject.INSTANCE; protected static final Gobject gobject = Gobject.INSTANCE;
protected static final Gtk gtk = Gtk.INSTANCE; protected static final Gtk gtk = Gtk.INSTANCE;
// protected static final GLib glib = GLib.INSTANCE;
final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false)); final static ExecutorService callbackExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SysTrayExecutor", false));
private Pointer menu; private Pointer menu;
private Pointer connectionStatusItem; private Pointer connectionStatusItem;
@Override synchronized @Override
public public
void shutdown() { void shutdown() {
// libgtk.gdk_threads_enter(); called by implementation GtkSupport.dispatch(new Runnable() {
obliterateMenu(); @Override
GtkSupport.shutdownGui(); public
void run() {
obliterateMenu();
GtkSupport.shutdownGui();
gtk.gdk_threads_leave(); callbackExecutor.shutdown();
}
callbackExecutor.shutdown(); });
} }
@Override @Override
public synchronized public
void setStatus(final String infoString) { void setStatus(final String infoString) {
gtk.gdk_threads_enter(); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
if (connectionStatusItem == null && infoString != null && !infoString.isEmpty()) {
deleteMenu();
if (this.connectionStatusItem == null && infoString != null && !infoString.isEmpty()) { connectionStatusItem = gtk.gtk_menu_item_new_with_label("");
deleteMenu(); gobject.g_object_ref(connectionStatusItem); // so it matches with 'createMenu'
this.connectionStatusItem = gtk.gtk_menu_item_new_with_label(""); // evil hacks abound...
gobject.g_object_ref(connectionStatusItem); // so it matches with 'createMenu' Pointer label = gtk.gtk_bin_get_child(connectionStatusItem);
gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
// evil hacks abound... Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", infoString);
Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); gtk.gtk_label_set_markup(label, markup);
gtk.gtk_label_set_use_markup(label, Gtk.TRUE); gobject.g_free(markup);
Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", infoString);
gtk.gtk_label_set_markup(label, markup);
gobject.g_free(markup);
gtk.gtk_widget_set_sensitive(this.connectionStatusItem, Gtk.FALSE); gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE);
createMenu(); createMenu();
} }
else { else {
if (infoString == null || infoString.isEmpty()) { if (infoString == null || infoString.isEmpty()) {
deleteMenu(); deleteMenu();
gtk.gtk_widget_destroy(connectionStatusItem); gtk.gtk_widget_destroy(connectionStatusItem);
connectionStatusItem = null; connectionStatusItem = null;
createMenu(); createMenu();
}
else {
// set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString);
// 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("<b>%s</b>", infoString);
gtk.gtk_label_set_markup(label, markup);
gobject.g_free(markup);
gtk.gtk_widget_show_all(menu);
}
}
} }
else { });
// set bold instead
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, infoString);
// 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("<b>%s</b>", infoString);
gtk.gtk_label_set_markup(label, markup);
gobject.g_free(markup);
gtk.gtk_widget_show_all(menu);
}
}
gtk.gdk_threads_leave();
} }
/** /**
@ -110,8 +118,6 @@ class GtkTypeSystemTray extends SystemTray {
void onMenuAdded(final Pointer menu); 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. * Completely obliterates the menu, no possible way to reconstruct it.
*/ */
@ -164,7 +170,7 @@ class GtkTypeSystemTray extends SystemTray {
menu = gtk.gtk_menu_new(); menu = gtk.gtk_menu_new();
} }
// UNSAFE. must be protected inside synchronized, and inside threads_enter/exit // UNSAFE. must be protected inside dispatch
void createMenu() { void createMenu() {
// now add status // now add status
if (connectionStatusItem != null) { if (connectionStatusItem != null) {
@ -185,8 +191,8 @@ class GtkTypeSystemTray extends SystemTray {
gtk.gtk_widget_show_all(menu); gtk.gtk_widget_show_all(menu);
} }
private synchronized private
void addMenuEntry_(String menuText, final String imagePath, final SystemTrayMenuAction callback) { void addMenuEntry_(final 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. // 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 // see: https://bugs.launchpad.net/glipper/+bug/1203888
@ -194,27 +200,28 @@ class GtkTypeSystemTray extends SystemTray {
throw new NullPointerException("Menu text cannot be null"); throw new NullPointerException("Menu text cannot be null");
} }
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText); GtkSupport.dispatch(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
GtkMenuEntry menuEntry = (GtkMenuEntry) getMenuEntry(menuText);
if (menuEntry != null) { if (menuEntry == null) {
throw new IllegalArgumentException("Menu entry already exists for given label '" + menuText + "'"); // 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.
else { deleteMenu();
gtk.gdk_threads_enter();
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. menuEntry = new GtkMenuEntry(menu, menuText, imagePath, callback, GtkTypeSystemTray.this);
// 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); gobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu'
menuEntries.add(menuEntry);
gobject.g_object_ref(menuEntry.menuItem); // so it matches with 'createMenu' createMenu();
this.menuEntries.add(menuEntry); }
}
createMenu(); }
});
gtk.gdk_threads_leave();
}
} }
@Override @Override

View File

@ -20,7 +20,6 @@ import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.SystemTrayMenuPopup;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
@ -32,7 +31,7 @@ import java.io.InputStream;
import java.net.URL; import java.net.URL;
class SwingMenuEntry implements MenuEntry { class SwingMenuEntry implements MenuEntry {
private final SystemTrayMenuPopup parent; private final SwingSystemTrayMenuPopup parent;
private final SystemTray systemTray; private final SystemTray systemTray;
private final JMenuItem menuItem; private final JMenuItem menuItem;
private final ActionListener swingCallback; private final ActionListener swingCallback;
@ -40,7 +39,7 @@ class SwingMenuEntry implements MenuEntry {
private volatile String text; private volatile String text;
private volatile SystemTrayMenuAction callback; private volatile SystemTrayMenuAction callback;
SwingMenuEntry(final SystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback, SwingMenuEntry(final SwingSystemTrayMenuPopup parentMenu, final String label, final String imagePath, final SystemTrayMenuAction callback,
final SystemTray systemTray) { final SystemTray systemTray) {
this.parent = parentMenu; this.parent = parentMenu;
this.text = label; this.text = label;

View File

@ -18,7 +18,6 @@ package dorkbox.systemTray.swing;
import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.SystemTrayMenuPopup;
import dorkbox.util.ScreenUtil; import dorkbox.util.ScreenUtil;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
@ -43,7 +42,7 @@ import java.net.URL;
*/ */
public public
class SwingSystemTray extends dorkbox.systemTray.SystemTray { class SwingSystemTray extends dorkbox.systemTray.SystemTray {
volatile SystemTrayMenuPopup menu; volatile SwingSystemTrayMenuPopup menu;
volatile JMenuItem connectionStatusItem; volatile JMenuItem connectionStatusItem;
volatile SystemTray tray; volatile SystemTray tray;
@ -132,7 +131,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
if (!isActive) { if (!isActive) {
isActive = true; isActive = true;
SwingSystemTray.this.menu = new SystemTrayMenuPopup(); SwingSystemTray.this.menu = new SwingSystemTrayMenuPopup();
Image trayImage = new ImageIcon(iconPath).getImage() Image trayImage = new ImageIcon(iconPath).getImage()
.getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH); .getScaledInstance(TRAY_SIZE, TRAY_SIZE, Image.SCALE_SMOOTH);
@ -147,7 +146,7 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
@Override @Override
public public
void mousePressed(MouseEvent e) { void mousePressed(MouseEvent e) {
final SystemTrayMenuPopup menu = SwingSystemTray.this.menu; final SwingSystemTrayMenuPopup menu = SwingSystemTray.this.menu;
Dimension size = menu.getPreferredSize(); Dimension size = menu.getPreferredSize();
Point point = e.getPoint(); Point point = e.getPoint();
@ -213,14 +212,16 @@ class SwingSystemTray extends dorkbox.systemTray.SystemTray {
void run() { void run() {
SwingSystemTray tray = SwingSystemTray.this; SwingSystemTray tray = SwingSystemTray.this;
synchronized (tray) { synchronized (tray) {
MenuEntry menuEntry = getMenuEntry(menuText); synchronized (menuEntries) {
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, tray); menuEntry = new SwingMenuEntry(menu, menuText, imagePath, callback, tray);
menuEntries.add(menuEntry); menuEntries.add(menuEntry);
}
} }
} }
} }

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package dorkbox.systemTray; package dorkbox.systemTray.swing;
import dorkbox.util.DelayTimer; import dorkbox.util.DelayTimer;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
@ -26,8 +26,7 @@ import java.awt.Point;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
public class SwingSystemTrayMenuPopup extends JPopupMenu {
class SystemTrayMenuPopup extends JPopupMenu {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Property @Property
@ -39,8 +38,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
// protected boolean mouseStillOnMenu; // protected boolean mouseStillOnMenu;
// private JDialog hiddenDialog; // private JDialog hiddenDialog;
public SwingSystemTrayMenuPopup() {
SystemTrayMenuPopup() {
super(); super();
setFocusable(true); setFocusable(true);
@ -59,7 +57,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width && if (location.x >= locationOnScreen.x && location.x < locationOnScreen.x + size.width &&
location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) { location.y >= locationOnScreen.y && location.y < locationOnScreen.y + size.height) {
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); SwingSystemTrayMenuPopup.this.timer.delay(SwingSystemTrayMenuPopup.this.timer.getDelay());
} }
else { else {
setVisible(false); setVisible(false);
@ -74,7 +72,7 @@ class SystemTrayMenuPopup extends JPopupMenu {
public public
void mouseExited(MouseEvent event) { void mouseExited(MouseEvent event) {
// wait before checking if mouse is still on the menu // wait before checking if mouse is still on the menu
SystemTrayMenuPopup.this.timer.delay(SystemTrayMenuPopup.this.timer.getDelay()); SwingSystemTrayMenuPopup.this.timer.delay(SwingSystemTrayMenuPopup.this.timer.getDelay());
} }
}); });