forked from dorkbox/SystemTray
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:
parent
8b109f6d1c
commit
c1b6c1a723
24
README.md
24
README.md
@ -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>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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" />
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user