Merge branch 'master' into windows_native

# Conflicts:
#	src/dorkbox/systemTray/SystemTray.java
This commit is contained in:
nathan 2018-01-08 22:15:18 +01:00
commit f1abfbe360
70 changed files with 1327 additions and 5726 deletions

View File

@ -1,7 +1,7 @@
- Dorkbox SystemTray - Apache 2.0 License
https://github.com/dorkbox
Copyright 2014, dorkbox, llc
Cross-platform SystemTray, GtkStatusIcon, and AppIndicator support for applications for Java 6+
Cross-platform SystemTray support for Swing/AWT, GtkStatusIcon, and AppIndicator on Java 6+
- Dorkbox Utils - Apache 2.0 License
@ -19,6 +19,12 @@
Copyright 2010, Brave New Software Project, Inc.
- QZTray - Apache 2.0 License
https://github.com/tresf/tray/blob/dorkbox/src/qz/utils/ShellUtilities.java
Copyright (C) 2016 Tres Finocchiaro, QZ Industries, LLC
Partial code released as Apache 2.0 for use in the SystemTray project by dorkbox, llc. Used with permission.
- SLF4J - MIT License
http://www.slf4j.org
Copyright 2004-2008, QOS.ch

158
README.md
View File

@ -1,21 +1,29 @@
SystemTray
==========
Professional, cross-platform **SystemTray** support for *Swing/AWT*, *GtkStatusIcon*, and *AppIndicator* system-tray types for java applications on Java 6+.
Professional, cross-platform **SystemTray** support for *Swing/AWT*, *GtkStatusIcon*, and *AppIndicator* on Java 6+.
This library provides **OS Native** menus and **Swing/AWT** menus, depending on the OS and Desktop Environment, and if AutoDetect (the default) is set. Linux/Unix will automatically choose *Nativ*e menus, Windows will choose *Swing*, and MacOS will choose *AWT*.
- Please note that *Native* menus, follow the specified look and feel of that OS and are limited by what is supported on the OS. Consequently they are not consistent across all platforms and environments.
This library provides **OS Native** menus and **Swing/AWT** menus, depending on the OS and Desktop Environment and if AutoDetect (the default) is enabled.
- Linux/Unix will automatically choose *Native* (*GtkStatusIcon*, and *AppIndicator*) menus, Windows will choose *Swing*, and MacOS will choose *AWT*.
- Please note that the *Native* and *AWT* menus follow the specified look and feel of that OS and are limited by what is supported on the OS. Consequently they are not consistent across all platforms and environments.
- In most cases on Linux/Unix, *Native* menus are used. In cases where libraries are missing or there are un-resolvable GTK version conflicts, we try to fallback to using *Swing*.
 
The following unique problems are also solved by this library:
1. *Sun/Oracle* system-tray icons on gnu/linux **do not** support images with transparent backgrounds
2. *Sun/Oracle* system-tray and *SWT* system-tray implementations **do not** support app-indicators, which are necessary on different distributions of gnu/linux and unix
3. *Sun/Oracle* system-tray menus on Windows **look absolutely horrid**
4. *Sun/Oracle* system-tray icons on Windows are **hard-coded** to a max size of 24x24 (it was last updated in *2006*)
5. *Sun/Oracle* system-tray menus on MacOS **do not** always respond to both mouse buttons, where Apple menus do
6. Windows *native* menus **do not** support images attached to menu entries
7. Windows menus **do not** support a different L&F from the running application
1. *Sun/Oracle* system-tray icons on Linux/Unix **do not** support images with transparent backgrounds
1. *Sun/Oracle* system-tray and *SWT* system-tray implementations **do not** support app-indicators, which are necessary on different distributions of Linux/Unix
1. *GtkStatusIcons* on GNOME3 desktop environments are hidden by default
1. *Sun/Oracle* system-tray menus on Windows **look absolutely horrid**
1. *Sun/Oracle* system-tray icons on Windows are **hard-coded** to a max size of 24x24 (it was last updated in *2006*)
1. *Sun/Oracle* system-tray menus on MacOS **do not** always respond to both mouse buttons, where Apple menus do
1. Windows *native* menus **do not** support images attached to menu entries
1. Windows menus **do not** support a different L&F from the running application
1. Windows, Linux, and MacOSX menus (native or otherwise) do not support HiDPI configurations
1. java.awt.Desktop.getDesktop() is **broken** when using GTK3 or on MacOS.
@ -31,47 +39,78 @@ Problems and Restrictions
- **SWT** can use *GTK2* or *GTK3*. If you want to use *GTK2* you must force SWT into *GTK2 mode* via `System.setProperty("SWT_GTK3", "0");` before SWT is initialized and only if there are problems with the autodetection, you can also set `SystemTray.FORCE_GTK2=true;`.
- **AppIndicators** under Ubuntu 16.04 (and possibly other distro's) **will not** work as a different user (ie: as a sudo'd user to `root`), since AppIndicators require a dbus connection to the current user's window manager -- and this cannot happen between different user accounts. **There is no workaround.**
- **AppIndicators** under Ubuntu 16.04 (and possibly other distro's) **will not** work as a different user (ie: as a sudo'd user to `root`), since AppIndicators require a dbus connection to the current user's window manager -- and this cannot happen between different user accounts. We attempt to detect this and fallback to using Swing.
- **MacOSX** is a *special snowflake* in how it handles GUI events, and so there are some bizzaro combinations of SWT, JavaFX, and Swing that do not work together (see the `Notes` below for the details.)
- **MacOSX** *native* menus cannot display images attached to menu entries. If desired, one could override the default for MacOSX so that it uses *Swing* instead of *AWT*, however this will result the SystemTray no-longer supporting the OS theme and transparency. The default of *AWT* was chosen because it looks much, much better than *Swing*.
- **Gnome3** (Fedora, Manjaro, Arch, etc) environments by default **do not** allow the SystemTray icon to be shown. This has been worked around (it will be placed next to the clock) for most Gnome environments, except for Arch linux. Another workaround is to install the [Top Icons plugin](https://extensions.gnome.org/extension/1031/topicons/) plugin which moves icons from the *notification drawer* (it is normally collapsed) at the bottom left corner of the screen to the menu panel next to the clock.
- **Gnome3** (Fedora, Manjaro, Arch, etc) environments by default **do not** allow the SystemTray icon to be shown. This has been worked around (it will be placed next to the clock) for most Gnome environments, except for Arch Linux. Another workaround is to install the [Top Icons plugin](https://extensions.gnome.org/extension/1031/topicons/) plugin which moves icons from the *notification drawer* (it is normally collapsed) at the bottom left corner of the screen to the menu panel next to the clock.
- **ToolTips** The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. Please note that **Ubuntu** does not always support this!
- **ToolTips** The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. Specifically, Swing and GtkStatusIcon types support tray tooltips and menu tooltips. AWT and AppIndicator types do not support tooltips of any kind. Please note that **Ubuntu** uses AppIndicators!
- **Linux/Unix Menus** Some linux environments only support right-click to display the menu, and it is not possible to change the behavior.
- **Linux/Unix Menus** Some Linux environments only support right-click to display the menu, and it is not possible to change the behavior.
- **Linux/Unix and java.awt.Desktop.getDesktop()** Please use the `dorkbox.util.Desktop` class as a replacement, which will
intelligently call the correct OS API to open a folder/directory, email, or browser. (*Many thanks to QZ Tray for this*).
Compatibility Matrix
AutoDetect Compatibility List
------------------
`✓`=supported, `-`= not supported, `+`= see notes
OS | Java/Swing | JavaFX | SWT
--- | --- | --- | --- |
XUbuntu 16.04 | ✓ | ✓ | ✓ |
Ubuntu 16.04 | ✓ | + | ✓ |
UbuntuGnome 16.04 | ✓ | + | ✓ |
Fedora 23 | ✓ | ✓ | ✓ |
Fedora 24 | ✓ | ✓ | ✓ |
Fedora 25 | ✓ | ✓ | ✓ |
Fedora 25 KDE | ✓ | ✓ | ✓ |
LinuxMint 18 | ✓ | ✓ | ✓ |
Elementary OS 0.3.2 | - | ✓ | ✓ |
Elementary OS 0.4 | - | ✓ | ✓ |
Arch Linux + Gnome3 | ✓ | ✓ | ✓ |
FreeBSD 11 + Gnome3 | ✓ | ✓ | + |
Debian 8.5 + Gnome3 | - | - | - |
Debian 8.6 + Gnome3 | - | - | - |
MacOSx | ✓ | + | ✓ |
Win XP | ✓ | ✓ | ✓ |
Win 7 | ✓ | ✓ | ✓ |
Win 8.1 | ✓ | ✓ | ✓ |
Win 10 | ✓ | ✓ | ✓ |
OS | Supported
--- | --- |
Arch Linux + Gnome3 | ✓ |
| |
ChromeOS | - |
| |
Debian 8.5 + Gnome3 | ✓ |
Debian 8.6 + Gnome3 | ✓ |
| |
Elementary OS 0.3.2 | ✓ |
Elementary OS 0.4 | ✓ |
| |
Fedora 23 | ✓ |
Fedora 24 | ✓ |
Fedora 25 | ✓ |
Fedora 25 KDE | ✓ |
| |
FreeBSD 11 + Gnome3 | ✓ |
| |
Kali 2016 | ✓ |
Kali 2017 | ✓ |
| |
LinuxMint 18 | ✓ |
| |
Ubuntu 12.04 | ✓ |
Ubuntu 14.04 | ✓ |
Ubuntu 16.04 | ✓ |
Ubuntu 17.04 | ✓ |
UbuntuGnome 16.04 | ✓ |
UbuntuGnome 17.04 | ✓ |
| |
XUbuntu 16.04 | ✓ |
| |
MacOSx | ✓ |
| |
Windows XP | ✓ |
Windows 7 | ✓ |
Windows 8.1 | ✓ |
Windows 10 | ✓ |
Notes:
-------
- Ubuntu 16.04+ with JavaFX require `libappindicator1` because of JavaFX GTK and indicator panel incompatibilities. See [more details](https://github.com/dorkbox/SystemTray/issues/14#issuecomment-248853532).
- The compatibility list only applies while the SystemTray is in `AutoDetect` mode. Not all OSes support forcing a custom tray type.
- The menu item callbacks occur on **their own dispatch thread** (instead of being on whatever OS's event dispatch thread), in order to
provide consistent actions across all platforms. It is critical to make sure that access to Swing/etc that depend on running events
inside their own EDT, are properly called. IE: `SwingUtilities.invokeLater()`. Do not use `invokeAndWait()` as weird GUI anomalies
can happen.
- Ubuntu 16.04+ with JavaFX require `libappindicator1` because of JavaFX GTK and indicator panel incompatibilities. See [more details](https://github.com/dorkbox/SystemTray/issues/14#issuecomment-248853532). We attempt to fallback to using Swing in this situation.
- Ubuntu 17.04+ Java only supports the X11 backend. MIR and Wayland are not supported.
- Debian + GNOME 3, SystemTray works, but will only show in a tray via pressing SUPER+M.
- MacOSX JavaFX (Java7) is incompatible with the SystemTray by default. See [issue details](https://bugs.openjdk.java.net/browse/JDK-8116017).
- To fix this do one of the following
@ -81,6 +120,8 @@ Notes:
- SWT builds for FreeBSD do not exist.
- Linux/Unix: If you want to run this library as a different user, you will need to launch your application via `sudo su username /bin/sh -c "DBUS_SESSION_BUS_ADDRESS='unix:abstract=/tmp/dbus-cLtEoBPmgC' XDG_CURRENT_DESKTOP=$XDG_CURRENT_DESKTOP program-name"`, where `unix:abstract=/tmp/dbus-cLtEoBPmgC` from `/run/user/{uid}/dbus-session`. You will also want to disable the root check + warnings via `SystemTray.ENABLE_ROOT_CHECK=false;` See [issue](https://github.com/dorkbox/SystemTray/issues/63) for more details.
 
 
@ -95,6 +136,10 @@ SystemTray.AUTO_SIZE (type boolean, default value 'true')
SystemTray.FORCE_GTK2 (type boolean, default value 'false')
- Forces the system tray to always choose GTK2 (even when GTK3 might be available).
SystemTray.PREFER_GTK3 (type boolean, default value 'true')
- Prefer to load GTK3 before trying to load GTK2.
SystemTray.FORCE_TRAY_TYPE (type SystemTray.TrayType, default value 'AutoDetect')
- Forces the system tray detection to be AutoDetect, GtkStatusIcon, AppIndicator, Swing, or AWT.
@ -111,6 +156,12 @@ SystemTray.ENABLE_SHUTDOWN_HOOK (type boolean, default value 'true')
SystemTray.AUTO_FIX_INCONSISTENCIES (type boolean, default value 'true')
- Allows the SystemTray logic to resolve various OS inconsistencies for the SystemTray in different combinations
SystemTray.ENABLE_ROOT_CHECK (type boolean, default value 'true')
- Allows the SystemTray logic to ignore if root is detected. Usually when running as root it won't work (because of
how DBUS operates), but in rare situations, it might work.
This is an advanced feature, and it is recommended to leave as true
SystemTray.SWING_UI (type SwingUIFactory, default value 'null')
@ -133,7 +184,7 @@ Extension.ENABLE_SHELL_RESTART (type boolean, default value 'true')
- Permit the gnome-shell to be restarted when the extension is installed.
Extension.SHELL_RESTART_COMMAND (type String, default value 'nome-shell --replace &')
Extension.SHELL_RESTART_COMMAND (type String, default value 'gnome-shell --replace &')
- Command to restart the gnome-shell. It is recommended to start it in the background (hence '&')
@ -182,13 +233,10 @@ Note: Gnome-shell users can install an extension to support placing the tray ico
is initially hidden.
````
````
Note: AppIndicator environments (mostly, just Ubuntu) might notice the menu
getting constructed (it starts out small, then fills the space). Sometimes
even the small menu will get stuck, and be slightly visible behind the
larger menu.
If this happens to you, please let us know in an issue, with detailed
system info, please!
Note: We have fixed the Swing notification tray on Linux (it no longer has a greyish background), however
to facilitate this, a screen-shot is grabbed where the icon is. Because this must happen after the
icon is placed, *sometimes* you can see this happen. Unfortunately this is the only way to fix
this problem, and there are no other known workarounds outside of writing an X11 wrapper from scratch.
````
 
 
@ -224,21 +272,27 @@ This project includes some utility classes that are a small subset of a much lar
Maven Info
---------
````
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>SystemTray</artifactId>
<version>3.1</version>
</dependency>
<dependencies>
...
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>SystemTray</artifactId>
<version>3.12</version>
</dependency>
</dependencies>
````
Or if you don't want to use Maven, you can access the latest files and source-code directly from here:
https://github.com/dorkbox/SystemTray/releases
https://oss.sonatype.org/content/repositories/releases/com/dorkbox/SystemTray/
https://oss.sonatype.org/content/repositories/releases/com/dorkbox/ShellExecutor/
https://repo1.maven.org/maven2/net/java/dev/jna/jna/
https://repo1.maven.org/maven2/net/java/dev/jna/jna-platform/
https://repo1.maven.org/maven2/org/slf4j/slf4j-api/
https://repo1.maven.org/maven2/org/javassist/javassist/

View File

@ -1,34 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="EclipseModuleManager">
<libelement value="jar://$MODULE_DIR$/libs/jna/jna-4.1.0.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/dorkboxUtil.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/logging/slf4j-api-1.7.5.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/slf4j-api-1.7.5.jar!/" />
<libelement value="jar://$MODULE_DIR$/libs/dorkboxUtil_v1.0.jar!/" />
<src_description expected_position="0">
<src_folder value="file://$MODULE_DIR$/src" expected_position="0" />
</src_description>
</component>
<component name="FindBugs-IDEA">
<detectors>
<detector name="InefficientIndexOf" enabled="true" />
<detector name="InefficientInitializationInsideLoop" enabled="true" />
<detector name="InefficientToArray" enabled="true" />
</detectors>
</component>
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.settings" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/libs" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
<orderEntry type="module" module-name="Utilities" />
<orderEntry type="library" name="dorkbox shell_executor" level="application" />
<orderEntry type="library" name="logging slf4j-api" level="application" />
<orderEntry type="library" name="logging logback" level="application" />
<orderEntry type="library" name="jna" level="application" />

View File

@ -81,7 +81,7 @@ class Entry {
}
/**
* Removes this menu entry from the menu and releases all system resources associated with this menu entry
* Removes this menu entry from the menu and releases all system resources associated with this menu entry.
*/
public
void remove() {

View File

@ -408,33 +408,15 @@ class Menu extends MenuItem {
}
}
/**
* This removes all menu entries from this menu
*/
public
void clear() {
List<Entry> copy;
synchronized (menuEntries) {
// access on this object must be synchronized for object visibility
// a copy is made to prevent deadlocks from occurring when operating in different threads
// have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
copy = new ArrayList<Entry>(menuEntries);
menuEntries.clear();
}
for (Entry entry : copy) {
entry.remove();
}
}
/**
* This removes all menu entries from this menu AND this menu from it's parent
*/
@Override
public
void remove() {
clear();
synchronized (menuEntries) {
menuEntries.clear();
}
super.remove();
}

View File

@ -33,6 +33,8 @@ import dorkbox.util.SwingUtil;
@SuppressWarnings({"unused", "SameParameterValue", "WeakerAccess"})
public
class MenuItem extends Entry {
private static boolean alreadyEmittedTooltipWarning = false;
private volatile String text;
private volatile File imageFile;
private volatile ActionListener callback;
@ -40,6 +42,7 @@ class MenuItem extends Entry {
// default enabled is always true
private volatile boolean enabled = true;
private volatile char mnemonicKey;
private volatile String tooltip;
public
MenuItem() {
@ -133,6 +136,7 @@ class MenuItem extends Entry {
peer.setText(this);
peer.setCallback(this);
peer.setShortcut(this);
peer.setTooltip(this);
}
protected
@ -343,15 +347,44 @@ class MenuItem extends Entry {
}
}
@Override
/**
* Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name, or to provide extra
* information during mouse-over for menu entries.
* <p>
* NOTE: Maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments.
* <p>
* For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12.
*
* @param tooltipText the text to use as a mouse-over tooltip for the tray icon or menu entry, null to remove.
*/
public
void remove() {
if (peer != null) {
setImage_(null);
setText(null);
setCallback(null);
void setTooltip(final String tooltipText) {
if (tooltipText != null) {
// this is a safety precaution, since the behavior of really long text is undefined.
if (tooltipText.length() > 64) {
throw new RuntimeException("Tooltip text cannot be longer than 64 characters.");
}
if (!alreadyEmittedTooltipWarning) {
alreadyEmittedTooltipWarning = true;
SystemTray.logger.warn("Tooltips are not consistent across all platforms and tray types.");
}
}
super.remove();
this.tooltip = tooltipText;
if (peer != null) {
((MenuItemPeer) peer).setTooltip(this);
}
}
/**
* Gets the mouse-over tooltip for the meme entry.
*
* NOTE: This is not consistent across all platforms and tray types.
*/
public
String getTooltip() {
return this.tooltip;
}
}

View File

@ -15,6 +15,8 @@
*/
package dorkbox.systemTray;
import dorkbox.systemTray.peer.SeparatorPeer;
/**
* This represents a common menu-spacer entry, that is cross platform in nature.
* <p>
@ -39,4 +41,14 @@ class Separator extends Entry {
Separator() {
super();
}
/**
* @param peer the platform specific implementation for all actions for this type
* @param parent the parent of this menu, null if the parent is the system tray
* @param systemTray the system tray (which is the object that sits in the system tray)
*/
public
void bind(final SeparatorPeer peer, final Menu parent, final SystemTray systemTray) {
super.bind(peer, parent, systemTray);
}
}

View File

@ -48,19 +48,22 @@ import dorkbox.systemTray.nativeUI._WindowsNativeTray;
import dorkbox.systemTray.swingUI.SwingUIFactory;
import dorkbox.systemTray.swingUI._SwingTray;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.LinuxSwingUI;
import dorkbox.systemTray.util.SizeAndScalingUtil;
import dorkbox.systemTray.util.Swt;
import dorkbox.systemTray.util.SystemTrayFixes;
import dorkbox.systemTray.util.WindowsSwingUI;
import dorkbox.util.CacheUtil;
import dorkbox.util.IO;
import dorkbox.util.JavaFX;
import dorkbox.util.OS;
import dorkbox.util.OSUtil;
import dorkbox.util.Property;
import dorkbox.util.SwingUtil;
import sun.security.action.GetPropertyAction;
import dorkbox.util.Swt;
import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.Gtk;
import dorkbox.util.jna.linux.GtkCheck;
import dorkbox.util.jna.linux.GtkEventDispatch;
/**
@ -99,6 +102,10 @@ class SystemTray {
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
public static boolean FORCE_GTK2 = false;
@Property
/** Prefer to load GTK3 before trying to load GTK2. */
public static boolean PREFER_GTK3 = true;
@Property
/**
* Forces the system tray detection to be AutoDetect, GtkStatusIcon, AppIndicator, Swing, or AWT.
@ -126,6 +133,15 @@ class SystemTray {
*/
public static boolean AUTO_FIX_INCONSISTENCIES = true;
@Property
/**
* Allows the SystemTray logic to ignore if root is detected. Usually when running as root it won't work (because of how DBUS
* operates), but in rare situations, it might work.
* <p>
* This is an advanced feature, and it is recommended to leave as true
*/
public static boolean ENABLE_ROOT_CHECK = true;
@Property
/**
* Allows a custom look and feel for the Swing UI, if defined. See the test example for specific use.
@ -136,41 +152,12 @@ class SystemTray {
/**
* This property is provided for debugging any errors in the logic used to determine the system-tray type.
*/
public static boolean DEBUG = true;
public static boolean DEBUG = false;
private static volatile SystemTray systemTray = null;
private static volatile Tray systemTrayMenu = null;
public final static boolean isJavaFxLoaded;
public final static boolean isSwtLoaded;
static {
boolean isJavaFxLoaded_ = false;
boolean isSwtLoaded_ = false;
try {
// this is important to use reflection, because if JavaFX is not being used, calling getToolkit() will initialize it...
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true);
ClassLoader cl = ClassLoader.getSystemClassLoader();
// JavaFX Java7,8 is GTK2 only. Java9 can have it be GTK3 if -Djdk.gtk.version=3 is specified
// see http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
isJavaFxLoaded_ = (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application"));
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be the same!!
// SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3
isSwtLoaded_ = null != m.invoke(cl, "org.eclipse.swt.widgets.Display");
} catch (Throwable e) {
if (DEBUG) {
logger.debug("Error detecting javaFX/SWT mode", e);
}
}
isJavaFxLoaded = isJavaFxLoaded_;
isSwtLoaded = isSwtLoaded_;
}
private static
boolean isTrayType(final Class<? extends Tray> tray, final TrayType trayType) {
@ -247,7 +234,7 @@ class SystemTray {
OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get();
if (DEBUG) {
logger.debug("Currently using the '{}' desktop environment", de);
logger.debug("Currently using the '{}' desktop environment" + OS.LINE_SEPARATOR + OSUtil.Linux.getInfo(), de);
}
switch (de) {
@ -288,11 +275,21 @@ class SystemTray {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("default".equalsIgnoreCase(GDM)) {
// this can be gnome3 on debian
Tray.usingGnome = true;
// this can be gnome3 on debian/kali
if (OSUtil.Linux.isDebian()) {
// note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs)
logger.warn("Debian with Gnome detected. SystemTray support is not known to work.");
if (OSUtil.Linux.isKali()) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
if (OSUtil.Linux.isDebian() && Extension.ENABLE_EXTENSION_INSTALL) {
logger.warn("Debian with Gnome detected. SystemTray works, but will only show via SUPER+M.");
if (DEBUG) {
logger.debug("Disabling the extension install. It won't work.");
}
Extension.ENABLE_EXTENSION_INSTALL = false;
}
return selectTypeQuietly(TrayType.GtkStatusIcon);
@ -304,6 +301,13 @@ class SystemTray {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("ubuntu".equalsIgnoreCase(GDM)) {
// ubuntu 17.10 uses the NEW gnome DE, which screws up previous Ubuntu workarounds, since it's now proper Gnome
int[] version = OSUtil.Linux.getUbuntuVersion();
if (version[0] > 17 || (version[0] == 17 && version[1] == 10)) {
// this is gnome 3.26.1
logger.debug("This Ubuntu 17.10 is not yet supported!");
}
return selectTypeQuietly(TrayType.AppIndicator);
}
break;
@ -324,6 +328,10 @@ class SystemTray {
// Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
return selectTypeQuietly(TrayType.AppIndicator);
}
case Unity7: {
// Ubuntu Unity7 (17.04+, which has MIR) is a weird combination. It's "Gnome", but it's not "Gnome Shell".
return selectTypeQuietly(TrayType.AppIndicator);
}
case XFCE: {
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
// see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/
@ -343,6 +351,12 @@ class SystemTray {
// With eOS, we CANNOT show the spacer image. It does not work.
return selectTypeQuietly(TrayType.AppIndicator);
}
case ChromeOS:
// ChromeOS cannot use the swing tray (ChromeOS is not supported!), nor AppIndicaitor/GtkStatusIcon, as those
// libraries do not exist on ChromeOS. Additionally, Java cannot load external libraries unless they are in /bin,
// BECAUSE of the `noexec` bit set. If JNA is moved into /bin, and the JNA library is specified to load from that
// location, we can use JNA.
return null;
}
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
@ -394,6 +408,19 @@ class SystemTray {
logger.error("Error detecting gnome version", e);
}
}
if (OS.isLinux()) {
// now just blanket query what we are to guess...
if (OSUtil.Linux.isUbuntu()) {
return selectTypeQuietly(TrayType.AppIndicator);
}
else if (OSUtil.Linux.isFedora()) {
return selectTypeQuietly(TrayType.AppIndicator);
} else {
// AppIndicators are now the "default" for most linux distro's.
return selectTypeQuietly(TrayType.AppIndicator);
}
}
}
return null;
@ -406,8 +433,6 @@ class SystemTray {
return;
}
systemTray = new SystemTray();
// if (DEBUG) {
// Properties properties = System.getProperties();
// for (Map.Entry<Object, Object> entry : properties.entrySet()) {
@ -441,7 +466,7 @@ class SystemTray {
// cannot mix Swing/AWT and JavaFX for MacOSX in java7 (fixed in java8) without special stuff.
// https://bugs.openjdk.java.net/browse/JDK-8116017
// https://bugs.openjdk.java.net/browse/JDK-8118714
if (isJavaFxLoaded && OS.javaVersion <= 7 && !System.getProperty("javafx.macosx.embedded", "false").equals("true")) {
if (JavaFX.isLoaded && OS.javaVersion <= 7 && !System.getProperty("javafx.macosx.embedded", "false").equals("true")) {
logger.error("MacOSX JavaFX (Java7) is incompatible with the SystemTray by default. See issue: " +
"'https://bugs.openjdk.java.net/browse/JDK-8116017' and 'https://bugs.openjdk.java.net/browse/JDK-8118714'\n" +
@ -459,7 +484,7 @@ class SystemTray {
// cannot mix Swing and SWT on MacOSX (for all versions of java) so we force native menus instead, which work just fine with SWT
// http://mail.openjdk.java.net/pipermail/bsd-port-dev/2008-December/000173.html
if (isSwtLoaded && FORCE_TRAY_TYPE == TrayType.Swing) {
if (Swt.isLoaded && FORCE_TRAY_TYPE == TrayType.Swing) {
if (AUTO_FIX_INCONSISTENCIES) {
logger.warn("Unable to load Swing + SWT (for all versions of Java). Using the AWT Tray type instead.");
@ -483,40 +508,57 @@ class SystemTray {
}
}
else if (isNix) {
// linux/unix can use all of the tray types
// linux/unix can use all of the tray types. AWT looks horrid. GTK versions are really sensitive...
// NOTE: if the UI uses the 'getSystemLookAndFeelClassName' and is on Linux, this will cause GTK2 to get loaded first,
// which will cause conflicts if one tries to use GTK3
if (!FORCE_GTK2 && !isJavaFxLoaded && !isSwtLoaded) {
// this checks to see if Swing/SWT/JavaFX has loaded GTK yet, and if so, what version they loaded.
int loadedGtkVersion = GtkCheck.getLoadedGtkVersion();
if (loadedGtkVersion == 2) {
if (AUTO_FIX_INCONSISTENCIES) {
if (!FORCE_GTK2) {
if (JavaFX.isLoaded) {
// JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified
// see
// http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
// https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
String currentUI = UIManager.getLookAndFeel()
.getClass()
.getName();
// we must use GTK2, because JavaFX is GTK2
FORCE_GTK2 = true;
if (DEBUG) {
logger.debug("Forcing GTK2 because JavaFX is GTK2");
}
}
else if (Swt.isLoaded) {
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
// System.setProperty("SWT_GTK3", "0");
boolean mustForceGtk2 = false;
// we must use GTK2, because SWT is GTK2
FORCE_GTK2 = true;
if (DEBUG) {
logger.debug("Forcing GTK2 because SWT is GTK2");
}
}
else {
// we are NOT using javaFX/SWT and our UI is GTK2 and we want GTK3
// JavaFX/SWT can be GTK3, but Swing is not GTK3.
// GTKLookAndFeel.class.getCanonicalName()
if (currentUI.equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel")) {
// this means our look and feel is the GTK look and feel... THIS CREATES PROBLEMS!
// THIS IS NOT DOCUMENTED ANYWHERE...
String swingGtkVersion = java.security.AccessController.doPrivileged(new GetPropertyAction("swing.gtk.version"));
mustForceGtk2 = swingGtkVersion == null || swingGtkVersion.startsWith("2");
}
if (mustForceGtk2) {
// we are NOT using javaFX/SWT and our UI is GTK2 and we want GTK3
// JavaFX/SWT can be GTK3, but Swing can not be GTK3.
if (AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2 because Swing is configured to use GTK2
FORCE_GTK2 = true;
logger.warn("Forcing GTK2 because the Swing UIManager is GTK2");
// we must use GTK2 because Java is configured to use GTK2
FORCE_GTK2 = true;
if (DEBUG) {
logger.debug("Forcing GTK2 because Java has already loaded GTK2");
}
}
} else {
logger.error("Unable to use the SystemTray when the Swing UIManager is configured to use the native L&F, which " +
"uses GTK2. This is incompatible with GTK3. " +
"Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.");
// we are already forcing GTK2, so no extra actions necessary
}
} else {
// !AUTO_FIX_INCONSISTENCIES
if (!FORCE_GTK2) {
// clearly the app developer did not want us to automatically fix anything, and have not correctly specified how
// to load GTK, so abort with an error message.
logger.error("Unable to use the SystemTray when there is a mismatch for GTK loaded preferences. Please correctly " +
"set `SystemTray.FORCE_GTK2=true` or set `SystemTray.AUTO_FIX_INCONSISTENCIES=true`. Aborting...");
systemTrayMenu = null;
systemTray = null;
@ -524,63 +566,124 @@ class SystemTray {
}
}
}
else if (isSwtLoaded) {
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
// System.setProperty("SWT_GTK3", "0");
else if (loadedGtkVersion == 3) {
if (AUTO_FIX_INCONSISTENCIES) {
if (JavaFX.isLoaded) {
// JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified
// see
// http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
// https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
// was SWT forced?
String swt_gtk3 = System.getProperty("SWT_GTK3");
boolean isSwt_GTK3 = swt_gtk3 != null && !swt_gtk3.equals("0");
if (!isSwt_GTK3) {
// check a different property
String property = System.getProperty("org.eclipse.swt.internal.gtk.version");
isSwt_GTK3 = property != null && !property.startsWith("2.");
}
if (FORCE_GTK2) {
// if we are java9, then we can change it -- otherwise we cannot.
if (OS.javaVersion == 9) {
FORCE_GTK2 = false;
logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " +
"configured to use GTK2. Please configure JavaFX to use GTK2 (via `System.setProperty(\"jdk.gtk.version\", \"3\");`) " +
"before JavaFX is initialized, or set `SystemTray.FORCE_GTK2=false;` Undoing `FORCE_GTK2`.");
if (isSwt_GTK3 && FORCE_GTK2) {
logger.error("Unable to use the SystemTray when SWT is configured to use GTK3 and the SystemTray is configured to use " +
"GTK2. Please configure SWT to use GTK2, via `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is " +
"initialized, or set `SystemTray.FORCE_GTK2=false;`");
}
}
systemTrayMenu = null;
systemTray = null;
return;
} else if (!isSwt_GTK3 && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2, because SWT is GTK2
FORCE_GTK2 = true;
if (!PREFER_GTK3) {
// we should use GTK3, since that is what is already loaded
PREFER_GTK3 = true;
if (DEBUG) {
logger.debug("Preferring GTK3 even though specified otherwise, because JavaFX is GTK3");
}
}
}
else if (Swt.isLoaded) {
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
// System.setProperty("SWT_GTK3", "0");
logger.warn("Forcing GTK2 because SWT is GTK2");
}
}
else if (isJavaFxLoaded) {
// JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified
// see
// http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
// https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
boolean isJava_GTK3_Possible = OS.javaVersion >= 9 && System.getProperty("jdk.gtk.version", "2").equals("3");
if (isJava_GTK3_Possible && FORCE_GTK2) {
// if we are java9, then we can change it -- otherwise we cannot.
if (OS.javaVersion == 9 && AUTO_FIX_INCONSISTENCIES) {
FORCE_GTK2 = false;
if (FORCE_GTK2) {
FORCE_GTK2 = false;
logger.warn("Unable to use the SystemTray when SWT is configured to use GTK3 and the SystemTray is configured to use " +
"GTK2. Please configure SWT to use GTK2, via `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is " +
"initialized, or set `SystemTray.FORCE_GTK2=false;`");
}
logger.warn("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " +
"configured to use GTK2. Please configure JavaFX to use GTK2 (via `System.setProperty(\"jdk.gtk.version\", \"3\");`) " +
"before JavaFX is initialized, or set `SystemTray.FORCE_GTK2=false;` Undoing `FORCE_GTK2`.");
if (!PREFER_GTK3) {
// we should use GTK3, since that is what is already loaded
PREFER_GTK3 = true;
if (DEBUG) {
logger.debug("Preferring GTK3 even though specified otherwise, because SWT is GTK3");
}
}
}
else {
// we are NOT using javaFX/SWT and our UI is GTK3 and we want GTK3
// JavaFX/SWT can be GTK3, but Swing is (maybe in the future?) GTK3.
} else {
logger.error("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is configured to use " +
"GTK2. Please set `SystemTray.FORCE_GTK2=false;` if that is not possible then it will not work.");
if (FORCE_GTK2) {
FORCE_GTK2 = false;
logger.warn("Unable to use the SystemTray when Swing is configured to use GTK3 and the SystemTray is " +
"configured to use GTK2. Undoing `FORCE_GTK2.");
}
if (!PREFER_GTK3) {
// we should use GTK3, since that is what is already loaded
PREFER_GTK3 = true;
if (DEBUG) {
logger.debug("Preferring GTK3 even though specified otherwise, because Java has already loaded GTK3");
}
}
}
} else {
// !AUTO_FIX_INCONSISTENCIES
if (JavaFX.isLoaded) {
// JavaFX Java7,8 is GTK2 only. Java9 can MAYBE have it be GTK3 if `-Djdk.gtk.version=3` is specified
// see
// http://mail.openjdk.java.net/pipermail/openjfx-dev/2016-May/019100.html
// https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
// from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+.
if (FORCE_GTK2) {
// if we are java9, then we can change it -- otherwise we cannot.
if (OS.javaVersion == 9) {
logger.error("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is " +
"configured to use GTK2. Please configure JavaFX to use GTK2 (via `System.setProperty(\"jdk.gtk.version\", \"3\");`) " +
"before JavaFX is initialized, or set `SystemTray.FORCE_GTK2=false;` Aborting.");
}
else {
logger.error("Unable to use the SystemTray when JavaFX is configured to use GTK3 and the SystemTray is configured to use " +
"GTK2. Please set `SystemTray.FORCE_GTK2=false;` Aborting.");
}
systemTrayMenu = null;
systemTray = null;
return;
}
}
else if (Swt.isLoaded) {
// Necessary for us to work with SWT based on version info. We can try to set us to be compatible with whatever it is set to
// System.setProperty("SWT_GTK3", "0");
if (FORCE_GTK2) {
logger.error("Unable to use the SystemTray when SWT is configured to use GTK3 and the SystemTray is configured to use " +
"GTK2. Please configure SWT to use GTK2, via `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is " +
"initialized, or set `SystemTray.FORCE_GTK2=false;`");
systemTrayMenu = null;
systemTray = null;
return;
}
}
else if (FORCE_GTK2) {
logger.error("Unable to use the SystemTray when Swing is configured to use GTK3 and the SystemTray is " +
"configured to use GTK2. Aborting.");
systemTrayMenu = null;
systemTray = null;
return;
}
} else if (!isJava_GTK3_Possible && !FORCE_GTK2 && AUTO_FIX_INCONSISTENCIES) {
// we must use GTK2, because JavaFX is GTK2
FORCE_GTK2 = true;
logger.warn("Forcing GTK2 because JavaFX is GTK2");
}
}
}
@ -598,8 +701,8 @@ class SystemTray {
logger.debug("Is Auto sizing tray/menu? {}", AUTO_SIZE);
logger.debug("Is JavaFX detected? {}", isJavaFxLoaded);
logger.debug("Is SWT detected? {}", isSwtLoaded);
logger.debug("Is JavaFX detected? {}", JavaFX.isLoaded);
logger.debug("Is SWT detected? {}", Swt.isLoaded);
logger.debug("Java Swing L&F: {}", UIManager.getLookAndFeel().getID());
if (FORCE_TRAY_TYPE == TrayType.AutoDetect) {
logger.debug("Auto-detecting tray type");
@ -607,7 +710,8 @@ class SystemTray {
else {
logger.debug("Forced tray type: {}", FORCE_TRAY_TYPE.name());
}
logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
logger.debug("Force GTK2: {}", FORCE_GTK2);
logger.debug("Prefer GTK3: {}", PREFER_GTK3);
}
// Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
@ -623,48 +727,70 @@ class SystemTray {
trayType = selectTypeQuietly(SystemTray.FORCE_TRAY_TYPE);
}
if (trayType == null && OSUtil.DesktopEnv.isChromeOS()) {
logger.error("ChromeOS detected and it is not supported. Aborting.");
// fix various incompatibilities
systemTrayMenu = null;
systemTray = null;
return;
}
// fix various incompatibilities with selected tray types
if (isNix) {
// Ubuntu UNITY has issues with GtkStatusIcon (it won't work at all...)
if (isTrayType(trayType, TrayType.GtkStatusIcon) && OSUtil.DesktopEnv.get() == OSUtil.DesktopEnv.Env.Unity && OSUtil.Linux.isUbuntu()) {
if (AUTO_FIX_INCONSISTENCIES) {
// GTK2 does not support AppIndicators!
if (Gtk.isGtk2) {
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("Forcing Swing Tray type because Ubuntu Unity display environment removed support for GtkStatusIcons " +
"and GTK2+ was specified.");
if (isTrayType(trayType, TrayType.GtkStatusIcon)) {
OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get();
if (OSUtil.DesktopEnv.isUnity(de) && OSUtil.Linux.isUbuntu()) {
if (AUTO_FIX_INCONSISTENCIES) {
// GTK2 does not support AppIndicators!
if (Gtk.isGtk2) {
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("Forcing Swing Tray type because Ubuntu Unity display environment removed support for GtkStatusIcons " +
"and GTK2+ was specified.");
}
else {
// we must use AppIndicator because Ubuntu Unity removed GtkStatusIcon support
SystemTray.FORCE_TRAY_TYPE = TrayType.AppIndicator; // this is required because of checks inside of AppIndicator...
trayType = selectTypeQuietly(TrayType.AppIndicator);
logger.warn("Forcing AppIndicator because Ubuntu Unity display environment removed support for GtkStatusIcons.");
}
}
else {
// we must use AppIndicator because Ubuntu Unity removed GtkStatusIcon support
SystemTray.FORCE_TRAY_TYPE = TrayType.AppIndicator; // this is required because of checks inside of AppIndicator...
trayType = selectTypeQuietly(TrayType.AppIndicator);
logger.error("Unable to use the GtkStatusIcons when running on Ubuntu with the Unity display environment, and thus" +
" the SystemTray will not work. " +
"Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.");
logger.warn("Forcing AppIndicator because Ubuntu Unity display environment removed support for GtkStatusIcons.");
systemTrayMenu = null;
systemTray = null;
return;
}
}
else {
logger.error("Unable to use the GtkStatusIcons when running on Ubuntu with the Unity display environment, and thus" +
" the SystemTray will not work. " +
"Please set `SystemTray.AUTO_FIX_INCONSISTENCIES=true;` to automatically fix this problem.");
systemTrayMenu = null;
systemTray = null;
return;
if (de == OSUtil.DesktopEnv.Env.Gnome && (OSUtil.Linux.isKali() || OSUtil.Linux.isFedora())) {
// Fedora and Kali linux has some WEIRD graphical oddities via GTK3. GTK2 looks just fine.
PREFER_GTK3 = false;
if (DEBUG) {
logger.debug("Preferring GTK2 because this OS has weird graphical issues with GTK3 status icons");
}
}
}
if (isTrayType(trayType, TrayType.AppIndicator) && OSUtil.Linux.isRoot()) {
if (SystemTray.ENABLE_ROOT_CHECK && isTrayType(trayType, TrayType.AppIndicator) && OSUtil.Linux.isRoot()) {
// if are we running as ROOT, there can be issues (definitely on Ubuntu 16.04, maybe others)!
if (AUTO_FIX_INCONSISTENCIES) {
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("Attempting to load the SystemTray as the 'root/sudo' user. This will likely not work because of dbus " +
"restrictions. Using the Swing Tray type instead.");
"restrictions. Using the Swing Tray type instead. Please refer to the readme notes or issue #63 on " +
"how to work around this.");
} else {
logger.error("Attempting to load the SystemTray as the 'root/sudo' user. This will likely NOT WORK because of dbus " +
"restrictions.");
"restrictions. Please refer to the readme notes or issue #63 on how to work around this.");
}
}
}
@ -683,78 +809,84 @@ class SystemTray {
// - swing version loads as an image (which can be stream or path, we use path)
CacheUtil.tempDir = "SystemTrayImages";
try {
// at this point, the tray type is what it should be. If there are failures or special cases, all types will fall back to
// Swing.
if (isNix) {
// NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3.
// appindicator3 doesn't support menu icons via GTK2!!
if (!Gtk.isLoaded) {
trayType = selectTypeQuietly(TrayType.Swing);
// linux/unix need access to GTK, so load it up before the tray is loaded!
// Swing gets the image size info VIA gtk, so this is important as well.
GtkEventDispatch.startGui(FORCE_GTK2, PREFER_GTK3, DEBUG);
GtkEventDispatch.waitForEventsToComplete();
logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead.");
if (DEBUG) {
// output what version of GTK we have loaded.
logger.debug("GTK Version: " + Gtk.MAJOR + "." + Gtk.MINOR + "." + Gtk.MICRO);
logger.debug("Is the system already running GTK? {}", Gtk.alreadyRunningGTK);
}
else if (OSUtil.Linux.isArch()) {
// arch linux is fun!
if (isTrayType(trayType, TrayType.AppIndicator) && !AppIndicator.isLoaded) {
// appindicators
// requires the install of libappindicator which is GTK2 (as of 25DEC2016)
// requires the install of libappindicator3 which is GTK3 (as of 25DEC2016)
if (!isTrayType(trayType, TrayType.Swing)) {
// NOTE: appindicator1 -> GTk2, appindicator3 -> GTK3.
// appindicator3 doesn't support menu icons via GTK2!!
if (!Gtk.isLoaded) {
trayType = selectTypeQuietly(TrayType.Swing);
if (Gtk.isGtk2) {
logger.warn("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " +
"Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " +
logger.error("Unable to initialize GTK! Something is severely wrong! Using the Swing Tray type instead.");
}
else if (OSUtil.Linux.isArch()) {
// arch linux is fun!
if (isTrayType(trayType, TrayType.AppIndicator) && !AppIndicator.isLoaded) {
// appindicators
// requires the install of libappindicator which is GTK2 (as of 25DEC2016)
// requires the install of libappindicator3 which is GTK3 (as of 25DEC2016)
trayType = selectTypeQuietly(TrayType.Swing);
if (Gtk.isGtk2) {
logger.warn("Unable to initialize AppIndicator for Arch linux, it requires GTK2! " +
"Please install libappindicator, for example: 'sudo pacman -S libappindicator'. " +
"Using the Swing Tray type instead.");
} else {
logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " +
"Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " +
"Using the Swing Tray type instead."); // GTK3
}
} else if (isTrayType(trayType, TrayType.GtkStatusIcon)) {
if (!Extension.isInstalled()) {
// Automatically install the extension for everyone except Arch. They are bonkers.
Extension.ENABLE_EXTENSION_INSTALL = false;
SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " +
"the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " +
"icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " +
"of the screen to the menu panel next to the clock.");
}
}
}
else if (isTrayType(trayType, TrayType.AppIndicator)) {
if (Gtk.isGtk2 && AppIndicator.isVersion3) {
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'. " +
"Using the Swing Tray type instead.");
} else {
logger.error("Unable to initialize AppIndicator for Arch linux, it requires GTK3! " +
"Please install libappindicator3, for example: 'sudo pacman -S libappindicator3'. " +
"Using the Swing Tray type instead."); // GTK3
}
else if (!AppIndicator.isLoaded) {
// YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load.
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead.");
}
}
}
else if (isTrayType(trayType, TrayType.AppIndicator)) {
if (Gtk.isGtk2 && AppIndicator.isVersion3) {
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
"Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'. " +
"Using the Swing Tray type instead.");
}
else if (!AppIndicator.isLoaded) {
// YIKES. Try to fallback to GtkStatusIndicator, since AppIndicator couldn't load.
trayType = selectTypeQuietly(TrayType.Swing);
logger.warn("Unable to initialize the AppIndicator correctly. Using the Swing Tray type instead.");
}
}
}
if (isJavaFxLoaded) {
// This will initialize javaFX dispatch methods
JavaFX.init();
}
else if (isSwtLoaded) {
// This will initialize swt dispatch methods
Swt.init();
}
if (isNix) {
// linux/unix need access to GTK, so load it up before the tray is loaded!
GtkEventDispatch.startGui();
GtkEventDispatch.waitForEventsToComplete();
}
// have to make adjustments BEFORE the tray/menu image size calculations
if (AUTO_FIX_INCONSISTENCIES && isTrayType(trayType, TrayType.Swing) && SystemTray.SWING_UI == null && SwingUtil.isDefaultLookAndFeel()) {
if (AUTO_FIX_INCONSISTENCIES && isTrayType(trayType, TrayType.Swing) && SystemTray.SWING_UI == null) {
if (isNix) {
SystemTray.SWING_UI = new LinuxSwingUI();
}
@ -765,11 +897,13 @@ class SystemTray {
// initialize tray/menu image sizes. This must be BEFORE the system tray has been created
int trayImageSize = SizeAndScalingUtil.getTrayImageSize(trayType);
int trayImageSize = SizeAndScalingUtil.getTrayImageSize();
int menuImageSize = SizeAndScalingUtil.getMenuImageSize(trayType);
logger.debug("Tray indicator image size: {}", trayImageSize);
logger.debug("Tray menu image size: {}", menuImageSize);
if (DEBUG) {
logger.debug("Tray indicator image size: {}", trayImageSize);
logger.debug("Tray menu image size: {}", menuImageSize);
}
if (AUTO_FIX_INCONSISTENCIES) {
// this logic has to be before we create the system Tray, but after GTK is started (if applicable)
@ -790,7 +924,7 @@ class SystemTray {
if ((isJavaFxLoaded || isSwtLoaded) && SwingUtilities.isEventDispatchThread()) {
if ((JavaFX.isLoaded || Swt.isLoaded) && SwingUtilities.isEventDispatchThread()) {
// This WILL NOT WORK. Let the dev know
logger.error("SystemTray initialization for JavaFX or SWT **CAN NOT** occur on the Swing Event Dispatch Thread " +
"(EDT). Something is seriously wrong.");
@ -803,9 +937,10 @@ class SystemTray {
// javaFX and SWT **CAN NOT** start on the EDT!!
// linux + GTK/AppIndicator menus must not start on the EDT!
systemTray = new SystemTray();
// AWT/Swing must be constructed on the EDT however...
if (isJavaFxLoaded || isSwtLoaded ||
if (JavaFX.isLoaded || Swt.isLoaded ||
(isNix && (isTrayType(trayType, TrayType.GtkStatusIcon) || isTrayType(trayType, TrayType.AppIndicator)))
) {
try {
@ -854,7 +989,7 @@ class SystemTray {
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
if (ENABLE_SHUTDOWN_HOOK) {
if (isJavaFxLoaded) {
if (JavaFX.isLoaded) {
// Necessary because javaFX **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive.
// Also, it's nice to have us shutdown at the same time as the main application
JavaFX.onShutdown(new Runnable() {
@ -867,10 +1002,10 @@ class SystemTray {
}
});
}
else if (isSwtLoaded) {
else if (Swt.isLoaded) {
// this is because SWT **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive
// Also, it's nice to have us shutdown at the same time as the main application
Swt.onShutdown(new Runnable() {
dorkbox.util.Swt.onShutdown(new Runnable() {
@Override
public
void run() {
@ -899,7 +1034,7 @@ class SystemTray {
*/
public static
String getVersion() {
return "3.1";
return "3.12";
}
/**
@ -924,13 +1059,14 @@ class SystemTray {
*/
public
void shutdown() {
// this will call "dispatchAndWait()" behind the scenes, so it is thread-safe
// this is thread-safe
final Menu menu = systemTrayMenu;
if (menu != null) {
menu.remove();
}
systemTrayMenu = null;
EventDispatch.shutdown();
}
/**
@ -1153,7 +1289,7 @@ class SystemTray {
*/
public
int getTrayImageSize() {
return SizeAndScalingUtil.getTrayImageSize(systemTrayMenu.getClass());
return SizeAndScalingUtil.getTrayImageSize();
}

View File

@ -22,9 +22,7 @@ import java.net.URL;
import javax.imageio.stream.ImageInputStream;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.OSUtil;
// This is public ONLY so that it is in the scope for SwingUI and NativeUI system tray components
public
@ -32,27 +30,6 @@ class Tray extends Menu {
// true if we are using gnome (and things depend on it) or false
public static volatile boolean usingGnome = false;
protected static
void installExtension() {
// do we need to install the GNOME extension??
if (Tray.usingGnome) {
if (OSUtil.Linux.isArch()) {
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Running Arch Linux.");
}
if (!Extension.isInstalled()) {
SystemTray.logger.info("You may need a work-around for showing the SystemTray icon - we suggest installing the " +
"the [Top Icons] plugin (https://extensions.gnome.org/extension/1031/topicons/) which moves " +
"icons from the *notification drawer* (it is normally collapsed) at the bottom left corner " +
"of the screen to the menu panel next to the clock.");
}
} else {
// Automatically install the extension for everyone except Arch. It's bonkers.
Extension.install();
}
}
}
// appindicators DO NOT support anything other than PLAIN gtk-menus
// they ALSO do not support tooltips!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
@ -113,32 +90,6 @@ class Tray extends Menu {
}
}
// method that is meant to be overridden by the tray implementations
protected
void setTooltip_(final String tooltipText) {
// default is NO OP
}
/**
* Specifies the tooltip text, usually this is used to brand the SystemTray icon with your product's name.
* <p>
* The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop
* Environments.
* <p>
* For more details on Linux see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12.
*
* @param tooltipText the text to use as tooltip for the tray icon, null to remove
*/
final
void setTooltip(final String tooltipText) {
// this is a safety precaution, since the behavior of really long text is undefined.
if (tooltipText.length() > 64) {
throw new RuntimeException("Tooltip text cannot be longer than 64 characters.");
}
setTooltip_(tooltipText);
}
/**
* Specifies the new image to set for the tray icon.
* <p>

View File

@ -19,7 +19,6 @@ import static dorkbox.systemTray.SystemTray.logger;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@ -27,17 +26,17 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import dorkbox.executor.ShellAsyncExecutor;
import dorkbox.executor.ShellExecutor;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.IO;
import dorkbox.util.OSUtil;
import dorkbox.util.Property;
import dorkbox.util.process.ShellProcessBuilder;
@SuppressWarnings({"DanglingJavadoc", "WeakerAccess"})
public
@ -61,19 +60,15 @@ class Extension {
public static
List<String> getEnabledExtensions() {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
// gsettings get org.gnome.shell enabled-extensions
final ShellProcessBuilder gsettings = new ShellProcessBuilder(outputStream);
final ShellExecutor gsettings = new ShellExecutor();
gsettings.setExecutable("gsettings");
gsettings.addArgument("get");
gsettings.addArgument("org.gnome.shell");
gsettings.addArgument("enabled-extensions");
gsettings.start();
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
String output = gsettings.getOutput();
// now we have to enable us if we aren't already enabled
@ -137,9 +132,6 @@ class Extension {
public static
void setEnabledExtensions(List<String> extensions) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
StringBuilder stringBuilder = new StringBuilder("[");
for (int i = 0, extensionsSize = extensions.size(), limit = extensionsSize-1; i < extensionsSize; i++) {
@ -166,7 +158,7 @@ class Extension {
// gsettings set org.gnome.shell enabled-extensions "['SystemTray@Dorkbox']"
// gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org']"
// gsettings set org.gnome.shell enabled-extensions "['background-logo@fedorahosted.org', 'SystemTray@Dorkbox']"
final ShellProcessBuilder setGsettings = new ShellProcessBuilder(outputStream);
final ShellExecutor setGsettings = new ShellExecutor();
setGsettings.setExecutable("gsettings");
setGsettings.addArgument("set");
setGsettings.addArgument("org.gnome.shell");
@ -179,7 +171,8 @@ class Extension {
void restartShell() {
if (ENABLE_SHELL_RESTART) {
if (SystemTray.DEBUG) {
logger.debug("DEBUG mode enabled. You need to manually restart the shell via '{}'", SHELL_RESTART_COMMAND);
logger.debug("DEBUG mode enabled. You need to log-in/out or manually restart the shell via '{}' to apply the changes.",
SHELL_RESTART_COMMAND);
return;
}
@ -187,11 +180,8 @@ class Extension {
logger.debug("Restarting gnome-shell so tray notification changes can be applied.");
}
// now we have to restart the gnome shell via bash
final ShellProcessBuilder restartShell = new ShellProcessBuilder();
// restart shell in background process
restartShell.addArgument(SHELL_RESTART_COMMAND);
restartShell.start();
// now we have to restart the gnome shell via bash in a background process
ShellAsyncExecutor.runShell(SHELL_RESTART_COMMAND);
// We don't care when the shell restarts, since WHEN IT DOES restart, our extension will show our icon.
}
@ -215,12 +205,15 @@ class Extension {
*/
public static
void install() {
boolean isGnome = OSUtil.DesktopEnv.isGnome();
if (!ENABLE_EXTENSION_INSTALL || !isGnome || (OSUtil.Linux.isDebian())) {
// note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs)
if (!ENABLE_EXTENSION_INSTALL) {
// note: Debian Gnome3 does NOT work! (tested on Debian 8.5 and 8.6 default installs).
return;
}
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Installing Gnome extension.");
}
boolean hasTopIcons;
boolean hasSystemTray;

View File

@ -1,198 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import static dorkbox.systemTray.SystemTray.logger;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.linux.structs.AppIndicatorInstanceStruct;
import dorkbox.util.OS;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for libappindicator
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
@SuppressWarnings({"Duplicates", "SameParameterValue", "DanglingJavadoc"})
public
class AppIndicator {
public static final boolean isVersion3;
public static final boolean isLoaded;
/**
* Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that
* standard library naming convention or features/API set is. We just try until we find one that works, and are able to map the
* symbols we need. There are bash commands that will tell us the linked library name, however - I'd rather not run bash commands
* to determine this.
*
* This is so hacky it makes me sick.
*/
static {
boolean _isVersion3 = false;
boolean _isLoaded = false;
boolean shouldLoadAppIndicator = !(OS.isWindows() || OS.isMacOsX());
if (!shouldLoadAppIndicator) {
_isLoaded = true;
}
// objdump -T /usr/lib/x86_64-linux-gnu/libappindicator.so.1 | grep foo
// objdump -T /usr/lib/x86_64-linux-gnu/libappindicator3.so.1 | grep foo
// NOTE:
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTK2, appindicator3 -> GTK3.
// appindiactor1 is GKT2 only (can't use GTK3 bindings with it)
// appindicator3 doesn't support menu icons via GTK2!!
if (!_isLoaded && SystemTray.FORCE_TRAY_TYPE == SystemTray.TrayType.GtkStatusIcon) {
// if we force GTK type system tray, don't attempt to load AppIndicator libs
if (SystemTray.DEBUG) {
logger.debug("Forced GtkStatusIcon tray type, not using AppIndicator");
}
_isLoaded = true;
}
if (!_isLoaded && SystemTray.FORCE_GTK2) {
// if specified, try loading appindicator1 first, maybe it's there?
// note: we can have GTK2 + appindicator3, but NOT ALWAYS.
try {
// deliberately without the "1" at the end.
final NativeLibrary library = JnaHelper.register("appindicator", AppIndicator.class);
if (library != null) {
_isLoaded = true;
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.debug("Error loading GTK2 explicit appindicator. {}", e.getMessage());
}
}
}
String nameToCheck1;
String nameToCheck2;
if (Gtk.isGtk2) {
nameToCheck1 = "appindicator"; // deliberately without the "1" at the end.
}
else {
nameToCheck1 = "appindicator3";
}
// start with base version using whatever the OS specifies as the proper symbolic link
if (!_isLoaded) {
try {
final NativeLibrary library = JnaHelper.register(nameToCheck1, AppIndicator.class);
String s = library.getFile().getName();
if (SystemTray.DEBUG) {
logger.debug("Loading library (first attempt): '{}'", s);
}
if (s.contains("appindicator3")) {
_isVersion3 = true;
}
_isLoaded = true;
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage());
}
}
}
// maybe it's really GTK2 version? who knows...
if (!_isLoaded) {
try {
JnaHelper.register("appindicator", AppIndicator.class);
_isLoaded = true;
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.debug("Error loading library: '{}'. \n{}", "appindicator", e.getMessage());
}
}
}
// If we are GTK2, change the order we check and load libraries
if (Gtk.isGtk2) {
nameToCheck1 = "appindicator-gtk";
nameToCheck2 = "appindicator-gtk3";
}
else {
nameToCheck1 = "appindicator-gtk3";
nameToCheck2 = "appindicator-gtk";
}
// another type. who knows...
if (!_isLoaded) {
try {
JnaHelper.register(nameToCheck1, AppIndicator.class);
_isLoaded = true;
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.debug("Error loading library: '{}'. \n{}", nameToCheck1, e.getMessage());
}
}
}
// this is HORRID. such a PITA
if (!_isLoaded) {
try {
JnaHelper.register(nameToCheck2, AppIndicator.class);
_isLoaded = true;
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.debug("Error loading library: '{}'. \n{}", nameToCheck2, e.getMessage());
}
}
}
// We fall back to GtkStatusIndicator or Swing if this cannot load
if (shouldLoadAppIndicator && _isLoaded) {
isLoaded = true;
isVersion3 = _isVersion3;
} else {
isLoaded = false;
isVersion3 = false;
}
}
// Note: AppIndicators DO NOT support tooltips, as per mark shuttleworth. Rather stupid IMHO.
// See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
public static final int CATEGORY_APPLICATION_STATUS = 0;
// public static final int CATEGORY_COMMUNICATIONS = 1;
// public static final int CATEGORY_SYSTEM_SERVICES = 2;
// public static final int CATEGORY_HARDWARE = 3;
// public static final int CATEGORY_OTHER = 4;
public static final int STATUS_PASSIVE = 0;
public static final int STATUS_ACTIVE = 1;
// public static final int STATUS_ATTENTION = 2;
public static native
AppIndicatorInstanceStruct app_indicator_new(String id, String icon_name, int category);
public static native void app_indicator_set_title(AppIndicatorInstanceStruct self, String title);
public static native void app_indicator_set_status(AppIndicatorInstanceStruct self, int status);
public static native void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu);
public static native void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name);
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import dorkbox.util.Keep;
@Keep
interface FuncCallback extends Callback {
/**
* @return Gtk.FALSE if it will be automatically removed from the stack once it's handled
*/
int callback(Pointer data);
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import dorkbox.systemTray.jna.linux.structs.GdkEventButton;
import dorkbox.util.Keep;
@Keep
public
interface GEventCallback extends Callback {
void callback(Pointer instance, GdkEventButton event);
}

View File

@ -1,72 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Callback;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for glib-2.0
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Glib {
static {
try {
NativeLibrary library = JnaHelper.register("glib-2.0", Glib.class);
if (library == null) {
SystemTray.logger.error("Error loading Glib library, it failed to load.");
}
} catch (Throwable e) {
SystemTray.logger.error("Error loading Glib library, it failed to load {}", e.getMessage());
}
}
public interface GLogLevelFlags {
public static final int RECURSION = 1 << 0;
public static final int FATAL = 1 << 1;
/* GLib log levels */
public static final int ERROR = 1 << 2; /* always fatal */
public static final int CRITICAL = 1 << 3;
public static final int WARNING = 1 << 4;
public static final int MESSAGE = 1 << 5;
public static final int INFO = 1 << 6;
public static final int DEBUG = 1 << 7;
public static final int MASK = ~(RECURSION | FATAL);
}
public interface GLogFunc extends Callback {
void callback (String log_domain, int log_level, String message, Pointer data);
}
public static final Glib.GLogFunc nullLogFunc = new Glib.GLogFunc() {
@Override
public
void callback(final String log_domain, final int log_level, final String message, final Pointer data) {
// do nothing
}
};
public static native int g_log_set_handler(String log_domain, int levels, GLogFunc handler, Pointer user_data);
public static native void g_log_default_handler (String log_domain, int log_level, String message, Pointer unused_data);
public static native GLogFunc g_log_set_default_handler(GLogFunc log_func, Pointer user_data);
public static native void g_log_remove_handler (String log_domain, int handler_id);
}

View File

@ -1,77 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Callback;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for libgobject-2.0
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Gobject {
static {
try {
NativeLibrary library = JnaHelper.register("gobject-2.0", Gobject.class);
if (library == null) {
SystemTray.logger.error("Error loading GObject library, it failed to load.");
}
} catch (Throwable e) {
SystemTray.logger.error("Error loading GObject library, it failed to load {}", e.getMessage());
}
}
// objdump -T /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0 | grep block
public static native void g_object_unref(Pointer object);
public static native void g_object_force_floating(Pointer object);
public static native void g_object_ref_sink(Pointer object);
// note: the return type here MUST be long to avoid issues on freeBSD. NativeLong (previously used) worked on everything except BSD.
public static native long g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags);
public static native void g_signal_handler_block(Pointer instance, long handlerId);
public static native void g_signal_handler_unblock(Pointer instance, long handlerId);
public static native void g_object_get(Pointer instance, String property_name, Pointer value, Pointer terminator);
// Types are here https://developer.gnome.org/gobject/stable/gobject-Type-Information.html
public static native void g_value_init(Pointer gvalue, double type);
/**
* Clears the current value in value (if any) and "unsets" the type, this releases all resources associated with this GValue. An unset value is the same as an uninitialized (zero-filled) GValue structure.
* @param gvalue
*/
public static native void g_value_unset(Pointer gvalue);
public static native String g_value_get_string(Pointer gvalue);
public static native int g_value_get_int(Pointer gvalue);
public static native Pointer g_type_class_ref(Pointer widgetType);
public static native void g_type_class_unref(Pointer widgetClass);
}

View File

@ -1,647 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import static dorkbox.systemTray.SystemTray.logger;
import com.sun.jna.Function;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.linux.structs.GtkStyle;
import dorkbox.util.OS;
import dorkbox.util.jna.JnaHelper;
/**
* Bindings for GTK+ 2. Bindings that are exclusively for GTK+ 3 are in that respective class
* <p>
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
@SuppressWarnings({"Duplicates", "SameParameterValue", "DeprecatedIsStillUsed", "WeakerAccess"})
public
class Gtk {
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
// objdump -T /usr/local/lib/libgtk-3.so.0 | grep gtk
// For funsies to look at, SyncThing did a LOT of work on compatibility in python (unfortunate for us, but interesting).
// https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py
@SuppressWarnings({"unused", "PointlessBitwiseExpression"})
public static class State {
public static final int NORMAL = 0x0; // normal state.
public static final int ACTIVE = 0x1; // pressed-in or activated; e.g. buttons while the mouse button is held down.
public static final int PRELIGHT = 0x2; // color when the mouse is over an activatable widget.
public static final int SELECTED = 0x3; // color when something is selected, e.g. when selecting some text to cut/copy.
public static final int INSENSITIVE = 0x4; // color when the mouse is over an activatable widget.
public static final int FLAG_NORMAL = 0;
public static final int FLAG_ACTIVE = 1 << 0;
public static final int FLAG_PRELIGHT = 1 << 1;
public static final int FLAG_SELECTED = 1 << 2;
public static final int FLAG_INSENSITIVE = 1 << 3;
public static final int FLAG_INCONSISTENT = 1 << 4;
public static final int FLAG_FOCUSED = 1 << 5;
public static final int FLAG_BACKDROP = 1 << 6;
}
public static class IconSize {
public static final int INVALID = 0; // Invalid size.
public static final int MENU = 1; // Size appropriate for menus (16px).
public static final int SMALL_TOOLBAR = 2; // Size appropriate for small toolbars (16px).
public static final int LARGE_TOOLBAR = 3; // Size appropriate for large toolbars (24px)
public static final int BUTTON = 4; // Size appropriate for buttons (16px)
public static final int DND = 5; // Size appropriate for drag and drop (32px)
public static final int DIALOG = 6; // Size appropriate for dialogs (48px)
}
// NOTE: AppIndicator uses this info to figure out WHAT VERSION OF appindicator to use: GTK2 -> appindicator1, GTK3 -> appindicator3
public static final boolean isGtk2;
public static final boolean isGtk3;
public static final boolean isLoaded;
public static final int FALSE = 0;
public static final int TRUE = 1;
static final boolean alreadyRunningGTK;
public static Function gtk_status_icon_position_menu = null;
public static final int MAJOR;
public static final int MINOR;
public static final int MICRO;
/*
* We can have GTK v3 or v2.
*
* Observations:
* JavaFX uses GTK2, and we can't load GTK3 if GTK2 symbols are loaded
* SWT uses GTK2 or GTK3. We do not work with the GTK3 version of SWT.
*/
static {
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2;
boolean _isGtk2 = false;
boolean _isLoaded = false;
boolean _alreadyRunningGTK = false;
int major = 0;
int minor = 0;
int micro = 0;
boolean shouldLoadGtk = !(OS.isWindows() || OS.isMacOsX());
if (!shouldLoadGtk) {
_isLoaded = true;
}
// we can force the system to use the swing indicator, which WORKS, but doesn't support transparency in the icon. However, there
// are certain GTK functions we might want to use (even if we are Swing or AWT), so we load GTK anyways...
// in some cases, we ALWAYS want to try GTK2 first
String gtk2LibName = "gtk-x11-2.0";
String gtk3LibName = "libgtk-3.so.0";
if (!_isLoaded && shouldUseGtk2) {
try {
NativeLibrary library = JnaHelper.register(gtk2LibName, Gtk2.class);
gtk_status_icon_position_menu = Function.getFunction(gtk2LibName, "gtk_status_icon_position_menu");
_isGtk2 = true;
// when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = Gtk2.gtk_main_level() != 0;
_isLoaded = true;
major = library.getGlobalVariableAddress("gtk_major_version").getInt(0);
minor = library.getGlobalVariableAddress("gtk_minor_version").getInt(0);
micro = library.getGlobalVariableAddress("gtk_micro_version").getInt(0);
if (SystemTray.DEBUG) {
logger.debug("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.error("Error loading library", e);
}
}
}
// now for the defaults...
// start with version 3
if (!_isLoaded) {
try {
// ALSO map Gtk2.java to GTK3 library. Cannot have Gtk3 extend Gtk2, it won't work.
JnaHelper.register(gtk3LibName, Gtk2.class);
gtk_status_icon_position_menu = Function.getFunction(gtk3LibName, "gtk_status_icon_position_menu");
// ALSO have to load the SPECIFIC Gtk+ 3 methods. We cannot subclass because JNA doesn't like it.
// This is BY FAR the best way to accomplish this, however because of the way static methods work, we are
// stuck "loading it twice"
JnaHelper.register(gtk3LibName, Gtk3.class);
// when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = Gtk2.gtk_main_level() != 0;
_isLoaded = true;
major = Gtk3.gtk_get_major_version();
minor = Gtk3.gtk_get_minor_version();
micro = Gtk3.gtk_get_micro_version();
if (SystemTray.DEBUG) {
logger.debug("GTK: {}", gtk3LibName);
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.error("Error loading library", e);
}
}
}
// now version 2
if (!_isLoaded) {
try {
NativeLibrary library = JnaHelper.register(gtk2LibName, Gtk2.class);
gtk_status_icon_position_menu = Function.getFunction(gtk2LibName, "gtk_status_icon_position_menu");
_isGtk2 = true;
// when running inside of JavaFX, this will be '1'. All other times this should be '0'
// when it's '1', it means that someone else has started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = Gtk2.gtk_main_level() != 0;
_isLoaded = true;
major = library.getGlobalVariableAddress("gtk_major_version").getInt(0);
minor = library.getGlobalVariableAddress("gtk_minor_version").getInt(0);
micro = library.getGlobalVariableAddress("gtk_micro_version").getInt(0);
if (SystemTray.DEBUG) {
logger.debug("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.error("Error loading library", e);
}
}
}
if (shouldLoadGtk && _isLoaded) {
isLoaded = true;
// depending on how the system is initialized, SWT may, or may not, have the gtk_main loop running. It will EVENTUALLY run, so we
// do not want to run our own GTK event loop.
_alreadyRunningGTK |= SystemTray.isSwtLoaded;
if (SystemTray.DEBUG) {
logger.debug("Is the system already running GTK? {}", _alreadyRunningGTK);
}
alreadyRunningGTK = _alreadyRunningGTK;
isGtk2 = _isGtk2;
isGtk3 = !_isGtk2;
MAJOR = major;
MINOR = minor;
MICRO = micro;
}
else {
isLoaded = false;
alreadyRunningGTK = false;
isGtk2 = false;
isGtk3 = false;
MAJOR = 0;
MINOR = 0;
MICRO = 0;
}
if (shouldLoadGtk) {
// now we output what version of GTK we have loaded.
if (SystemTray.DEBUG) {
SystemTray.logger.debug("GTK Version: " + MAJOR + "." + MINOR + "." + MICRO);
}
if (!_isLoaded) {
throw new RuntimeException("We apologize for this, but we are unable to determine the GTK library is in use, " +
"or even if it is in use... Please create an issue for this and include your OS type and configuration.");
}
}
}
/**
* Creates a new GtkMenu
*/
public static
Pointer gtk_menu_new() {
return Gtk2.gtk_menu_new();
}
/**
* Sets or replaces the menu items submenu, or removes it when a NULL submenu is passed.
*/
public static
void gtk_menu_item_set_submenu(Pointer menuEntry, Pointer menu) {
Gtk2.gtk_menu_item_set_submenu(menuEntry, menu);
}
/**
* Creates a new GtkSeparatorMenuItem.
*/
public static
Pointer gtk_separator_menu_item_new() {
return Gtk2.gtk_separator_menu_item_new();
}
/**
* Creates a new GtkImage displaying the file filename . If the file isnt found or cant be loaded, the resulting GtkImage will
* display a broken image icon. This function never returns NULL, it always returns a valid GtkImage widget.
* <p>
* If the file contains an animation, the image will contain an animation.
*/
public static
Pointer gtk_image_new_from_file(String iconPath) {
return Gtk2.gtk_image_new_from_file(iconPath);
}
/**
* Sets the active state of the menu items check box.
*/
public static
void gtk_check_menu_item_set_active(Pointer check_menu_item, boolean isChecked) {
Gtk2.gtk_check_menu_item_set_active(check_menu_item, isChecked);
}
/**
* Creates a new GtkImageMenuItem containing a label. The label will be created using gtk_label_new_with_mnemonic(), so underscores
* in label indicate the mnemonic for the menu item.
* <p>
* uses '_' to define which key is the mnemonic
* <p>
* gtk_image_menu_item_new_with_mnemonic has been deprecated since version 3.10 and should not be used in newly-written code.
* NOTE: Use gtk_menu_item_new_with_mnemonic() instead.
*/
public static
Pointer gtk_image_menu_item_new_with_mnemonic(String label) {
return Gtk2.gtk_image_menu_item_new_with_mnemonic(label);
}
public static
Pointer gtk_check_menu_item_new_with_mnemonic(String label) {
return Gtk2.gtk_check_menu_item_new_with_mnemonic(label);
}
/**
* Sets the image of image_menu_item to the given widget. Note that it depends on the show-menu-images setting whether the image
* will be displayed or not.
* <p>
* gtk_image_menu_item_set_image has been deprecated since version 3.10 and should not be used in newly-written code.
*/
public static
void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image) {
Gtk2.gtk_image_menu_item_set_image(image_menu_item, image);
}
/**
* If TRUE, the menu item will ignore the gtk-menu-images setting and always show the image, if available.
* Use this property if the menuitem would be useless or hard to use without the image
* <p>
* gtk_image_menu_item_set_always_show_image has been deprecated since version 3.10 and should not be used in newly-written code.
*/
public static
void gtk_image_menu_item_set_always_show_image(Pointer menu_item, boolean forceShow) {
Gtk2.gtk_image_menu_item_set_always_show_image(menu_item, forceShow);
}
/**
* Creates an empty status icon object.
* <p>
* gtk_status_icon_new has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
Pointer gtk_status_icon_new() {
return Gtk2.gtk_status_icon_new();
}
/**
* Obtains the root window (parent all other windows are inside) for the default display and screen.
*
* @return the default root window
*/
public static
Pointer gdk_get_default_root_window() {
return Gtk2.gdk_get_default_root_window();
}
/**
* Gets the default screen for the default display. (See gdk_display_get_default()).
*
* @return a GdkScreen, or NULL if there is no default display.
*
* @since 2.2
*/
public static
Pointer gdk_screen_get_default() {
return Gtk2.gdk_screen_get_default();
}
/**
* Gets the resolution for font handling on the screen; see gdk_screen_set_resolution() for full details.
*
* IE:
*
* The resolution for font handling on the screen. This is a scale factor between points specified in a PangoFontDescription and
* cairo units. The default value is 96, meaning that a 10 point font will be 13 units high. (10 * 96. / 72. = 13.3).
*
* @return the current resolution, or -1 if no resolution has been set.
*
* @since Since: 2.10
*/
public static
double gdk_screen_get_resolution(Pointer screen) {
return Gtk2.gdk_screen_get_resolution(screen);
}
/**
* Makes status_icon display the file filename . See gtk_status_icon_new_from_file() for details.
* <p>
* gtk_status_icon_set_from_file has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
void gtk_status_icon_set_from_file(Pointer widget, String label) {
Gtk2.gtk_status_icon_set_from_file(widget, label);
}
/**
* Shows or hides a status icon.
* <p>
* gtk_status_icon_set_visible has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
void gtk_status_icon_set_visible(Pointer widget, boolean visible) {
Gtk2.gtk_status_icon_set_visible(widget, visible);
}
/**
* Sets text as the contents of the tooltip.
* This function will take care of setting has-tooltip to TRUE and of the default handler for the query-tooltip signal.
*
* app indicators don't support this
*
* gtk_status_icon_set_tooltip_text has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
void gtk_status_icon_set_tooltip_text(Pointer widget, String tooltipText) {
Gtk2.gtk_status_icon_set_tooltip_text(widget, tooltipText);
}
/**
* Sets the title of this tray icon. This should be a short, human-readable, localized string describing the tray icon. It may be used
* by tools like screen readers to render the tray icon.
* <p>
* gtk_status_icon_set_title has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
void gtk_status_icon_set_title(Pointer widget, String titleText) {
Gtk2.gtk_status_icon_set_title(widget, titleText);
}
/**
* Sets the name of this tray icon. This should be a string identifying this icon. It is may be used for sorting the icons in the
* tray and will not be shown to the user.
* <p>
* gtk_status_icon_set_name has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
public static
void gtk_status_icon_set_name(Pointer widget, String name) {
Gtk2.gtk_status_icon_set_name(widget, name);
}
/**
* Displays a menu and makes it available for selection.
* <p>
* gtk_menu_popup has been deprecated since version 3.22 and should not be used in newly-written code.
* NOTE: Please use gtk_menu_popup_at_widget(), gtk_menu_popup_at_pointer(). or gtk_menu_popup_at_rect() instead
*/
public static
void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time) {
Gtk2.gtk_menu_popup(menu, widget, bla, func, data, button, time);
}
/**
* Sets text on the menu_item label
*/
public static
void gtk_menu_item_set_label(Pointer menu_item, String label) {
Gtk2.gtk_menu_item_set_label(menu_item, label);
}
/**
* Adds a new GtkMenuItem to the end of the menu shell's item list.
*/
public static
void gtk_menu_shell_append(Pointer menu_shell, Pointer child) {
Gtk2.gtk_menu_shell_append(menu_shell, child);
}
/**
* Sets the sensitivity of a widget. A widget is sensitive if the user can interact with it. Insensitive widgets are grayed out
* and the user cant interact with them. Insensitive widgets are known as inactive, disabled, or ghosted in some other toolkits.
*/
public static
void gtk_widget_set_sensitive(Pointer widget, boolean sensitive) {
Gtk2.gtk_widget_set_sensitive(widget, sensitive);
}
/**
* Recursively shows a widget, and any child widgets (if the widget is a container)
*/
public static
void gtk_widget_show_all(Pointer widget) {
Gtk2.gtk_widget_show_all(widget);
}
/**
* Removes widget from container . widget must be inside container . Note that container will own a reference to widget , and that
* this may be the last reference held; so removing a widget from its container can destroy that widget.
* <p>
* If you want to use widget again, you need to add a reference to it before removing it from a container, using g_object_ref().
* If you dont want to use widget again its usually more efficient to simply destroy it directly using gtk_widget_destroy()
* since this will remove it from the container and help break any circular reference count cycles.
*/
public static
void gtk_container_remove(Pointer parentWidget, Pointer widget) {
Gtk2.gtk_container_remove(parentWidget, widget);
}
/**
* Destroys a widget.
* When a widget is destroyed all references it holds on other objects will be released:
* - if the widget is inside a container, it will be removed from its parent
* - if the widget is a container, all its children will be destroyed, recursively
* - if the widget is a top level, it will be removed from the list of top level widgets that GTK+ maintains internally
* <p>
* It's expected that all references held on the widget will also be released; you should connect to the destroy signal if you
* hold a reference to widget and you wish to remove it when this function is called. It is not necessary to do so if you are
* implementing a GtkContainer, as you'll be able to use the GtkContainerClass.remove() virtual function for that.
* <p>
* It's important to notice that gtk_widget_destroy() will only cause the widget to be finalized if no additional references,
* acquired using g_object_ref(), are held on it. In case additional references are in place, the widget will be in an "inert" state
* after calling this function; widget will still point to valid memory, allowing you to release the references you hold, but you
* may not query the widget's own state.
* <p>
* NOTE You should typically call this function on top level widgets, and rarely on child widgets.
*/
public static
void gtk_widget_destroy(Pointer widget) {
Gtk2.gtk_widget_destroy(widget);
}
/**
* Gets the GtkSettings object for screen , creating it if necessary.
*
* @since 2.2
*/
public static
Pointer gtk_settings_get_for_screen(Pointer screen) {
return Gtk2.gtk_settings_get_for_screen(screen);
}
/**
* Simply an accessor function that returns @widget->style.
*/
public static
GtkStyle.ByReference gtk_widget_get_style(Pointer widget) {
return Gtk2.gtk_widget_get_style(widget);
}
/**
* Finds all matching RC styles for a given widget, composites them together, and then creates a GtkStyle representing the composite
* appearance. (GTK+ actually keeps a cache of previously created styles, so a new style may not be created.)
*/
public static
Pointer gtk_rc_get_style(Pointer widget) {
return Gtk2.gtk_rc_get_style(widget);
}
/**
* Looks up color_name in the styles logical color mappings, filling in color and returning TRUE if found, otherwise returning
* FALSE. Do not cache the found mapping, because it depends on the GtkStyle and might change when a theme switch occurs.
*
* @since 2.10
*/
public static
boolean gtk_style_lookup_color(Pointer widgetStyle, String color_name, Pointer color) {
return Gtk2.gtk_style_lookup_color(widgetStyle, color_name, color);
}
/**
* Adds widget to container . Typically used for simple containers such as GtkWindow, GtkFrame, or GtkButton; for more complicated
* layout containers such as GtkBox or GtkTable, this function will pick default packing parameters that may not be correct. So
* consider functions such as gtk_box_pack_start() and gtk_table_attach() as an alternative to gtk_container_add() in those cases.
* A widget may be added to only one container at a time; you can't place the same widget inside two different containers.
*/
public static
void gtk_container_add(Pointer offscreen, Pointer widget) {
Gtk2.gtk_container_add(offscreen, widget);
}
/**
* Get's the child from a GTK Bin object
*/
public static
Pointer gtk_bin_get_child(Pointer bin) {
return Gtk2.gtk_bin_get_child(bin);
}
/**
* Gets the PangoLayout used to display the label. The layout is useful to e.g. convert text positions to pixel positions, in
* combination with gtk_label_get_layout_offsets(). The returned layout is owned by the label so need not be freed by the caller.
*
* The label is free to recreate its layout at any time, so it should be considered read-only.
*/
public static
Pointer gtk_label_get_layout(Pointer label) {
return Gtk2.gtk_label_get_layout(label);
}
/**
* Computes the logical and ink extents of layout in device units. This function just calls pango_layout_get_extents() followed
* by two pango_extents_to_pixels() calls, rounding ink_rect and logical_rect such that the rounded rectangles fully contain the
* unrounded one (that is, passes them as first argument to pango_extents_to_pixels()).
*
* @param layout a PangoLayout
* @param ink_rect rectangle used to store the extents of the layout as drawn or NULL to indicate that the result is not needed.
* @param logical_rect rectangle used to store the logical extents of the layout or NULL to indicate that the result is not needed.
*/
public static
void pango_layout_get_pixel_extents(Pointer layout, Pointer ink_rect, Pointer logical_rect) {
Gtk2.pango_layout_get_pixel_extents(layout, ink_rect, logical_rect);
}
/**
* Creates a toplevel container widget that is used to retrieve snapshots of widgets without showing them on the screen.
*
* @since 2.20
*/
public static
Pointer gtk_offscreen_window_new() {
return Gtk2.gtk_offscreen_window_new();
}
/**
* This function is typically used when implementing a GtkContainer subclass. Obtains the preferred size of a widget. The
* container uses this information to arrange its child widgets and decide what size allocations to give them with
* gtk_widget_size_allocate().
*
* You can also call this function from an application, with some caveats. Most notably, getting a size request requires the
* widget to be associated with a screen, because font information may be needed. Multihead-aware applications should keep this in mind.
*
* Also remember that the size request is not necessarily the size a widget will actually be allocated.
*/
public static
void gtk_widget_size_request(final Pointer widget, final Pointer requisition) {
if (isGtk2) {
Gtk2.gtk_widget_size_request(widget, requisition);
}
else {
Gtk3.gtk_widget_get_preferred_size(widget, requisition, null);
}
}
/**
* Creates a new GtkImageMenuItem containing the image and text from a stock item. Some stock ids have preprocessor macros
* like GTK_STOCK_OK and GTK_STOCK_APPLY.
*
* @param stock_id the name of the stock item.
* @param accel_group the GtkAccelGroup to add the menu items accelerator to, or NULL.
*
* @return a new GtkImageMenuItem.
*/
public static
Pointer gtk_image_menu_item_new_from_stock(String stock_id, Pointer accel_group) {
return Gtk2.gtk_image_menu_item_new_from_stock(stock_id, accel_group);
}
}

View File

@ -1,410 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Function;
import com.sun.jna.Pointer;
import dorkbox.systemTray.jna.linux.structs.GtkStyle;
/**
* Bindings for GTK+ 2. Bindings that are exclusively for GTK+ 3 are in that respective class
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Gtk2 {
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
/**
* Adds a function to be called whenever there are no higher priority events pending. If the function returns FALSE it is automatically
* removed from the list of event sources and will not be called again.
* <p>
* This variant of g_idle_add_full() calls function with the GDK lock held. It can be thought of a MT-safe version for GTK+ widgets
* for the following use case, where you have to worry about idle_callback() running in thread A and accessing self after it has
* been finalized in thread B.
*/
static native
int gdk_threads_add_idle_full(int priority, FuncCallback function, Pointer data, Pointer notify);
/**
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky
* enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
*/
static native
boolean gtk_init_check(int argc);
/**
* Runs the main loop until gtk_main_quit() is called. You can nest calls to gtk_main(). In that case gtk_main_quit() will make the
* innermost invocation of the main loop return.
*/
static native
void gtk_main();
/**
* aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running
*/
static native
int gtk_main_level();
/**
* Makes the innermost invocation of the main loop return when it regains control. ONLY CALL FROM THE GtkSupport class, UNLESS you know
* what you're doing!
*/
static native
void gtk_main_quit();
/**
* Creates a new GtkMenu
*/
public static native
Pointer gtk_menu_new();
/**
* Sets or replaces the menu items submenu, or removes it when a NULL submenu is passed.
*/
public static native
void gtk_menu_item_set_submenu(Pointer menuEntry, Pointer menu);
/**
* Creates a new GtkSeparatorMenuItem.
*/
public static native
Pointer gtk_separator_menu_item_new();
/**
* Creates a new GtkImage displaying the file filename . If the file isnt found or cant be loaded, the resulting GtkImage will
* display a broken image icon. This function never returns NULL, it always returns a valid GtkImage widget.
* <p>
* If the file contains an animation, the image will contain an animation.
*/
public static native
Pointer gtk_image_new_from_file(String iconPath);
/**
* Sets the active state of the menu items check box.
*/
public static native
void gtk_check_menu_item_set_active(Pointer check_menu_item, boolean isChecked);
/**
* Creates a new GtkImageMenuItem containing a label. The label will be created using gtk_label_new_with_mnemonic(), so underscores
* in label indicate the mnemonic for the menu item.
* <p>
* uses '_' to define which key is the mnemonic
* <p>
* gtk_image_menu_item_new_with_mnemonic has been deprecated since version 3.10 and should not be used in newly-written code.
* NOTE: Use gtk_menu_item_new_with_mnemonic() instead.
*/
@Deprecated
public static native
Pointer gtk_image_menu_item_new_with_mnemonic(String label);
public static native
Pointer gtk_check_menu_item_new_with_mnemonic(String label);
/**
* Sets the image of image_menu_item to the given widget. Note that it depends on the show-menu-images setting whether the image
* will be displayed or not.
* <p>
* gtk_image_menu_item_set_image has been deprecated since version 3.10 and should not be used in newly-written code.
*/
@Deprecated
public static native
void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image);
/**
* If TRUE, the menu item will ignore the gtk-menu-images setting and always show the image, if available.
* Use this property if the menuitem would be useless or hard to use without the image
* <p>
* gtk_image_menu_item_set_always_show_image has been deprecated since version 3.10 and should not be used in newly-written code.
*/
@Deprecated
public static native
void gtk_image_menu_item_set_always_show_image(Pointer menu_item, boolean forceShow);
/**
* Creates an empty status icon object.
* <p>
* gtk_status_icon_new has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
Pointer gtk_status_icon_new();
/**
* Obtains the root window (parent all other windows are inside) for the default display and screen.
*
* @return the default root window
*/
public static native
Pointer gdk_get_default_root_window();
/**
* Gets the default screen for the default display. (See gdk_display_get_default()).
*
* @return a GdkScreen, or NULL if there is no default display.
*
* @since 2.2
*/
public static native
Pointer gdk_screen_get_default();
/**
* Gets the resolution for font handling on the screen; see gdk_screen_set_resolution() for full details.
*
* IE:
*
* The resolution for font handling on the screen. This is a scale factor between points specified in a PangoFontDescription and
* cairo units. The default value is 96, meaning that a 10 point font will be 13 units high. (10 * 96. / 72. = 13.3).
*
* @return the current resolution, or -1 if no resolution has been set.
*
* @since Since: 2.10
*/
public static native
double gdk_screen_get_resolution(Pointer screen);
/**
* Makes status_icon display the file filename . See gtk_status_icon_new_from_file() for details.
* <p>
* gtk_status_icon_set_from_file has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
void gtk_status_icon_set_from_file(Pointer widget, String label);
/**
* Shows or hides a status icon.
* <p>
* gtk_status_icon_set_visible has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
void gtk_status_icon_set_visible(Pointer widget, boolean visible);
/**
* Sets text as the contents of the tooltip.
* This function will take care of setting has-tooltip to TRUE and of the default handler for the query-tooltip signal.
*
* app indicators don't support this
*
* gtk_status_icon_set_tooltip_text has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
void gtk_status_icon_set_tooltip_text(Pointer widget, String tooltipText);
/**
* Sets the title of this tray icon. This should be a short, human-readable, localized string describing the tray icon. It may be used
* by tools like screen readers to render the tray icon.
* <p>
* gtk_status_icon_set_title has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
void gtk_status_icon_set_title(Pointer widget, String titleText);
/**
* Sets the name of this tray icon. This should be a string identifying this icon. It is may be used for sorting the icons in the
* tray and will not be shown to the user.
* <p>
* gtk_status_icon_set_name has been deprecated since version 3.14 and should not be used in newly-written code.
* Use notifications
*/
@Deprecated
public static native
void gtk_status_icon_set_name(Pointer widget, String name);
/**
* Displays a menu and makes it available for selection.
* <p>
* gtk_menu_popup has been deprecated since version 3.22 and should not be used in newly-written code.
* NOTE: Please use gtk_menu_popup_at_widget(), gtk_menu_popup_at_pointer(). or gtk_menu_popup_at_rect() instead
*/
@Deprecated
public static native
void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time);
/**
* Sets text on the menu_item label
*/
public static native
void gtk_menu_item_set_label(Pointer menu_item, String label);
/**
* Adds a new GtkMenuItem to the end of the menu shell's item list.
*/
public static native
void gtk_menu_shell_append(Pointer menu_shell, Pointer child);
/**
* Sets the sensitivity of a widget. A widget is sensitive if the user can interact with it. Insensitive widgets are grayed out
* and the user cant interact with them. Insensitive widgets are known as inactive, disabled, or ghosted in some other toolkits.
*/
public static native
void gtk_widget_set_sensitive(Pointer widget, boolean sensitive);
/**
* Recursively shows a widget, and any child widgets (if the widget is a container)
*/
public static native
void gtk_widget_show_all(Pointer widget);
/**
* Removes widget from container . widget must be inside container . Note that container will own a reference to widget , and that
* this may be the last reference held; so removing a widget from its container can destroy that widget.
* <p>
* If you want to use widget again, you need to add a reference to it before removing it from a container, using g_object_ref().
* If you dont want to use widget again its usually more efficient to simply destroy it directly using gtk_widget_destroy()
* since this will remove it from the container and help break any circular reference count cycles.
*/
public static native
void gtk_container_remove(Pointer parentWidget, Pointer widget);
/**
* Destroys a widget.
* When a widget is destroyed all references it holds on other objects will be released:
* - if the widget is inside a container, it will be removed from its parent
* - if the widget is a container, all its children will be destroyed, recursively
* - if the widget is a top level, it will be removed from the list of top level widgets that GTK+ maintains internally
* <p>
* It's expected that all references held on the widget will also be released; you should connect to the destroy signal if you
* hold a reference to widget and you wish to remove it when this function is called. It is not necessary to do so if you are
* implementing a GtkContainer, as you'll be able to use the GtkContainerClass.remove() virtual function for that.
* <p>
* It's important to notice that gtk_widget_destroy() will only cause the widget to be finalized if no additional references,
* acquired using g_object_ref(), are held on it. In case additional references are in place, the widget will be in an "inert" state
* after calling this function; widget will still point to valid memory, allowing you to release the references you hold, but you
* may not query the widget's own state.
* <p>
* NOTE You should typically call this function on top level widgets, and rarely on child widgets.
*/
public static native
void gtk_widget_destroy(Pointer widget);
/**
* Gets the GtkSettings object for screen , creating it if necessary.
*
* @since 2.2
*/
public static native
Pointer gtk_settings_get_for_screen(Pointer screen);
/**
* Simply an accessor function that returns @widget->style.
*/
public static native
GtkStyle.ByReference gtk_widget_get_style(Pointer widget);
/**
* Finds all matching RC styles for a given widget, composites them together, and then creates a GtkStyle representing the composite
* appearance. (GTK+ actually keeps a cache of previously created styles, so a new style may not be created.)
*/
public static native
Pointer gtk_rc_get_style(Pointer widget);
/**
* Looks up color_name in the styles logical color mappings, filling in color and returning TRUE if found, otherwise returning
* FALSE. Do not cache the found mapping, because it depends on the GtkStyle and might change when a theme switch occurs.
*
* @since 2.10
*/
public static native
boolean gtk_style_lookup_color(Pointer widgetStyle, String color_name, Pointer color);
/**
* Adds widget to container . Typically used for simple containers such as GtkWindow, GtkFrame, or GtkButton; for more complicated
* layout containers such as GtkBox or GtkTable, this function will pick default packing parameters that may not be correct. So
* consider functions such as gtk_box_pack_start() and gtk_table_attach() as an alternative to gtk_container_add() in those cases.
* A widget may be added to only one container at a time; you can't place the same widget inside two different containers.
*/
public static native
void gtk_container_add(Pointer offscreen, Pointer widget);
/**
* Get's the child from a GTK Bin object
*/
public static native
Pointer gtk_bin_get_child(Pointer bin);
/**
* Gets the PangoLayout used to display the label. The layout is useful to e.g. convert text positions to pixel positions, in
* combination with gtk_label_get_layout_offsets(). The returned layout is owned by the label so need not be freed by the caller.
*
* The label is free to recreate its layout at any time, so it should be considered read-only.
*/
public static native
Pointer gtk_label_get_layout(Pointer label);
/**
* Computes the logical and ink extents of layout in device units. This function just calls pango_layout_get_extents() followed
* by two pango_extents_to_pixels() calls, rounding ink_rect and logical_rect such that the rounded rectangles fully contain the
* unrounded one (that is, passes them as first argument to pango_extents_to_pixels()).
*
*
* @param layout a PangoLayout
* @param ink_rect rectangle used to store the extents of the layout as drawn or NULL to indicate that the result is not needed.
* @param logical_rect rectangle used to store the logical extents of the layout or NULL to indicate that the result is not needed.
*
*/
public static native
void pango_layout_get_pixel_extents (Pointer layout, Pointer ink_rect, Pointer logical_rect);
/**
* Creates a toplevel container widget that is used to retrieve snapshots of widgets without showing them on the screen.
*
* @since 2.20
*/
public static native
Pointer gtk_offscreen_window_new ();
/**
* This function is typically used when implementing a GtkContainer subclass. Obtains the preferred size of a widget. The
* container uses this information to arrange its child widgets and decide what size allocations to give them with
* gtk_widget_size_allocate().
*
* You can also call this function from an application, with some caveats. Most notably, getting a size request requires the
* widget to be associated with a screen, because font information may be needed. Multihead-aware applications should keep this in mind.
*
* Also remember that the size request is not necessarily the size a widget will actually be allocated.
*/
@Deprecated
public static native
void gtk_widget_size_request(final Pointer widget, final Pointer requisition);
/**
* Creates a new GtkImageMenuItem containing the image and text from a stock item. Some stock ids have preprocessor macros
* like GTK_STOCK_OK and GTK_STOCK_APPLY.
*
* @param stock_id the name of the stock item.
* @param accel_group the GtkAccelGroup to add the menu items accelerator to, or NULL.
*
* @return a new GtkImageMenuItem.
*/
public static native
Pointer gtk_image_menu_item_new_from_stock(String stock_id, Pointer accel_group);
}

View File

@ -1,187 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Pointer;
/**
* bindings for GTK+ 3.
* <p>
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Gtk3 {
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
// objdump -T /usr/local/lib/libgtk-3.so.0 | grep gtk
public static native
int gtk_get_major_version();
public static native
int gtk_get_minor_version();
public static native
int gtk_get_micro_version();
/**
* Loads a theme from the usual theme paths
*
* @param name A theme name
* @param variant variant to load, for example, "dark", or NULL for the default.
*
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
*
* @since 3.0
*/
public static native
Pointer gtk_css_provider_get_named(String name, String variant);
/**
* Returns the provider containing the style settings used as a fallback for all widgets.
*
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
*
* @since 3.0
*/
public static native
Pointer gtk_css_provider_get_default();
/**
* Converts the provider into a string representation in CSS format.
* <p>
* Using gtk_css_provider_load_from_data() with the return value from this function on a new provider created with
* gtk_css_provider_new() will basically create a duplicate of this provider .
*
* @since 3.2 (released in 2011)
*/
public static native
String gtk_css_provider_to_string(Pointer provider);
/**
* Gets the foreground color for a given state.
*
* @since 3.0
*/
public static native
void gtk_style_context_get_color(Pointer context, int stateFlags, Pointer color);
/**
* Returns the state used for style matching.
*
* @since 3.0
*/
public static native
int gtk_style_context_get_state(Pointer context);
/**
* Looks up and resolves a color name in the context color map.
*
* @since 3.0 (but not in the documentation...)
*/
public static native
boolean gtk_style_context_lookup_color(Pointer widget, String name, Pointer color);
/**
* Returns the style context associated to widget . The returned object is guaranteed to be the same for the lifetime of widget .
*
* @since 3.0 (but not in the documentation...)
*/
public static native
Pointer gtk_widget_get_style_context(Pointer widget);
/**
* Saves the context state, so temporary modifications done through gtk_style_context_add_class(), gtk_style_context_remove_class(),
* gtk_style_context_set_state(), etc. can quickly be reverted in one go through gtk_style_context_restore().
* <p>
* The matching call to gtk_style_context_restore() must be done before GTK returns to the main loop.
*
* @since 3.0
*/
public static native
void gtk_style_context_save(Pointer context);
/**
* Restores context state to a previous stage. See gtk_style_context_save().
*
* @since 3.0
*/
public static native
void gtk_style_context_restore(Pointer context);
/**
* Adds a style class to context , so posterior calls to gtk_style_context_get() or any of the gtk_render_*() functions will make
* use of this new class for styling.
*
* @since 3.0
*/
public static native
void gtk_style_context_add_class(Pointer context, String className);
/**
* Gets the padding for a given state as a GtkBorder. See gtk_style_context_get() and GTK_STYLE_PROPERTY_PADDING for details.
*
* @since 3.0
*/
public static native
void gtk_style_context_get_padding(Pointer context, int state, Pointer border);
/**
* Gets the border for a given state as a GtkBorder.
* <p>
* See gtk_style_context_get_property() and GTK_STYLE_PROPERTY_BORDER_WIDTH for details.
*
* @since 3.0
*/
public static native
void gtk_style_context_get_border(Pointer context, int state, Pointer border);
/**
* Returns the internal scale factor that maps from window coordinates to the actual device pixels. On traditional systems this is 1,
* but on very high density outputs this can be a higher value (often 2).
* <p>
* A higher value means that drawing is automatically scaled up to a higher resolution, so any code doing drawing will automatically
* look nicer. However, if you are supplying pixel-based data the scale value can be used to determine whether to use a pixel
* resource with higher resolution data.
* <p>
* The scale of a window may change during runtime, if this happens a configure event will be sent to the toplevel window.
*
* @return the scale factor
*
* @since 3.10
*/
public static native
int gdk_window_get_scale_factor(Pointer window);
/**
* Retrieves the minimum and natural size of a widget, taking into account the widgets preference for height-for-width management.
* <p>
* This is used to retrieve a suitable size by container widgets which do not impose any restrictions on the child placement.
* It can be used to deduce toplevel window and menu sizes as well as child widgets in free-form containers such as GtkLayout.
* <p>
* Handle with care. Note that the natural height of a height-for-width widget will generally be a smaller size than the minimum
* height, since the required height for the natural width is generally smaller than the required height for the minimum width.
* <p>
* Use gtk_widget_get_preferred_height_and_baseline_for_width() if you want to support baseline alignment.
*
* @param widget a GtkWidget instance
* @param minimum_size location for storing the minimum size, or NULL.
* @param natural_size location for storing the natural size, or NULL.
*/
public static native
void gtk_widget_get_preferred_size(final Pointer widget, final Pointer minimum_size, final Pointer natural_size);
}

View File

@ -1,351 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import static dorkbox.systemTray.SystemTray.logger;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.sun.jna.Pointer;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.Swt;
public
class GtkEventDispatch {
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>();
// This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread
private static ThreadLocal<Boolean> isDispatch = new ThreadLocal<Boolean>() {
@Override
protected
Boolean initialValue() {
return false;
}
};
private static volatile boolean started = false;
@SuppressWarnings("FieldCanBeLocal")
private static Thread gtkUpdateThread = null;
// when debugging the EDT, we need a longer timeout.
private static final boolean debugEDT = true;
// timeout is in seconds
private static final int TIMEOUT = debugEDT ? 10000000 : 2;
public static
void startGui() {
// only permit one startup per JVM instance
if (!started) {
started = true;
// startup the GTK GUI event loop. There can be multiple/nested loops.
if (!Gtk.alreadyRunningGTK) {
// If JavaFX/SWT is used, this is UNNECESSARY (we can detect if the GTK main_loop is running)
gtkUpdateThread = new Thread() {
@Override
public
void run() {
Glib.GLogFunc orig = null;
if (SystemTray.DEBUG) {
logger.debug("Running GTK Native Event Loop");
} else {
// NOTE: This can output warnings, so we suppress them
orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
}
// prep for the event loop.
// GThread.g_thread_init(null); would be needed for g_idle_add()
if (!Gtk2.gtk_init_check(0)) {
if (SystemTray.DEBUG) {
logger.error("Error starting GTK");
}
return;
}
// gdk_threads_enter(); would be needed for g_idle_add()
if (orig != null) {
Glib.g_log_set_default_handler(orig, null);
}
// blocks unit quit
Gtk2.gtk_main();
// clean up threads
// gdk_threads_leave(); would be needed for g_idle_add()
}
};
gtkUpdateThread.setDaemon(false); // explicitly NOT daemon so that this will hold the JVM open as necessary
gtkUpdateThread.setName("GTK Native Event Loop");
gtkUpdateThread.start();
}
}
}
/**
* Waits for the all posted events to GTK to finish loading
*/
@SuppressWarnings("Duplicates")
public static
void waitForEventsToComplete() {
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
dispatch(new Runnable() {
@Override
public
void run() {
blockUntilStarted.countDown();
}
});
if (SystemTray.isJavaFxLoaded) {
if (!JavaFX.isEventThread()) {
try {
if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.",
new Exception(""));
}
}
// we have to WAIT until all events are done processing, OTHERWISE we have initialization issues
while (true) {
Thread.sleep(100);
synchronized (gtkCallbacks) {
if (gtkCallbacks.isEmpty()) {
break;
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
else if (SystemTray.isSwtLoaded) {
if (!Swt.isEventThread()) {
// we have to WAIT until all events are done processing, OTHERWISE we have initialization issues
try {
if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.",
new Exception(""));
}
}
while (true) {
Thread.sleep(100);
synchronized (gtkCallbacks) {
if (gtkCallbacks.isEmpty()) {
break;
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
else {
try {
if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.",
new Exception(""));
}
}
// we have to WAIT until all events are done processing, OTHERWISE we have initialization issues
while (true) {
Thread.sleep(100);
synchronized (gtkCallbacks) {
if (gtkCallbacks.isEmpty()) {
break;
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that.
*/
public static
void dispatch(final Runnable runnable) {
if (Gtk.alreadyRunningGTK) {
if (SystemTray.isJavaFxLoaded) {
// JavaFX only
if (JavaFX.isEventThread()) {
// Run directly on the JavaFX event thread
runnable.run();
}
else {
JavaFX.dispatch(runnable);
}
return;
}
if (SystemTray.isSwtLoaded) {
if (Swt.isEventThread()) {
// Run directly on the SWT event thread. If it's not on the dispatch thread, we can use raw GTK to put it there
runnable.run();
return;
}
}
}
// not javafx
// gtk/swt are **mostly** the same in how events are dispatched, so we can use "raw" gtk methods for SWT
if (isDispatch.get()) {
// Run directly on the dispatch thread
runnable.run();
}
else {
final FuncCallback callback = new FuncCallback() {
@Override
public
int callback(final Pointer data) {
synchronized (gtkCallbacks) {
gtkCallbacks.removeFirst(); // now that we've 'handled' it, we can remove it from our callback list
}
isDispatch.set(true);
try {
runnable.run();
} finally {
isDispatch.set(false);
}
return Gtk.FALSE; // don't want to call this again
}
};
synchronized (gtkCallbacks) {
gtkCallbacks.offer(callback); // prevent GC from collecting this object before it can be called
}
// the correct way to do it. Add with a slightly higher value
Gtk2.gdk_threads_add_idle_full(100, callback, null, null);
}
}
public static
void shutdownGui() {
dispatchAndWait(new Runnable() {
@Override
public
void run() {
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown)
if (!Gtk.alreadyRunningGTK) {
Gtk2.gtk_main_quit();
}
started = false;
}
});
}
public static
void dispatchAndWait(final Runnable runnable) {
if (isDispatch.get()) {
// Run directly on the dispatch thread (should not "redispatch" this again)
runnable.run();
}
else {
final CountDownLatch countDownLatch = new CountDownLatch(1);
dispatch(new Runnable() {
@Override
public
void run() {
try {
runnable.run();
} catch (Exception e) {
SystemTray.logger.error("Error during GTK run loop: ", e);
} finally {
countDownLatch.countDown();
}
}
});
// this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI
// thread occur in REASONABLE time-frames, and alert the user if not.
try {
if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) {
if (SystemTray.DEBUG) {
SystemTray.logger.error(
"Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT + " seconds " +
"to complete.", new Exception(""));
}
else {
throw new RuntimeException("Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT +
" seconds " + "to complete.");
}
}
} catch (InterruptedException e) {
SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception(""));
}
}
}
/**
* required to properly setup the dispatch flag when using native menus
*
* @param callback will never be null.
*/
public static
void proxyClick(final Entry menuEntry, final ActionListener callback) {
isDispatch.set(true);
try {
if (menuEntry != null) {
callback.actionPerformed(new ActionEvent(menuEntry, ActionEvent.ACTION_PERFORMED, ""));
}
else {
// checkbox entries will not pass the menuEntry in, because they redispatch the click event so that the checkbox state is
// toggled
callback.actionPerformed(null);
}
} finally {
isDispatch.set(false);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class AppIndicatorInstanceStruct extends Structure {
public GObjectStruct parent;
public Pointer priv;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("parent", "priv");
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GObjectStruct extends Structure {
public
class ByValue extends GObjectStruct implements Structure.ByValue {}
public
class ByReference extends GObjectStruct implements Structure.ByReference {}
public GTypeInstanceStruct g_type_instance;
public int ref_count;
public Pointer qdata;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("g_type_instance", "ref_count", "qdata");
}
}

View File

@ -1,48 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GParamSpecStruct extends Structure {
public
class ByValue extends GParamSpecStruct implements Structure.ByValue {}
public
class ByReference extends GParamSpecStruct implements Structure.ByReference {}
public GTypeInstanceStruct g_type_instance;
public String name; /* interned string */
// Pointer flags;
// double value_type;
// double owner_type; /* class or interface using this property */
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("g_type_instance", "name");
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GTypeInstanceStruct extends Structure {
public
class ByValue extends GTypeInstanceStruct implements Structure.ByValue {}
public
class ByReference extends GTypeInstanceStruct implements Structure.ByReference {}
public Pointer g_class;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("g_class");
}
}

View File

@ -1,88 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.awt.Color;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gnome.org/gdk3/stable/gdk3-Colors.html
*
* GdkColor has been deprecated since version 3.14 and should not be used in newly-written code.
*/
public
class GdkColor extends Structure {
/* The color type.
* A color consists of red, green and blue values in the
* range 0-65535 and a pixel value. The pixel value is highly
* dependent on the depth and colormap which this color will
* be used to draw into. Therefore, sharing colors between
* colormaps is a bad idea.
*/
public int pixel;
public short red;
public short green;
public short blue;
/**
* Convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255
*/
private static int convert(int inputColor) {
return (inputColor & 0x0000FFFF >> 8) & 0xFF;
}
public int red() {
return convert(red);
}
public int green() {
return convert(green);
}
public int blue() {
return convert(blue);
}
public
Color getColor() {
read(); // have to read the struct members first!
return new Color(red(), green(), blue());
}
@Override
public
String toString() {
return "[r=" + red() + ",g=" + green() + ",b=" + blue() + "]";
}
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("pixel", "red", "green", "blue");
}
public
class ByValue extends GdkColor implements Structure.ByValue {}
public static
class ByReference extends GdkColor implements Structure.ByReference {}
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GdkEventButton extends Structure {
public int type;
public Pointer window;
public int send_event;
public int time;
public double x;
public double y;
public Pointer axes;
public int state;
public int button;
public Pointer device;
public double x_root;
public double y_root;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root");
}
}

View File

@ -1,68 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.awt.Color;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gnome.org/gdk3/stable/gdk3-RGBA-Colors.html#GdkRGBA
*/
public
class GdkRGBAColor extends Structure {
// these are from 0.0 to 1.0 inclusive
public double red;
public double green;
public double blue;
public double alpha;
public float red() {
return (float) red;
}
public float green() {
return (float) green;
}
public float blue() {
return (float) blue;
}
public
Color getColor() {
read(); // have to read the struct members first!
return new Color(red(), green(), blue());
}
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("red", "green", "blue", "alpha");
}
public
class ByValue extends GdkRGBAColor implements Structure.ByValue {}
public
class ByReference extends GdkRGBAColor implements Structure.ByReference {}
}

View File

@ -1,37 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gimp.org/api/2.0/gtk/GtkWidget.html#GtkRequisition
*/
public
class GtkRequisition extends Structure {
public int width;
public int height;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("width", "height");
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GtkStyle extends Structure {
/*
* There are several 'directives' to change the attributes of a widget.
* fg - Sets the foreground color of a widget.
* bg - Sets the background color of a widget.
* text - Sets the foreground color for widgets that have editable text.
* base - Sets the background color for widgets that have editable text.
* bg_pixmap - Sets the background of a widget to a tiled pixmap.
* font_name - Sets the font to be used with the given widget.
* xthickness - Sets the left and right border width. This is not what you might think; it sets the borders of children(?)
* ythickness - similar to above but for the top and bottom.
*
* There are several states a widget can be in, and you can set different colors, pixmaps and fonts for each state. These states are:
* NORMAL - The normal state of a widget. Ie the mouse is not over it, and it is not being pressed, etc.
* PRELIGHT - When the mouse is over top of the widget, colors defined using this state will be in effect.
* ACTIVE - When the widget is pressed or clicked it will be active, and the attributes assigned by this tag will be in effect.
* INSENSITIVE - This is the state when a widget is 'greyed out'. It is not active, and cannot be clicked on.
* SELECTED - When an object is selected, it takes these attributes.
*/
public static
class ByReference extends GtkStyle implements Structure.ByReference {}
public
class ByValue extends GtkStyle implements Structure.ByValue {}
// only list public fields
/** fg: foreground for drawing GtkLabel */
public GdkColor fg[] = new GdkColor[5];
/** bg: the usual background color, gray by default */
public GdkColor bg[] = new GdkColor[5];
public GdkColor light[] = new GdkColor[5];
public GdkColor dark[] = new GdkColor[5];
public GdkColor mid[] = new GdkColor[5];
/**
* text: text for entries and text widgets (although in GTK 1.2 sometimes fg gets used, this is more or less a bug and fixed in GTK 2.0).
*/
public GdkColor text[] = new GdkColor[5];
/** base: background when using text, colored white in the default theme. */
public GdkColor base[] = new GdkColor[5];
public GdkColor text_aa[] = new GdkColor[5]; /* Halfway between text/base */
public GdkColor black;
public GdkColor white;
public Pointer /*PangoFontDescription*/ font_desc;
public int xthickness;
public int ythickness;
public Pointer /*cairo_pattern_t*/ background[] = new Pointer[5];
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("fg",
"bg",
"light",
"dark",
"mid",
"text",
"base",
"text_aa",
"black",
"white",
"font_desc",
"xthickness",
"ythickness",
"background");
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux.structs;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gnome.org/pango/stable/pango-Glyph-Storage.html#PangoRectangle
*/
public
class PangoRectangle extends Structure {
public int x;
public int y;
public int width;
public int height;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("x", "y", "width", "height");
}
}

View File

@ -1,30 +0,0 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
/**
* Represents a System Tray or menu, that will have it's menu rendered via the native subsystem.
* <p>
* This is does not have as many features as the swing-based UI, however the trade off is that this will always have the native L&F of
* the system (with the exception of Windows, whose native menu looks absolutely terrible).
* <p>
* Noticeable differences that are limitations for the NativeUI only:
* - AppIndicator Status entries must be plain text (they are not bold as they are everywhere else).
* - MacOS cannot have images in their menu or sub-menu's -- only plain text is possible
*/
public
interface NativeUI {}

View File

@ -31,4 +31,6 @@ interface MenuItemPeer extends EntryPeer {
void setCallback(MenuItem menuItem);
void setShortcut(MenuItem menuItem);
void setTooltip(MenuItem menuItem);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2015 dorkbox, llc
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,18 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
package dorkbox.systemTray.peer;
import com.sun.jna.Callback;
import com.sun.jna.Pointer;
import dorkbox.util.Keep;
@Keep
/**
* Internal component used to bind the API to the implementation
*/
public
interface GCallback extends Callback {
/**
* @return Gtk.TRUE if we handled this event
*/
int callback(Pointer instance, Pointer data);
interface SeparatorPeer extends EntryPeer {
}

View File

@ -1,29 +0,0 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
/**
* Represents a System Tray or menu, that will have it's menu rendered via Swing.
* <p>
* This has the most standard L&F across all systems (as all systems will render this menu the exact same way), however the tradeoff is that
* one loses the native L&F of the system (with the exception of Windows, whose native menu looks absolutely terrible).
* <p>
* Noticeable differences that are limitations for the NativeUI only:
* - AppIndicator Status entries must be plain text (they are not bold as they are everywhere else).
* - MacOS cannot have images in their menu or sub-menu's -- only plain text is possible
*/
public
interface SwingUI {}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import java.awt.MenuShortcut;
@ -136,6 +136,12 @@ class AwtMenu implements MenuPeer {
});
}
@Override
public
void setTooltip(final MenuItem menuItem) {
// no op. (awt menus cannot show tooltips)
}
@Override
public
void remove() {

View File

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import java.awt.MenuShortcut;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.peer.MenuItemPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.util.SwingUtil;
class AwtMenuItem implements MenuItemPeer {
@ -74,28 +76,32 @@ class AwtMenuItem implements MenuItemPeer {
_native.removeActionListener(callback);
}
if (menuItem.getCallback() != null) {
callback = menuItem.getCallback(); // can be set to null
if (callback != null) {
callback = new ActionListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
ActionListener cb = menuItem.getCallback();
if (cb != null) {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
}
}
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
}
}
});
}
};
_native.addActionListener(callback);
}
else {
callback = null;
}
}
@Override
@ -114,6 +120,12 @@ class AwtMenuItem implements MenuItemPeer {
});
}
@Override
public
void setTooltip(final MenuItem menuItem) {
// no op. (awt menus cannot show tooltips)
}
@SuppressWarnings("Duplicates")
@Override
public

View File

@ -13,15 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import java.awt.MenuShortcut;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.peer.CheckboxPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.util.SwingUtil;
class AwtMenuItemCheckbox implements CheckboxPeer {
@ -30,7 +33,7 @@ class AwtMenuItemCheckbox implements CheckboxPeer {
private final java.awt.CheckboxMenuItem _native = new java.awt.CheckboxMenuItem();
// these have to be volatile, because they can be changed from any thread
private volatile ActionListener callback;
private volatile ItemListener callback;
private volatile boolean isChecked = false;
// this is ALWAYS called on the EDT.
@ -67,33 +70,39 @@ class AwtMenuItemCheckbox implements CheckboxPeer {
@Override
public
void setCallback(final Checkbox menuItem) {
// of critical note: AWT only works with ItemListener -- but we use ActionListener for everything, so here we make things compatible
if (callback != null) {
_native.removeActionListener(callback);
_native.removeItemListener(callback);
}
callback = menuItem.getCallback(); // can be set to null
ActionListener callback = menuItem.getCallback(); // can be set to null
if (callback != null) {
callback = new ActionListener() {
this.callback = new ItemListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
void itemStateChanged(final ItemEvent e) {
// this will run on the EDT, since we are calling it from the EDT
menuItem.setChecked(!isChecked);
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
ActionListener cb = menuItem.getCallback();
if (cb != null) {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu checkbox entry {} click event.", menuItem.getText(), throwable);
}
}
}
});
}
};
_native.addActionListener(callback);
_native.addItemListener(this.callback);
}
}
@ -144,7 +153,7 @@ class AwtMenuItemCheckbox implements CheckboxPeer {
_native.setEnabled(false);
if (callback != null) {
_native.removeActionListener(callback);
_native.removeItemListener(callback);
callback = null;
}
parent._native.remove(_native);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import dorkbox.systemTray.peer.EntryPeer;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import static java.awt.Font.DIALOG;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.awt;
import java.awt.AWTException;
import java.awt.Image;
@ -42,7 +42,7 @@ import dorkbox.util.SwingUtil;
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public final
class _AwtTray extends Tray implements NativeUI {
class _AwtTray extends Tray {
private volatile SystemTray tray;
private volatile TrayIcon trayIcon;
@ -182,6 +182,31 @@ class _AwtTray extends Tray implements NativeUI {
// no op
}
@Override
public
void setTooltip(final MenuItem menuItem) {
final String text = menuItem.getTooltip();
if (tooltipText != null && tooltipText.equals(text) ||
tooltipText == null && text != null) {
return;
}
tooltipText = text;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(text);
}
}
});
}
@Override
public
void remove() {
@ -209,31 +234,6 @@ class _AwtTray extends Tray implements NativeUI {
bind(awtMenu, null, systemTray);
}
@Override
protected
void setTooltip_(final String tooltipText) {
if (this.tooltipText.equals(tooltipText)){
return;
}
this.tooltipText = tooltipText;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
if (tray == null) {
tray = SystemTray.getSystemTray();
}
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(tooltipText);
}
}
});
}
@Override
public
boolean hasImage() {

View File

@ -13,23 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import java.io.File;
import com.sun.jna.Pointer;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.peer.EntryPeer;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.GtkEventDispatch;
abstract
class GtkBaseMenuItem implements EntryPeer {
// these are necessary BECAUSE GTK menus look funky as hell when there are some menu entries WITH icons and some WITHOUT
private static final File transparentIcon = ImageResizeUtil.getTransparentImage();
private volatile boolean hasLegitImage = true;
private volatile boolean hasLegitImage = false; // default is to not have an image assigned
// these have to be volatile, because they can be changed from any thread
private volatile Pointer spacerImage;
@ -50,6 +51,39 @@ class GtkBaseMenuItem implements EntryPeer {
hasLegitImage = isLegit;
}
/**
* always remove a spacer image.
* <p>
* called on the DISPATCH thread
*/
protected
void removeSpacerImage() {
if (spacerImage != null) {
Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
spacerImage = null;
Gtk2.gtk_widget_show_all(_native);
}
}
/**
* always add a spacer image.
* <p>
* called on the DISPATCH thread
*/
protected
void addSpacerImage() {
if (spacerImage == null) {
spacerImage = Gtk2.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, spacerImage);
// must always re-set always-show after setting the image
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
}
}
/**
* the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images.
* This is primarily only with AppIndicators, although not always.
@ -63,21 +97,13 @@ class GtkBaseMenuItem implements EntryPeer {
return;
}
if (spacerImage != null) {
Gtk.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
spacerImage = null;
Gtk.gtk_widget_show_all(_native);
}
removeSpacerImage();
if (everyoneElseHasImages) {
spacerImage = Gtk.gtk_image_new_from_file(transparentIcon.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(_native, spacerImage);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(_native, true);
addSpacerImage();
}
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
@ -85,7 +111,7 @@ class GtkBaseMenuItem implements EntryPeer {
// always on EDT
void onDeleteMenu(final Pointer parentNative) {
Gobject.g_object_force_floating(_native); // makes it a floating reference
Gtk.gtk_container_remove(parentNative, _native);
Gtk2.gtk_container_remove(parentNative, _native);
}
// some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
@ -95,9 +121,9 @@ class GtkBaseMenuItem implements EntryPeer {
setSpacerImage(hasImagesInMenu);
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
Gtk.gtk_menu_shell_append(parentNative, _native);
Gtk2.gtk_menu_shell_append(parentNative, _native);
Gobject.g_object_ref_sink(_native); // undoes "floating"
Gtk.gtk_widget_show_all(_native); // necessary to guarantee widget is visible
Gtk2.gtk_widget_show_all(_native); // necessary to guarantee widget is visible
}
@Override
@ -108,7 +134,7 @@ class GtkBaseMenuItem implements EntryPeer {
public
void run() {
if (spacerImage != null) {
Gtk.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, spacerImage); // will automatically get destroyed if no other references to it
spacerImage = null;
}
}

View File

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import java.util.ArrayList;
import java.util.List;
@ -27,9 +29,8 @@ import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.peer.MenuPeer;
import dorkbox.util.jna.linux.GtkEventDispatch;
@SuppressWarnings("deprecation")
class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@ -61,7 +62,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@SuppressWarnings("IncompleteCopyConstructor")
private
GtkMenu(final GtkMenu parent) {
super(Gtk.gtk_image_menu_item_new_with_mnemonic("")); // is what is added to the parent menu (so images work)
super(Gtk2.gtk_image_menu_item_new_with_mnemonic("")); // is what is added to the parent menu (so images work)
this.parent = parent;
}
@ -102,7 +103,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
menuEntry__.onDeleteMenu(_nativeMenu);
}
Gtk.gtk_widget_destroy(_nativeMenu);
Gtk2.gtk_widget_destroy(_nativeMenu);
}
if (parent != null) {
@ -110,11 +111,11 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
}
// makes a new one
_nativeMenu = Gtk.gtk_menu_new();
_nativeMenu = Gtk2.gtk_menu_new();
// binds sub-menu to entry (if it exists! it does not for the root menu)
if (parent != null) {
Gtk.gtk_menu_item_set_submenu(_native, _nativeMenu);
Gtk2.gtk_menu_item_set_submenu(_native, _nativeMenu);
}
}
@ -158,8 +159,8 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
}
}
Gtk.gtk_widget_show_all(_nativeMenu); // necessary to guarantee widget is visible (doesn't always show_all for all children)
onMenuAdded(_nativeMenu);
Gtk2.gtk_widget_show_all(_nativeMenu); // necessary to guarantee widget is visible (doesn't always show_all for all children)
}
/**
@ -186,7 +187,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
}
menuEntriesCopy.clear();
Gtk.gtk_widget_destroy(_nativeMenu);
Gtk2.gtk_widget_destroy(_nativeMenu);
_nativeMenu = null;
obliterateInProgress.set(false);
@ -196,7 +197,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@Override
public
void add(final Menu parentMenu, final Entry entry, final int index) {
// must always be called on the GTK dispatch. This must be dispatchAndWait
// must always be called on the GTK dispatch. This must be dispatchAndWait() so it will properly executed immediately
GtkEventDispatch.dispatchAndWait(new Runnable() {
@Override
public
@ -205,35 +206,50 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
// To work around this issue, we destroy then recreate the menu every time something is changed.
deleteMenu();
GtkBaseMenuItem item = null;
if (entry instanceof Menu) {
// 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
GtkMenu item = new GtkMenu(GtkMenu.this);
item = new GtkMenu(GtkMenu.this);
menuEntries.add(index, item);
((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
GtkMenuItemSeparator item = new GtkMenuItemSeparator(GtkMenu.this);
item = new GtkMenuItemSeparator(GtkMenu.this);
menuEntries.add(index, item);
entry.bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
GtkMenuItemCheckbox item = new GtkMenuItemCheckbox(GtkMenu.this);
item = new GtkMenuItemCheckbox(GtkMenu.this);
menuEntries.add(index, item);
((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
GtkMenuItemStatus item = new GtkMenuItemStatus(GtkMenu.this);
item = new GtkMenuItemStatus(GtkMenu.this);
menuEntries.add(index, item);
((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
GtkMenuItem item = new GtkMenuItem(GtkMenu.this);
item = new GtkMenuItem(GtkMenu.this);
menuEntries.add(index, item);
((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
createMenu();
// we must create the menu BEFORE binding the menu, otherwise the menus' children's GTK element can be added before
// their parent GTK elements are added (and the menu won't show up)
if (entry instanceof Menu) {
((Menu) entry).bind((GtkMenu) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
((Separator)entry).bind((GtkMenuItemSeparator) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
((Checkbox) entry).bind((GtkMenuItemCheckbox) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
((Status) entry).bind((GtkMenuItemStatus) item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
((MenuItem) entry).bind((GtkMenuItem) item, parentMenu, parentMenu.getSystemTray());
}
}
});
}
@ -256,21 +272,20 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
public
void run() {
if (image != null) {
Gtk.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
if (menuItem.getImage() != null) {
image = Gtk.gtk_image_new_from_file(menuItem.getImage()
.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(_native, image);
image = Gtk2.gtk_image_new_from_file(menuItem.getImage().getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(_native, true);
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
}
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
});
}
@ -284,7 +299,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@Override
public
void run() {
Gtk.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
Gtk2.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
}
});
}
@ -323,8 +338,8 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
@Override
public
void run() {
Gtk.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk2.gtk_widget_show_all(_native);
}
});
}
@ -345,6 +360,20 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
setText(menuItem);
}
@Override
public
void setTooltip(final MenuItem menuItem) {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
// NOTE: this will not work for AppIndicator tray types!
// null will remove the tooltip
Gtk2.gtk_widget_set_tooltip_text(_native, menuItem.getTooltip());
}
});
}
/**
* called when a child removes itself from the parent menu. Does not work for sub-menus
*
@ -379,7 +408,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
if (parent != null) {
// remove the gtk entry item from our menu NATIVE components
Gtk.gtk_menu_item_set_submenu(_native, null);
Gtk2.gtk_menu_item_set_submenu(_native, null);
// have to rebuild the menu now...
parent.deleteMenu(); // must be on EDT

View File

@ -13,25 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import com.sun.jna.Pointer;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.linux.GCallback;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.peer.MenuItemPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.util.jna.linux.GCallback;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.GtkEventDispatch;
class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
private final GtkMenu parent;
// these have to be volatile, because they can be changed from any thread
private volatile MenuItem menuItemForActionCallback;
private volatile ActionListener callback;
private volatile Pointer image;
// The mnemonic will ONLY show-up once a menu entry is selected. IT WILL NOT show up before then!
@ -44,7 +47,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuItem(final GtkMenu parent) {
super(Gtk.gtk_image_menu_item_new_with_mnemonic(""));
super(Gtk2.gtk_image_menu_item_new_with_mnemonic(""));
this.parent = parent;
Gobject.g_signal_connect_object(_native, "activate", this, null, 0);
@ -55,18 +58,12 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
int callback(final Pointer instance, final Pointer data) {
if (menuItemForActionCallback != null) {
final ActionListener cb = menuItemForActionCallback.getCallback();
if (cb != null) {
try {
GtkEventDispatch.proxyClick(menuItemForActionCallback, cb);
} catch (Exception e) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItemForActionCallback.getText(), e);
}
}
ActionListener callback = this.callback;
if (callback != null) {
GtkEventDispatch.proxyClick(callback);
}
return Gtk.TRUE;
return Gtk2.TRUE;
}
// NOTE: XFCE used to use appindicator3, which DOES NOT support images in the menu. This change was reverted.
@ -76,6 +73,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void setImage(final MenuItem menuItem) {
final boolean hadImage = hasImage();
setLegitImage(menuItem.getImage() != null);
GtkEventDispatch.dispatch(new Runnable() {
@ -83,21 +81,29 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
public
void run() {
if (image != null) {
Gtk.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
if (menuItem.getImage() != null) {
image = Gtk.gtk_image_new_from_file(menuItem.getImage()
.getAbsolutePath());
Gtk.gtk_image_menu_item_set_image(_native, image);
// always remove the spacer image in case it's there. The spacer image will correctly added when the menu is created.
removeSpacerImage();
image = Gtk2.gtk_image_new_from_file(menuItem.getImage()
.getAbsolutePath());
Gtk2.gtk_image_menu_item_set_image(_native, image);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(_native, true);
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
}
else if (hadImage) {
// if at one point, we had an image, we should set the spacer image back, so that menu spacing looks correct.
// since we USED to have an image, it is safe to assume that we should have a spacer image.
addSpacerImage();
}
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
});
}
@ -109,7 +115,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void run() {
Gtk.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
Gtk2.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
}
});
}
@ -146,16 +152,40 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void run() {
Gtk.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk2.gtk_widget_show_all(_native);
}
});
}
@SuppressWarnings("Duplicates")
@Override
public
void setCallback(final MenuItem menuItem) {
this.menuItemForActionCallback = menuItem;
callback = menuItem.getCallback(); // can be set to null
if (callback != null) {
callback = new ActionListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
}
}
});
}
};
}
}
@Override
@ -166,6 +196,20 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
setText(menuItem);
}
@Override
public
void setTooltip(final MenuItem menuItem) {
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
// NOTE: this will not work for AppIndicator tray types!
// null will remove the tooltip
Gtk2.gtk_widget_set_tooltip_text(_native, menuItem.getTooltip());
}
});
}
@SuppressWarnings("Duplicates")
@Override
public
@ -174,13 +218,14 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
@Override
public
void run() {
Gtk.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItem.super.remove();
menuItemForActionCallback = null;
callback = null;
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
if (image != null) {
Gtk.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
}

View File

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import java.awt.Color;
import java.awt.Rectangle;
@ -24,15 +26,15 @@ import com.sun.jna.Pointer;
import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.linux.GCallback;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.jna.linux.GtkTheme;
import dorkbox.systemTray.peer.CheckboxPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.systemTray.util.HeavyCheckMark;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.OSUtil;
import dorkbox.util.jna.linux.GCallback;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.GtkEventDispatch;
import dorkbox.util.jna.linux.GtkTheme;
@SuppressWarnings("deprecation")
class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCallback {
@ -51,7 +53,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
(SystemTray.get().getMenu() instanceof _AppIndicatorNativeTray) && OSUtil.Linux.isUbuntu()) {
useFakeCheckMark = true;
} else {
useFakeCheckMark = true;
useFakeCheckMark = false;
}
if (SystemTray.DEBUG) {
@ -91,14 +93,20 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
* show this as a GTK Status Icon (not an AppIndicator), this way the "proper" checkbox is shown.
*/
GtkMenuItemCheckbox(final GtkMenu parent) {
super(useFakeCheckMark ? Gtk.gtk_image_menu_item_new_with_mnemonic("") : Gtk.gtk_check_menu_item_new_with_mnemonic(""));
super(useFakeCheckMark ? Gtk2.gtk_image_menu_item_new_with_mnemonic("") : Gtk2.gtk_check_menu_item_new_with_mnemonic(""));
this.parent = parent;
handlerId = Gobject.g_signal_connect_object(_native, "activate", this, null, 0);
if (useFakeCheckMark) {
if (checkedFile == null) {
final Color color = GtkTheme.getTextColor();
Color color = GtkTheme.getTextColor();
if (color == null) {
SystemTray.logger.error("Unable to determine the text color in use by your system. Please create an issue and include your " +
"full OS configuration and desktop environment, including theme details, such as the theme name, color " +
"variant, and custom theme options (if any).");
color = Color.BLACK;
}
if (checkedFile == null) {
Rectangle size = GtkTheme.getPixelTextHeight("X");
@ -117,7 +125,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
setCheckedIconForFakeCheckMarks();
} else {
Gobject.g_signal_handler_block(_native, handlerId);
Gtk.gtk_check_menu_item_set_active(_native, false);
Gtk2.gtk_check_menu_item_set_active(_native, false);
Gobject.g_signal_handler_unblock(_native, handlerId);
}
}
@ -126,12 +134,12 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
@Override
public
int callback(final Pointer instance, final Pointer data) {
ActionListener callback = this.callback;
if (callback != null) {
// this will redispatch to our created callback via `setCallback`
GtkEventDispatch.proxyClick(null, callback);
GtkEventDispatch.proxyClick(callback);
}
return Gtk.TRUE;
return Gtk2.TRUE;
}
@Override
@ -153,7 +161,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
@Override
public
void run() {
Gtk.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
Gtk2.gtk_widget_set_sensitive(_native, menuItem.getEnabled());
}
});
}
@ -185,8 +193,8 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
@Override
public
void run() {
Gtk.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_menu_item_set_label(_native, textWithMnemonic);
Gtk2.gtk_widget_show_all(_native);
}
});
}
@ -199,21 +207,26 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
if (callback != null) {
callback = new ActionListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
// this will run on the EDT, since we are calling it from the EDT. This can ALSO recursively call the callback
menuItem.setChecked(!isChecked);
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
ActionListener cb = menuItem.getCallback();
if (cb != null) {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu checkbox entry {} click event.", menuItem.getText(), throwable);
}
}
}
});
}
};
}
@ -240,7 +253,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
// https://github.com/GNOME/gtk/blob/master/gtk/gtkcheckmenuitem.c#L317
// this disables the signal handler, then enables it
Gobject.g_signal_handler_block(_native, handlerId);
Gtk.gtk_check_menu_item_set_active(_native, isChecked);
Gtk2.gtk_check_menu_item_set_active(_native, isChecked);
Gobject.g_signal_handler_unblock(_native, handlerId);
}
}
@ -252,24 +265,24 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
private
void setCheckedIconForFakeCheckMarks() {
if (checkedImage != null) {
Gtk.gtk_container_remove(_native, checkedImage); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, checkedImage); // will automatically get destroyed if no other references to it
checkedImage = null;
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
if (this.isChecked) {
checkedImage = Gtk.gtk_image_new_from_file(checkedFile);
checkedImage = Gtk2.gtk_image_new_from_file(checkedFile);
} else {
checkedImage = Gtk.gtk_image_new_from_file(uncheckedFile);
checkedImage = Gtk2.gtk_image_new_from_file(uncheckedFile);
}
Gtk.gtk_image_menu_item_set_image(_native, checkedImage);
Gtk2.gtk_image_menu_item_set_image(_native, checkedImage);
// must always re-set always-show after setting the image
Gtk.gtk_image_menu_item_set_always_show_image(_native, true);
Gtk2.gtk_image_menu_item_set_always_show_image(_native, true);
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_widget_show_all(_native);
}
@Override
@ -288,12 +301,14 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
@Override
public
void run() {
Gtk.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItemCheckbox.super.remove();
callback = null;
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
if (image != null) {
Gtk.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(_native, image); // will automatically get destroyed if no other references to it
image = null;
}

View File

@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.peer.EntryPeer;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
import dorkbox.systemTray.peer.SeparatorPeer;
import dorkbox.util.jna.linux.GtkEventDispatch;
class GtkMenuItemSeparator extends GtkBaseMenuItem implements SeparatorPeer {
private final GtkMenu parent;
@ -28,7 +29,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuItemSeparator(final GtkMenu parent) {
super(Gtk.gtk_separator_menu_item_new());
super(Gtk2.gtk_separator_menu_item_new());
this.parent = parent;
}
@ -40,7 +41,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
@Override
public
void run() {
Gtk.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
parent.remove(GtkMenuItemSeparator.this);
}

View File

@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.peer.StatusPeer;
import dorkbox.util.jna.linux.GtkEventDispatch;
// you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else,
// where a GtkStatusIconTray + SwingUI will have everything lined up. (with or without icons). This is to normalize how it looks
@ -31,7 +32,7 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer {
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*/
GtkMenuItemStatus(final GtkMenu parent) {
super(Gtk.gtk_image_menu_item_new_with_mnemonic(""));
super(Gtk2.gtk_image_menu_item_new_with_mnemonic(""));
this.parent = parent;
// need that extra space so it matches windows/mac
@ -48,10 +49,10 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer {
// AppIndicator strips out markup text.
// https://mail.gnome.org/archives/commits-list/2016-March/msg05444.html
Gtk.gtk_menu_item_set_label(_native, menuItem.getText());
Gtk.gtk_widget_show_all(_native);
Gtk2.gtk_menu_item_set_label(_native, menuItem.getText());
Gtk2.gtk_widget_show_all(_native);
Gtk.gtk_widget_set_sensitive(_native, false);
Gtk2.gtk_widget_set_sensitive(_native, false);
}
});
}
@ -64,10 +65,10 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer {
@Override
public
void run() {
Gtk.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
GtkMenuItemStatus.super.remove();
Gtk2.gtk_container_remove(parent._nativeMenu, _native); // will automatically get destroyed if no other references to it
parent.remove(GtkMenuItemStatus.this);
}
});

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
@ -24,11 +24,11 @@ import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.jna.linux.AppIndicator;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.jna.linux.structs.AppIndicatorInstanceStruct;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.jna.linux.AppIndicator;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.GtkEventDispatch;
import dorkbox.util.jna.linux.structs.AppIndicatorInstanceStruct;
/**
* Class for handling all system tray interactions.
@ -77,7 +77,7 @@ import dorkbox.systemTray.util.ImageResizeUtil;
*/
@SuppressWarnings("Duplicates")
public final
class _AppIndicatorNativeTray extends Tray implements NativeUI {
class _AppIndicatorNativeTray extends Tray {
private volatile AppIndicatorInstanceStruct appIndicator;
private boolean isActive = false;
@ -91,7 +91,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
// has the name already been set for the indicator?
private volatile boolean setName = false;
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// appindicators DO NOT support anything other than PLAIN gtk-menus
// they ALSO do not support tooltips!!
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
@ -110,7 +110,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
protected final
void onMenuAdded(final Pointer menu) {
// see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
AppIndicator.app_indicator_set_menu(appIndicator, menu);
appIndicator.app_indicator_set_menu(menu);
if (!setName) {
setName = true;
@ -126,7 +126,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
// in extension.js, so don't change it
// additionally, this is required to be set HERE (not somewhere else)
AppIndicator.app_indicator_set_title(appIndicator, Extension.DEFAULT_NAME);
appIndicator.app_indicator_set_title(Extension.DEFAULT_NAME);
}
}
@ -141,11 +141,11 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
if (visible && !enabled) {
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
appIndicator.app_indicator_set_status(AppIndicator.STATUS_PASSIVE);
visible = false;
}
else if (!visible && enabled) {
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
appIndicator.app_indicator_set_status(AppIndicator.STATUS_ACTIVE);
visible = true;
}
}
@ -164,11 +164,11 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
@Override
public
void run() {
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
appIndicator.app_indicator_set_icon(imageFile.getAbsolutePath());
if (!isActive) {
isActive = true;
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
appIndicator.app_indicator_set_status(AppIndicator.STATUS_ACTIVE);
}
}
});
@ -186,30 +186,35 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
// no op.
}
@Override
public
void setTooltip(final MenuItem menuItem) {
// no op. see https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
}
@Override
public
void remove() {
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
if (!shuttingDown.getAndSet(true)) {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
super.remove();
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
savedAppIndicator.app_indicator_set_status(AppIndicator.STATUS_PASSIVE);
Pointer p = savedAppIndicator.getPointer();
Gobject.g_object_unref(p);
GtkEventDispatch.shutdownGui();
}
});
super.remove();
// does not need to be called on the dispatch (it does that)
GtkEventDispatch.shutdownGui();
}
}
};
@ -231,12 +236,6 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
bind(gtkMenu, null, systemTray);
}
@Override
protected
void setTooltip_(final String tooltipText) {
// https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
}
@Override
public
boolean hasImage() {

View File

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.nativeUI;
package dorkbox.systemTray.ui.gtk;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
@ -24,11 +26,11 @@ import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.jna.linux.GEventCallback;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.systemTray.jna.linux.structs.GdkEventButton;
import dorkbox.util.JavaFX;
import dorkbox.util.jna.linux.GEventCallback;
import dorkbox.util.jna.linux.Gobject;
import dorkbox.util.jna.linux.GtkEventDispatch;
import dorkbox.util.jna.linux.structs.GdkEventButton;
/**
* Class for handling all system tray interactions via GTK.
@ -37,7 +39,7 @@ import dorkbox.systemTray.jna.linux.structs.GdkEventButton;
*/
@SuppressWarnings("Duplicates")
public final
class _GtkStatusIconNativeTray extends Tray implements NativeUI {
class _GtkStatusIconNativeTray extends Tray {
private volatile Pointer trayIcon;
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
@ -76,11 +78,11 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
Gtk2.gtk_status_icon_set_visible(trayIcon, enabled);
visible = false;
}
else if (!visible && enabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
Gtk2.gtk_status_icon_set_visible(trayIcon, enabled);
visible = true;
}
}
@ -99,11 +101,11 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
@Override
public
void run() {
Gtk.gtk_status_icon_set_from_file(trayIcon, imageFile.getAbsolutePath());
Gtk2.gtk_status_icon_set_from_file(trayIcon, imageFile.getAbsolutePath());
if (!isActive) {
isActive = true;
Gtk.gtk_status_icon_set_visible(trayIcon, true);
Gtk2.gtk_status_icon_set_visible(trayIcon, true);
}
}
});
@ -121,6 +123,27 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
// no op
}
@Override
public
void setTooltip(final MenuItem menuItem) {
final String text = menuItem.getTooltip();
if (tooltipText != null && tooltipText.equals(text) ||
tooltipText == null && text != null) {
return;
}
tooltipText = text;
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
Gtk2.gtk_status_icon_set_tooltip_text(trayIcon, text);
}
});
}
@Override
public
void remove() {
@ -131,7 +154,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
public
void run() {
// this hides the indicator
Gtk.gtk_status_icon_set_visible(trayIcon, false);
Gtk2.gtk_status_icon_set_visible(trayIcon, false);
Gobject.g_object_unref(trayIcon);
// mark for GC
@ -152,7 +175,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
@Override
public
void run() {
trayIcon = Gtk.gtk_status_icon_new();
trayIcon = Gtk2.gtk_status_icon_new();
gtkCallback = new GEventCallback() {
@Override
@ -161,8 +184,8 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
// show the swing menu on the EDT
// BUTTON_PRESS only (any mouse click)
if (event.type == 4) {
Gtk.gtk_menu_popup(gtkMenu._nativeMenu, null, null, Gtk.gtk_status_icon_position_menu,
trayIcon, 0, event.time);
Gtk2.gtk_menu_popup(gtkMenu._nativeMenu, null, null, Gtk2.gtk_status_icon_position_menu,
trayIcon, 0, event.time);
}
}
};
@ -182,7 +205,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
// necessary for gnome icon detection/placement because we move tray icons around by title. This is hardcoded
// in extension.js, so don't change it
Gtk.gtk_status_icon_set_title(trayIcon, Extension.DEFAULT_NAME);
Gtk2.gtk_status_icon_set_title(trayIcon, Extension.DEFAULT_NAME);
// can cause
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
@ -192,33 +215,18 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
// BUT this is REQUIRED when running JavaFX or Gnome For unknown reasons, the title isn't pushed to GTK, so our
// gnome-shell extension cannot see our tray icon -- so naturally, it won't move it to the "top" area and
// we appear broken.
if (SystemTray.isJavaFxLoaded || Tray.usingGnome) {
Gtk.gtk_status_icon_set_name(trayIcon, Extension.DEFAULT_NAME);
if (JavaFX.isLoaded || Tray.usingGnome) {
Gtk2.gtk_status_icon_set_name(trayIcon, Extension.DEFAULT_NAME);
}
}
});
bind(gtkMenu, null, systemTray);
// do we need to install the GNOME extension??
Tray.installExtension();
}
@Override
protected
void setTooltip_(final String tooltipText) {
if (this.tooltipText.equals(tooltipText)){
return;
// install the Gnome extension
if (Tray.usingGnome) {
Extension.install();
}
this.tooltipText = tooltipText;
GtkEventDispatch.dispatch(new Runnable() {
@Override
public
void run() {
Gtk.gtk_status_icon_set_tooltip_text(trayIcon, tooltipText);
}
});
}
@Override

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.io.File;
@ -173,6 +173,12 @@ class SwingMenu implements MenuPeer {
});
}
@Override
public
void setTooltip(final MenuItem menuItem) {
}
/**
* This removes all menu entries from this menu AND this menu from it's parent
*/

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -27,6 +27,7 @@ import dorkbox.systemTray.Entry;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.peer.MenuItemPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.SwingUtil;
@ -126,28 +127,32 @@ class SwingMenuItem implements MenuItemPeer {
_native.removeActionListener(callback);
}
if (menuItem.getCallback() != null) {
callback = menuItem.getCallback(); // can be set to null
if (callback != null) {
callback = new ActionListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
ActionListener cb = menuItem.getCallback();
if (cb != null) {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
}
}
}
});
}
};
_native.addActionListener(callback);
}
else {
callback = null;
}
}
@Override
@ -166,6 +171,18 @@ class SwingMenuItem implements MenuItemPeer {
});
}
@Override
public
void setTooltip(final MenuItem menuItem) {
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
_native.setToolTipText(menuItem.getTooltip());
}
});
}
@Override
public
void remove() {

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@ -25,6 +25,7 @@ import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.peer.CheckboxPeer;
import dorkbox.systemTray.util.EventDispatch;
import dorkbox.systemTray.util.HeavyCheckMark;
import dorkbox.util.FontUtil;
import dorkbox.util.SwingUtil;
@ -106,21 +107,26 @@ class SwingMenuItemCheckbox extends SwingMenuItem implements CheckboxPeer {
if (callback != null) {
callback = new ActionListener() {
final ActionListener cb = menuItem.getCallback();
@Override
public
void actionPerformed(ActionEvent e) {
// this will run on the EDT, since we are calling it from the EDT
menuItem.setChecked(!isChecked);
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
ActionListener cb = menuItem.getCallback();
if (cb != null) {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
// we want it to run on our own with our own action event info (so it is consistent across all platforms)
EventDispatch.runLater(new Runnable() {
@Override
public
void run() {
try {
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu checkbox entry {} click event.", menuItem.getText(), throwable);
}
}
}
});
}
};

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import javax.swing.JSeparator;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.Font;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.Color;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.Dimension;
import java.awt.Frame;
@ -89,14 +89,17 @@ class TrayPopup extends JPopupMenu {
hiddenDialog.setBounds(0,0,0,0);
addPopupMenuListener(new PopupMenuListener() {
@Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
@Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
hiddenDialog.setVisible(false);
hiddenDialog.toBack();
}
@Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
});

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.swingUI;
package dorkbox.systemTray.ui.swing;
import java.awt.AWTException;
import java.awt.Image;
@ -28,9 +28,9 @@ import javax.swing.JPopupMenu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
import dorkbox.util.OS;
import dorkbox.util.SwingUtil;
import dorkbox.util.jna.linux.GtkEventDispatch;
/**
* Class for handling all system tray interaction, via Swing.
@ -42,7 +42,7 @@ import dorkbox.util.SwingUtil;
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public final
class _SwingTray extends Tray implements SwingUI {
class _SwingTray extends Tray {
private volatile SystemTray tray;
private volatile TrayIcon trayIcon;
@ -167,6 +167,31 @@ class _SwingTray extends Tray implements SwingUI {
// no op
}
@Override
public
void setTooltip(final MenuItem menuItem) {
final String text = menuItem.getTooltip();
if (tooltipText != null && tooltipText.equals(text) ||
tooltipText == null && text != null) {
return;
}
tooltipText = text;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(text);
}
}
});
}
@Override
public
void remove() {
@ -199,27 +224,6 @@ class _SwingTray extends Tray implements SwingUI {
bind(swingMenu, null, systemTray);
}
@Override
protected
void setTooltip_(final String tooltipText) {
if (this.tooltipText.equals(tooltipText)){
return;
}
this.tooltipText = tooltipText;
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
if (trayIcon != null) {
trayIcon.setToolTip(tooltipText);
}
}
});
}
@Override
public
boolean hasImage() {

View File

@ -1,711 +0,0 @@
package dorkbox.systemTray.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import dorkbox.util.FileUtil;
import dorkbox.util.OS;
/**
* A simple, basic CSS parser
*/
public
class CssParser {
private static final boolean DEBUG = false;
private static final boolean DEBUG_NODES = false;
private static final boolean DEBUG_GETTING_ATTRIBUTE_FROM_NODES = false;
private static final boolean DEBUG_VERBOSE = false;
private static
String trim(String s) {
s = s.replaceAll("\n", "");
s = s.replaceAll("\t", "");
// shrink all whitespace more than 1 space wide.
while (s.contains(" ")) {
s = s.replaceAll(" ", " ");
}
return s.trim();
}
public static
class Css {
List<Entry> colorDefinitions;
List<CssNode> cssNodes;
Css(final List<Entry> colorDefinitions, final List<CssNode> cssNodes) {
this.colorDefinitions = colorDefinitions;
this.cssNodes = cssNodes;
}
@Override
public
String toString() {
StringBuilder def = new StringBuilder();
for (Entry attribute : colorDefinitions) {
def.append(attribute.key)
.append(" : ")
.append(attribute.value)
.append(OS.LINE_SEPARATOR);
}
StringBuilder nodes = new StringBuilder();
for (CssNode node : cssNodes) {
nodes.append(OS.LINE_SEPARATOR)
.append(node.toString())
.append(OS.LINE_SEPARATOR);
}
return nodes.toString() + "\n\n" + nodes.toString();
}
public
String getColorDefinition(final String colorString) {
for (Entry definition : colorDefinitions) {
if (definition.key.equals(colorString)) {
return definition.value;
}
}
return null;
}
}
@SuppressWarnings("WeakerAccess")
public static
class CssNode {
public String label;
public List<Entry> attributes;
CssNode(final String label, final List<Entry> attributes) {
this.label = trim(label);
this.attributes = attributes;
}
@Override
public
String toString() {
StringBuilder builder = new StringBuilder();
for (Entry attribute : attributes) {
builder.append("\t")
.append(attribute.key)
.append(" : ")
.append(attribute.value)
.append(OS.LINE_SEPARATOR);
}
return label + "\n" + builder.toString();
}
}
@SuppressWarnings("WeakerAccess")
public static
class Entry {
public String key;
public String value;
Entry(final String key, final String value) {
this.key = trim(key);
this.value = trim(value);
}
@Override
public
String toString() {
return key + " : " + value;
}
@SuppressWarnings("SimplifiableIfStatement")
@Override
public
boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Entry attribute = (Entry) o;
if (key != null ? !key.equals(attribute.key) : attribute.key != null) {
return false;
}
return value != null ? value.equals(attribute.value) : attribute.value == null;
}
@Override
public
int hashCode() {
int result = key != null ? key.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
}
/**
* Gets the sections of text, of the specified CSS nodes.
*
* @param css the css
* @param nodes the section nodes we are interested in (ie: .menuitem, *)
* @param states the section state we are interested in (ie: focus, hover, active). Null (or empty list) means no state.
*/
public static
List<CssNode> getSections(Css css, String[] nodes, String[] states) {
if (states == null) {
states = new String[0];
}
List<CssNode> sections = new ArrayList<CssNode>(css.cssNodes.size());
// our sections can ONLY contain what we are looking for, as a word.
for (final CssNode section : css.cssNodes) {
String label = section.label;
boolean canSave = false;
if (!section.attributes.isEmpty()) {
main:
for (String node : nodes) {
if (label.equals(node)) {
// exactly what our node is
canSave = true;
break;
}
if (label.length() > node.length() && label.startsWith(node)) {
// a combination of our node + MAYBE some other node
int index = node.length();
label = trim(label.substring(index));
if (label.charAt(0) == '>') {
// if it's an override, we have to check what it overrides.
label = label.substring(1);
}
// then, this MUST be one of our other nodes (that we are looking for, otherwise remove this section)
for (String n : nodes) {
//noinspection StringEquality
if (n != node && label.startsWith(n)) {
canSave = true;
break main;
}
}
}
}
if (canSave) {
// if this section is for a state we DO NOT care about, remove it
int stateIndex = label.lastIndexOf(':');
if (stateIndex != -1) {
String stateValue = label.substring(stateIndex + 1);
boolean saveState = false;
for (String state : states) {
if (stateValue.equals(state)) {
// this is a state we care about
saveState = true;
break;
}
}
if (!saveState) {
canSave = false;
}
}
}
}
if (canSave) {
sections.add(section);
}
}
if (DEBUG_NODES) {
for (CssNode section : sections) {
System.err.println("--------------");
System.err.println(section);
System.err.println("--------------");
}
}
return sections;
}
/**
* find an attribute name from the list of sections. The incoming sections will all be related to one of the nodes, we prioritize
* them on WHO has the attribute we are looking for.
*
* @param sections the css sections
* @param attributeName the name of the attribute we are looking for.
* @param equalsOrContained true if we want to EXACT match, false if the attribute key can contain what we are looking for.
*
* @return the attribute value, if found
*/
@SuppressWarnings("Duplicates")
public static
List<Entry> getAttributeFromSections(final List<CssNode> sections, final String attributeName, boolean equalsOrContained) {
// a list of sections that contains the exact attribute we are looking for
List<Entry> sectionsWithAttribute = new ArrayList<Entry>();
for (CssNode cssNode : sections) {
for (Entry attribute : cssNode.attributes) {
if (equalsOrContained) {
if (attribute.key.equals(attributeName)) {
sectionsWithAttribute.add(new Entry(cssNode.label, attribute.value));
}
}
else {
if (attribute.key.contains(attributeName)) {
sectionsWithAttribute.add(new Entry(cssNode.label, attribute.value));
}
}
}
}
if (DEBUG_GETTING_ATTRIBUTE_FROM_NODES) {
System.err.println("--------------");
System.err.println("Cleaned Sections");
System.err.println("--------------");
for (Entry section : sectionsWithAttribute) {
System.err.println("--------------");
System.err.println(section);
System.err.println("--------------");
}
}
return sectionsWithAttribute;
}
public static
void injectAdditionalCss(final File parent, final StringBuilder stringBuilder) {
// not the BEST way to do this because duplicates are not merged at all.
int start = 0;
while (start != -1) {
// now check if it says: @import url("gtk-main.css")
start = stringBuilder.indexOf("@import url(", start);
if (start != -1) {
int end = stringBuilder.indexOf("\")", start);
if (end != -1) {
String url = stringBuilder.substring(start + 13, end);
stringBuilder.delete(start, end + 2); // 2 is the size of ")
if (DEBUG) {
System.err.println("import url: " + url);
}
try {
// now inject the new file where the import command was.
File file = new File(parent, url);
StringBuilder stringBuilder2 = new StringBuilder((int) (file.length()));
FileUtil.read(file, stringBuilder2);
removeComments(stringBuilder2);
stringBuilder.insert(start, stringBuilder2);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
@SuppressWarnings("Duplicates")
public static
void removeComments(final StringBuilder stringBuilder) {
// remove block comments, /* .... */ This can span multiple lines
int start = 0;
while (start != -1) {
// get the start of a comment
start = stringBuilder.indexOf("/*", start);
if (start != -1) {
// get the end of a comment
int end = stringBuilder.indexOf("*/", start);
if (end != -1) {
stringBuilder.delete(start, end + 2); // 2 is the size of */
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
if (stringBuilder.charAt(start) == '\n') {
stringBuilder.delete(start, start + 1);
}
else {
start++;
}
}
}
}
// now remove comments that start with // (line MUST start with //)
start = 0;
while (start != -1) {
// get the start of a comment
start = stringBuilder.indexOf("//", start);
if (start != -1) {
// the comment is at the start of a line
if (start == 0 || stringBuilder.charAt(start - 1) == '\n') {
// get the end of the comment (the end of the line)
int end = stringBuilder.indexOf("\n", start);
if (end != -1) {
stringBuilder.delete(start, end + 1); // 1 is the size of \n
}
}
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
if (stringBuilder.charAt(start) == '\n') {
stringBuilder.delete(start, start + 1);
}
else if (start > 0) {
start++;
}
}
}
// now remove comments that start with # (line MUST start with #)
start = 0;
while (start != -1) {
// get the start of a comment
start = stringBuilder.indexOf("#", start);
if (start != -1) {
// the comment is at the start of a line
if (start == 0 || stringBuilder.charAt(start - 1) == '\n') {
// get the end of the comment (the end of the line)
int end = stringBuilder.indexOf("\n", start);
if (end != -1) {
stringBuilder.delete(start, end + 1); // 1 is the size of \n
}
}
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
if (stringBuilder.charAt(start) == '\n') {
stringBuilder.delete(start, start + 1);
}
else if (start > 0) {
start++;
}
}
}
}
/**
* @return the parsed out CSS, or NULL
*/
public static
Css parse(final String css) {
if (css == null) {
return null;
}
// extract the color definitions
List<Entry> colorDefinitions = getColorDefinition(css);
int endOfColorDefinitions = css.indexOf("{");
// find the start of the line.
for (int lineStart = endOfColorDefinitions; lineStart > 0; lineStart--) {
if (css.charAt(lineStart) == '\n') {
endOfColorDefinitions = lineStart + 1;
break;
}
}
// collect a list of all of the sections that have what we are interested in.
List<CssNode> sections = new ArrayList<CssNode>();
int index = endOfColorDefinitions;
int length = css.length();
// now create a list of the CSS nodes
do {
int endOfNodeLabels = css.indexOf("{", index);
if (endOfNodeLabels == -1) {
break;
}
int endOfSection = css.indexOf("}", endOfNodeLabels + 1) + 1;
int endOfSectionTest = css.indexOf("}", index) + 1;
// this makes sure that weird parsing errors don't happen as a result of node keywords appearing in node sections
if (endOfSection != endOfSectionTest) {
// advance the index
index = endOfSection;
continue;
}
// find the start of the line.
for (int lineStart = index; lineStart > 0; lineStart--) {
if (css.charAt(lineStart) == '\n') {
index = lineStart + 1;
break;
}
}
String nodeLabel = css.substring(index, endOfNodeLabels)
.trim();
List<Entry> attributes = new ArrayList<Entry>();
// split the section into an arrayList, one per item. Split by attribute element
String nodeSection = css.substring(endOfNodeLabels, endOfSection);
int sectionStart = nodeSection.indexOf('{') + 1;
int sectionEnd = nodeSection.indexOf('}');
while (sectionStart != -1) {
int end = nodeSection.indexOf(';', sectionStart);
if (end != -1) {
int separator = nodeSection.indexOf(':', sectionStart);
// String.charAt() is a bit slow because of all the extra range checking it performs
if (separator < end) {
// the parenthesis must be balanced for the value
short parenCount = 0;
int j = separator;
while (j < end) {
j++;
char c = nodeSection.charAt(j);
if (c == '(') {
parenCount++;
}
else if (c == ')') {
parenCount--;
}
}
j--;
if (parenCount > 0) {
do {
// find the extended balancing paren
if (nodeSection.charAt(j) == ')') {
parenCount--;
}
j++;
} while (parenCount > 0 && j < sectionEnd);
}
end = j + 1;
String key = nodeSection.substring(sectionStart, separator);
String value = nodeSection.substring(separator + 1, end);
attributes.add(new Entry(key, value));
}
sectionStart = end + 1;
}
else {
break;
}
}
// if the label contains ',' this means that MORE than that one CssNode has the same attributes. We want to split it up and duplicate it.
int multiIndex = nodeLabel.indexOf(',');
if (multiIndex != -1) {
multiIndex = 0;
while (multiIndex != -1) {
int multiEndIndex = nodeLabel.indexOf(',', multiIndex);
if (multiEndIndex != -1) {
String newLabel = nodeLabel.substring(multiIndex, multiEndIndex);
sections.add(new CssNode(newLabel, attributes));
multiIndex = multiEndIndex + 1;
}
else {
// now add the last part of the label.
String newLabel = nodeLabel.substring(multiIndex);
sections.add(new CssNode(newLabel, attributes));
multiIndex = -1;
}
}
}
else {
// we are the only one with these attributes
sections.add(new CssNode(nodeLabel, attributes));
}
// advance the index
index = endOfSection;
} while (index < length);
// now merge all nodes that have the same labels.
for (Iterator<CssNode> iterator = sections.iterator(); iterator.hasNext(); ) {
final CssNode section = iterator.next();
if (section != null) {
String label = section.label;
for (int i = 0; i < sections.size(); i++) {
final CssNode section2 = sections.get(i);
if (section != section2 && section2 != null && label.equals(section2.label)) {
sections.set(i, null);
// now merge both lists.
final List<Entry> attributes = section.attributes;
for (int i1 = 0; i1 < attributes.size(); i1++) {
final Entry attribute = attributes.get(i1);
for (Iterator<Entry> iterator2 = section2.attributes.iterator(); iterator2.hasNext(); ) {
final Entry attribute2 = iterator2.next();
if (attribute.equals(attribute2)) {
iterator2.remove();
break;
}
}
}
// now both lists are unique.
section.attributes.addAll(section2.attributes);
}
}
} else {
// clean up the (possible) null entries.
iterator.remove();
}
}
// final cleanup loop for empty CSS sections
for (Iterator<CssNode> iterator = sections.iterator(); iterator.hasNext(); ) {
final CssNode section = iterator.next();
for (Iterator<Entry> iterator1 = section.attributes.iterator(); iterator1.hasNext(); ) {
final Entry attribute = iterator1.next();
if (attribute == null) {
iterator1.remove();
}
}
if (section.attributes.isEmpty()) {
iterator.remove();
}
}
return new Css(colorDefinitions, sections);
}
/**
* Gets the color definitions (which exists at the beginnning of the CSS/gtkrc files) as a list of key/value attributes. The values
* are also recursively resolved.
*/
private static
List<Entry> getColorDefinition(final String css) {
// since it's a color definition, it will start a very specific way. This will recursively get the defined colors.
List<Entry> defines = new ArrayList<Entry>();
{
// have to setup the "define color" section
String colorDefine = "@define-color";
int length = colorDefine.length() + 1;
int start = 0;
int mid = 0;
int end = 0;
while (start < css.length()) {
start = css.indexOf(colorDefine, start);
if (start == -1) {
break;
}
start += length;
end = css.indexOf(';', start) + 1; // include the ;
mid = css.indexOf(' ', start);
if (mid > -1) {
String label = css.substring(start, mid);
String value = css.substring(mid + 1, end);
// remove the trailing ;
int endOfValue = value.length() - 1;
if (value.charAt(endOfValue) == ';') {
value = value.substring(0, endOfValue);
}
Entry attribute = new Entry(label, value);
defines.add(attribute);
}
start = end + 1;
}
}
// now to recursively figure out the color definitions
boolean allClean = false;
while (!allClean) {
allClean = true;
for (Entry d : defines) {
String value = d.value;
int i = value.indexOf('@');
if (i > -1) {
// where is the last letter?
int lastLetter;
for (lastLetter = i+1; lastLetter < value.length(); lastLetter++) {
char c = value.charAt(lastLetter);
if (!Character.isLetter(c) && c != '_') {
allClean = false;
break;
}
}
String replacement = value.substring(i+1, lastLetter);
// the target value for replacement will ALWAYS be earlier in the list.
for (Entry d2 : defines) {
if (d2.key.equals(replacement)) {
int length = d2.value.length();
// string.replace() goes bonkers on the heap... This keeps it under control
StringBuilder builder = new StringBuilder(value);
builder.insert(i, d2.value);
builder.delete(i + length, lastLetter + length);
d.value = builder.toString();
break;
}
}
}
}
}
return defines;
}
/**
* Select the most relevant CSS attribute based on the input cssNodes
*
* @param cssNodes the list of in-order cssNodes we care about
* @param entries a list of key/value pairs, where the key is the CSS Node label, and the value is the attribute value
*
* @return the most relevant attribute or NULL
*/
public static
String selectMostRelevantAttribute(final String[] cssNodes, final List<Entry> entries) {
// we care about 'cssNodes' IN ORDER, so if the first one has what we are looking for, that is what we choose.
for (String node : cssNodes) {
for (Entry s : entries) {
if (s.key.equals(node)) {
return s.value;
}
}
// check now if one of the children has it
for (Entry s : entries) {
if (s.key.contains(node)) {
return s.value;
}
}
}
return null;
}
}

View File

@ -0,0 +1,39 @@
package dorkbox.systemTray.util;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import dorkbox.util.NamedThreadFactory;
/**
* Adds events to a single thread event dispatch, so that regardless of OS, all event callbacks happen on the same thread -- which is NOT
* the GTK/AWT/SWING event dispatch thread. There can be ODD peculiarities across on GTK with how AWT/SWING react with the GTK Event
* Dispatch Thread.
*/
public
class EventDispatch {
private static ExecutorService eventDispatchExecutor = null;
/**
* Schedule an event to occur sometime in the future.
*/
public static synchronized
void runLater(Runnable runnable) {
if (eventDispatchExecutor == null) {
eventDispatchExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("SystemTrayEventDispatch", false));
}
eventDispatchExecutor.execute(runnable);
}
/**
* Shutdown the event dispatch
*/
public static synchronized
void shutdown() {
if (eventDispatchExecutor != null) {
eventDispatchExecutor.shutdownNow();
eventDispatchExecutor = null;
}
}
}

View File

@ -220,33 +220,30 @@ class ImageResizeUtil {
// if it's already there, we have to delete it
newFile.delete();
// resize the image, keep aspect ratio
Image image = ImageUtil.getImageImmediate(ImageIO.read(inputStream));
int height = image.getHeight(null);
int width = image.getWidth(null);
BufferedImage bufferedImage = ImageUtil.getBufferedImage(image);
// resize the image, keep aspect ratio
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
if (width > height) {
// fit on the width
image = image.getScaledInstance(size, -1, Image.SCALE_SMOOTH);
} else {
// fit on the height
image = image.getScaledInstance(-1, size, Image.SCALE_SMOOTH);
bufferedImage = ImageUtil.resizeImage(bufferedImage, size, -1);
}
else {
bufferedImage = ImageUtil.resizeImage(bufferedImage, -1, size);
}
// make the image "square" so there is padding on the sides that are smaller
bufferedImage = ImageUtil.getSquareBufferedImage(bufferedImage);
// now write out the new one
BufferedImage bufferedImage = ImageUtil.getSquareBufferedImage(image);
ImageIO.write(bufferedImage, "png", newFile); // made up extension
return newFile;
}
public static
File shouldResizeOrCache(final boolean isTrayImage, final File imageFile) {
if (imageFile == null) {
@ -324,8 +321,8 @@ class ImageResizeUtil {
try {
final Image trayImage = ImageUtil.getImageImmediate(image);
BufferedImage bufferedImage = ImageUtil.getBufferedImage(trayImage);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "png", os);
InputStream imageInputStream = new ByteArrayInputStream(os.toByteArray());

View File

@ -1,135 +0,0 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.util;
import java.lang.reflect.Method;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.OS;
/**
* Utility methods for JavaFX.
* <p>
* We use reflection for these methods so that we can compile everything under Java 1.6 (which doesn't have JavaFX).
*/
public
class JavaFX {
// Methods are cached for performance
private static final Method dispatchMethod;
private static final Method isEventThreadMethod;
private static final Object isEventThreadObject;
static {
Method _isEventThreadMethod = null;
Method _dispatchMethod = null;
Object _isEventThreadObject = null;
try {
Class<?> clazz = Class.forName("javafx.application.Platform");
_dispatchMethod = clazz.getMethod("runLater", Runnable.class);
// JAVA 7
// javafx.application.Platform.isFxApplicationThread();
// JAVA 8
// com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread();
if (OS.javaVersion <= 7) {
clazz = Class.forName("javafx.application.Platform");
_isEventThreadMethod = clazz.getMethod("isFxApplicationThread");
_isEventThreadObject = null;
} else {
clazz = Class.forName("com.sun.javafx.tk.Toolkit");
_isEventThreadMethod = clazz.getMethod("getToolkit");
_isEventThreadObject = _isEventThreadMethod.invoke(null);
_isEventThreadMethod = _isEventThreadObject.getClass()
.getMethod("isFxUserThread", (java.lang.Class<?>[])null);
}
} catch (Throwable e) {
SystemTray.logger.error("Cannot initialize JavaFX", e);
}
dispatchMethod = _dispatchMethod;
isEventThreadMethod = _isEventThreadMethod;
isEventThreadObject = _isEventThreadObject;
}
public static
void init() {
if (dispatchMethod == null || isEventThreadMethod == null) {
SystemTray.logger.error("Unable to initialize JavaFX! Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
public static
void dispatch(final Runnable runnable) {
// javafx.application.Platform.runLater(runnable);
try {
dispatchMethod.invoke(null, runnable);
} catch (Throwable e) {
SystemTray.logger.error("Unable to execute JavaFX runLater(). Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
public static
boolean isEventThread() {
// JAVA 7
// javafx.application.Platform.isFxApplicationThread();
// JAVA 8
// com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread();
try {
if (OS.javaVersion <= 7) {
return (Boolean) isEventThreadMethod.invoke(null);
} else {
return (Boolean) isEventThreadMethod.invoke(isEventThreadObject, (java.lang.Class<?>[])null);
}
} catch (Throwable e) {
SystemTray.logger.error("Unable to check if JavaFX is in the event thread. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
return false;
}
public static
void onShutdown(final Runnable runnable) {
// com.sun.javafx.tk.Toolkit.getToolkit()
// .addShutdownHook(runnable);
try {
Class<?> clazz = Class.forName("com.sun.javafx.tk.Toolkit");
Method method = clazz.getMethod("getToolkit");
Object o = method.invoke(null);
Method m = o.getClass()
.getMethod("addShutdownHook", Runnable.class);
m.invoke(o, runnable);
} catch (Throwable e) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Cannot initialize JavaFX", e);
}
SystemTray.logger.error("Unable to insert shutdown hook into JavaFX. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
}

View File

@ -31,7 +31,7 @@ import javax.swing.plaf.metal.MetalBorders;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.swingUI.SwingUIFactory;
import dorkbox.systemTray.ui.swing.SwingUIFactory;
import dorkbox.util.swing.DefaultMenuItemUI;
import dorkbox.util.swing.DefaultPopupMenuUI;
import dorkbox.util.swing.DefaultSeparatorUI;
@ -56,7 +56,6 @@ class LinuxSwingUI implements SwingUIFactory {
public static class Metal_MenuItemBorder extends MetalBorders.MenuItemBorder {
private final int verticalPadding;
public
Metal_MenuItemBorder(int verticalPadding) {
this.verticalPadding = verticalPadding;
}
@ -69,6 +68,10 @@ class LinuxSwingUI implements SwingUIFactory {
}
}
private static PopupMenuUI popupMenuUI = null;
private static MenuItemUI menuItemUI = null;
private static SeparatorUI separatorUI = null;
/**
* Allows one to specify the Look & Feel of the menus (The main SystemTray and sub-menus)
*
@ -134,7 +137,6 @@ class LinuxSwingUI implements SwingUIFactory {
return new DefaultSeparatorUI(jSeparator);
}
/**
* This saves a vector CheckMark to a correctly sized PNG file. The checkmark image will ALWAYS be centered in the targetImageSize
* (which is square)

View File

@ -15,11 +15,8 @@
*/
package dorkbox.systemTray.util;
import static com.sun.jna.platform.win32.WinDef.HDC;
import static com.sun.jna.platform.win32.WinDef.POINT;
import static com.sun.jna.platform.win32.WinUser.SM_CYMENUCHECK;
import static dorkbox.util.jna.windows.GDI32.GetDeviceCaps;
import static dorkbox.util.jna.windows.GDI32.LOGPIXELSX;
import static com.sun.jna.platform.win32.WinUser.SM_CYSMICON;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
@ -28,22 +25,16 @@ import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import javax.swing.JMenuItem;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.jna.linux.GtkTheme;
import dorkbox.systemTray.swingUI._SwingTray;
import dorkbox.systemTray.ui.swing._SwingTray;
import dorkbox.util.OS;
import dorkbox.util.OSUtil;
import dorkbox.util.SwingUtil;
import dorkbox.util.jna.windows.ShCore;
import dorkbox.util.jna.linux.GtkTheme;
import dorkbox.util.jna.windows.User32;
public
@ -52,15 +43,9 @@ class SizeAndScalingUtil {
static int TRAY_SIZE = 0;
static int TRAY_MENU_SIZE = 0;
static {
// if (OSUtil.Windows.isWindows8_1_plus()) {
// ShCore.SetProcessDpiAwareness(ProcessDpiAwareness.PROCESS_SYSTEM_DPI_AWARE);
// }
}
private static
public static
double getMacOSScaleFactor() {
// apple will ALWAYS return 2.0 on (apple) retina displays. If it's a non-standard, then who knows...
// apple will ALWAYS return 2.0 on (apple) retina displays. This is enforced by apple
// java6 way of getting it...
if (OS.javaVersion == 6) {
@ -102,54 +87,11 @@ class SizeAndScalingUtil {
}
/**
* Number of pixels per logical inch along the screen width. In a system with multiple display monitors, this value is the
* same for all monitors.
*/
public static
int getWindowsLogicalDPI() {
// get the logical resolution
HDC screen = User32.IMPL.GetDC(null);
int logical_dpiX = GetDeviceCaps(screen, LOGPIXELSX);
User32.IMPL.ReleaseDC(null, screen);
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Windows logical DPI: '{}'", logical_dpiX);
}
return logical_dpiX;
}
public static
int getWindowsPrimaryMonitorHardwareDPI() {
// WINDOWS 8.1+ ONLY! Parts of this API were added in Windows 8.1, so this will not work at all for < 8.1
if (OSUtil.Windows.isWindows8_1_plus()) {
// FROM: https://blogs.msdn.microsoft.com/oldnewthing/20070809-00/?p=25643
// to get the **PRIMARY** monitor, pass in point 0,0
IntByReference hardware_dpiX = new IntByReference();
// get the primary monitor handle
Pointer pointer = User32.IMPL.MonitorFromPoint(new POINT(0, 0), 1);// MONITOR_DEFAULTTOPRIMARY -> 1
ShCore.GetDpiForMonitor(pointer, 1, hardware_dpiX, new IntByReference()); // don't care about y
int value = hardware_dpiX.getValue();
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Windows hardware DPI: '{}'", value);
}
return value;
}
return 0;
}
public static
int getTrayImageSize(final Class<? extends Tray> trayType) {
int getTrayImageSize() {
if (TRAY_SIZE == 0) {
if (OS.isLinux()) {
TRAY_SIZE = GtkTheme.getIndicatorSize(trayType);
TRAY_SIZE = GtkTheme.getIndicatorSize();
}
else if (OS.isMacOsX()) {
// these are the standard icon sizes. From what I can tell, they are Apple defined, and cannot be changed.
@ -160,99 +102,8 @@ class SizeAndScalingUtil {
}
}
else if (OS.isWindows()) {
int[] version = OSUtil.Windows.getVersion();
if (SystemTray.DEBUG) {
SystemTray.logger.debug("Windows version: '{}'", Arrays.toString(version));
}
// http://kynosarges.org/WindowsDpi.html
// 96 DPI = 100% scaling
// 120 DPI = 125% scaling
// 144 DPI = 150% scaling
// 192 DPI = 200% scaling
final double defaultDPI = 96.0;
// windows 8/8.1/10 are the only windows OSes to do scaling properly (XP/Vista/7 do DPI scaling, which is terrible anyways)
// XP - 7.0 - only global DPI settings, no scaling
// 8.0 - only global DPI settings + scaling
// 8.1 - 10 - global + per-monitor DPI settings + scaling
// get the logical resolution
int windowsLogicalDPI = getWindowsLogicalDPI();
if (!OSUtil.Windows.isWindows8_1_plus()) {
// < Windows 8.1 doesn't do scaling + DPI changes, they just "magnify" (but not scale w/ DPI) the icon + change the font size.
// 96 DPI = 16
// 120 DPI = 20 (16 * 1.25)
// 144 DPI = 24 (16 * 1.5)
TRAY_SIZE = 16;
return TRAY_SIZE;
}
else {
// Windows 8.1+ does proper scaling, so an icon at a higher resolution is drawn, instead of drawing the "original"
// resolution image and scaling it up to the new size
// 96 DPI = 16
// 120 DPI = 20 (16 * 1.25)
// 144 DPI = 24 (16 * 1.5)
TRAY_SIZE = (int) (16 * (windowsLogicalDPI / defaultDPI));
return TRAY_SIZE;
}
// NOTE: can override DPI settings
// * At a 100% scaling, the DPI is 96.
//
//
// Integer winDPIScaling;
// if (PlatformDetector.isWin7()) {
// winDPIScaling = 1;
// } else {
// // Win 8 or later.
// winDPIScaling = RegistryUtil.getRegistryIntValue(
// RegistryUtil.HKEY_CURRENT_USER,
// "Control Panel\\Desktop",
// "Win8DpiScaling");
// if(winDPIScaling == null){
// winDPIScaling = 0;
// }
// }
//
// Integer desktopDPIOverride;
// if (PlatformDetector.isWin7()) {
// desktopDPIOverride = 0;
// } else {
// // Win 8 or later.
// desktopDPIOverride = RegistryUtil.getRegistryIntValue(
// RegistryUtil.HKEY_CURRENT_USER,
// "Control Panel\\Desktop",
// "DesktopDPIOverride");
// if(desktopDPIOverride == null){
// desktopDPIOverride = 0;
// }
//
// }
//
//
// if (winDPIScaling == 1 && desktopDPIOverride == 0){
// // There is scaling, but on override (magnifying glass).
// Integer logPixels = RegistryUtil.getRegistryIntValue(
// RegistryUtil.HKEY_CURRENT_USER,
// "Control Panel\\Desktop",
// "LogPixels");
//
// if (logPixels != null && logPixels != WIN_DEFAULT_DPI){
// this.scalingFactor = ((float)logPixels)/WIN_DEFAULT_DPI;
// }
// }
// https://msdn.microsoft.com/en-us/library/bb773352(v=vs.85).aspx
// provide both a 16x16 pixel icon and a 32x32 icon
// https://msdn.microsoft.com/en-us/library/dn742495.aspx
// Use an icon with 16x16, 20x20, and 24x24 pixel versions. The larger versions are used in high-dpi display mode
TRAY_SIZE = User32.User32.GetSystemMetrics(SM_CYSMICON);
return TRAY_SIZE;
} else {
// reasonable default
TRAY_SIZE = 32;
@ -281,7 +132,6 @@ class SizeAndScalingUtil {
if (OS.isWindows()) {
// http://kynosarges.org/WindowsDpi.html
// image-size/menu-height
// 96 DPI = 100% actual size: 14/17
// 144 DPI = 150% actual size: 24/29
@ -289,7 +139,7 @@ class SizeAndScalingUtil {
// gets the height of the default checkmark size, adjusted
// This is the closest image size we can get to the actual size programmatically. This is a LOT closer that checking the
// largest size a JMenu image can be before the menu size changes.
TRAY_MENU_SIZE = User32.IMPL.GetSystemMetrics(SM_CYMENUCHECK) - 1;
TRAY_MENU_SIZE = User32.User32.GetSystemMetrics(SM_CYMENUCHECK) - 1;
// image-size/menu-height
// 96 DPI = 100% mark size: 14/20

View File

@ -1,84 +0,0 @@
/*
* Copyright 2016 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.util;
import static dorkbox.systemTray.SystemTray.logger;
import org.eclipse.swt.widgets.Display;
/**
* Utility methods for SWT.
* <p>
* SWT system tray types are just GTK trays.
*/
public
class Swt {
private static final Display currentDisplay;
private static final Thread currentDisplayThread;
static {
// we MUST save this, otherwise it is "null" when methods are run from the swing EDT.
currentDisplay = Display.getCurrent();
currentDisplayThread = currentDisplay.getThread();
}
public static
void init() {
if (currentDisplay == null) {
logger.error("Unable to get the current display for SWT. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
public static
void dispatch(final Runnable runnable) {
currentDisplay.syncExec(runnable);
}
public static
boolean isEventThread() {
return Thread.currentThread() == currentDisplayThread;
}
public static
void onShutdown(final Runnable runnable) {
// currentDisplay.getShells() can only happen inside the event thread!
if (isEventThread()) {
currentDisplay.getShells()[0].addListener(org.eclipse.swt.SWT.Close, new org.eclipse.swt.widgets.Listener() {
@Override
public
void handleEvent(final org.eclipse.swt.widgets.Event event) {
runnable.run();
}
});
} else {
dispatch(new Runnable() {
@Override
public
void run() {
currentDisplay.getShells()[0].addListener(org.eclipse.swt.SWT.Close, new org.eclipse.swt.widgets.Listener() {
@Override
public
void handleEvent(final org.eclipse.swt.widgets.Event event) {
runnable.run();
}
});
}
});
}
}
}

View File

@ -22,7 +22,7 @@ import java.awt.AWTException;
import java.util.Locale;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.BootStrapClassLoader;
import dorkbox.util.ClassLoaderUtil;
import dorkbox.util.OS;
import javassist.ClassPool;
import javassist.CtBehavior;
@ -106,7 +106,7 @@ class SystemTrayFixes {
.toLowerCase(Locale.US);
// spaces at the end to make sure we check for words
return !(vendor.contains("sun ") || vendor.contains("oracle "));
return vendor.contains("sun ") || vendor.contains("oracle ");
}
@ -231,8 +231,8 @@ class SystemTrayFixes {
}
// whoosh, past the classloader and directly into memory.
BootStrapClassLoader.defineClass(trayBytes);
BootStrapClassLoader.defineClass(trayIconBytes);
ClassLoaderUtil.Bootstrap.defineClass(trayBytes);
ClassLoaderUtil.Bootstrap.defineClass(trayIconBytes);
if (SystemTray.DEBUG) {
logger.debug("Successfully changed tray icon size to: {}", trayIconSize);
@ -412,7 +412,7 @@ class SystemTrayFixes {
mouseEventBytes = trayClass.toBytecode();
// whoosh, past the classloader and directly into memory.
BootStrapClassLoader.defineClass(mouseEventBytes);
ClassLoaderUtil.Bootstrap.defineClass(mouseEventBytes);
if (SystemTray.DEBUG) {
logger.debug("Successfully changed mouse trigger for MacOSX");
@ -691,11 +691,11 @@ class SystemTrayFixes {
}
// whoosh, past the classloader and directly into memory.
BootStrapClassLoader.defineClass(runnableBytes);
BootStrapClassLoader.defineClass(eFrameBytes);
BootStrapClassLoader.defineClass(iconCanvasBytes);
BootStrapClassLoader.defineClass(trayIconBytes);
BootStrapClassLoader.defineClass(trayPeerBytes);
ClassLoaderUtil.Bootstrap.defineClass(runnableBytes);
ClassLoaderUtil.Bootstrap.defineClass(eFrameBytes);
ClassLoaderUtil.Bootstrap.defineClass(iconCanvasBytes);
ClassLoaderUtil.Bootstrap.defineClass(trayIconBytes);
ClassLoaderUtil.Bootstrap.defineClass(trayPeerBytes);
if (SystemTray.DEBUG) {
logger.debug("Successfully changed tray icon background color");

View File

@ -16,8 +16,12 @@
package dorkbox.systemTray.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JSeparator;
@ -25,9 +29,13 @@ import javax.swing.plaf.MenuItemUI;
import javax.swing.plaf.PopupMenuUI;
import javax.swing.plaf.SeparatorUI;
import com.sun.java.swing.plaf.windows.WindowsMenuItemUI;
import com.sun.java.swing.plaf.windows.WindowsMenuUI;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.swingUI.SwingUIFactory;
import dorkbox.systemTray.ui.swing.SwingUIFactory;
import dorkbox.util.OSUtil;
import dorkbox.util.swing.DefaultMenuItemUI;
import dorkbox.util.swing.DefaultPopupMenuUI;
import dorkbox.util.swing.DefaultSeparatorUI;
@ -46,8 +54,10 @@ import dorkbox.util.swing.DefaultSeparatorUI;
* large
* myTextField.putClientProperty("JComponent.sizeVariant", "large");
*/
@SuppressWarnings("Duplicates")
public
class WindowsSwingUI implements SwingUIFactory {
private static final boolean isWindowsXP = OSUtil.Windows.isWindowsXP();
/**
* Allows one to specify the Look & Feel of the menus (The main SystemTray and sub-menus)
@ -67,7 +77,7 @@ class WindowsSwingUI implements SwingUIFactory {
super.installUI(c);
}
};
}
}
/**
* Allows one to specify the Look & Feel of a menu entry
@ -80,13 +90,88 @@ class WindowsSwingUI implements SwingUIFactory {
@Override
public
MenuItemUI getItemUI(final JMenuItem jMenuItem, final Entry entry) {
return new DefaultMenuItemUI(jMenuItem) {
@Override
public
void installUI(final JComponent c) {
super.installUI(c);
if (isWindowsXP) {
// fix for "Swing Menus - text/icon/checkmark alignment schemes severely broken"
// http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4199382
// basically, override everything to have a 'null' checkbox, so the graphics system thinks it's not there.
if (jMenuItem instanceof JMenu) {
return new WindowsMenuUI() {
@Override
public
void installUI(final JComponent c) {
super.installUI(c);
}
@Override
protected
void paintMenuItem(Graphics g,
JComponent c,
Icon checkIcon,
Icon arrowIcon,
Color background,
Color foreground,
int defaultTextIconGap) {
super.paintMenuItem(g, c, null, arrowIcon, background, foreground, defaultTextIconGap);
}
@Override
public Dimension getPreferredSize(JComponent c) {
return getPreferredMenuItemSize(c,
null,
arrowIcon,
defaultTextIconGap);
}
@Override
protected
Dimension getPreferredMenuItemSize(final JComponent c,
final Icon checkIcon,
final Icon arrowIcon,
final int defaultTextIconGap) {
return super.getPreferredMenuItemSize(c, null, arrowIcon, defaultTextIconGap);
}
};
} else {
return new WindowsMenuItemUI() {
@Override
public
void installUI(final JComponent c) {
super.installUI(c);
}
@Override
protected
void paintMenuItem(Graphics g,
JComponent c,
Icon checkIcon,
Icon arrowIcon,
Color background,
Color foreground,
int defaultTextIconGap) {
// we don't use checkboxes, we draw our own as an image. -OFFSET is to offset insanely large margins
super.paintMenuItem(g, c, null, arrowIcon, background, foreground, defaultTextIconGap);
}
@Override
public Dimension getPreferredSize(JComponent c) {
return getPreferredMenuItemSize(c,
null,
arrowIcon,
defaultTextIconGap);
}
};
}
};
}
else {
return new DefaultMenuItemUI(jMenuItem) {
@Override
public
void installUI(final JComponent c) {
super.installUI(c);
}
};
}
}
/**

View File

@ -27,7 +27,7 @@ import javax.swing.plaf.SeparatorUI;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.swingUI.SwingUIFactory;
import dorkbox.systemTray.ui.swing.SwingUIFactory;
import dorkbox.systemTray.util.HeavyCheckMark;
import dorkbox.util.swing.DefaultMenuItemUI;
import dorkbox.util.swing.DefaultPopupMenuUI;

View File

@ -18,6 +18,7 @@ package dorkbox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import dorkbox.systemTray.Checkbox;
@ -25,6 +26,9 @@ import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.CacheUtil;
import dorkbox.util.Desktop;
import dorkbox.util.OS;
/**
* Icons from 'SJJB Icons', public domain/CC0 icon set
@ -56,8 +60,11 @@ class TestTray {
public
TestTray() {
// SwingUtil.setLookAndFeel(null);
// SystemTray.SWING_UI = new CustomSwingUI();
SystemTray.DEBUG = true; // for test apps, we always want to run in debug mode
CacheUtil.clear(); // for test apps, make sure the cache is always reset. You should never do this in production.
// SwingUtil.setLookAndFeel(null); // set Native L&F (this is the System L&F instead of CrossPlatform L&F)
// SystemTray.SWING_UI = new CustomSwingUI();
this.systemTray = SystemTray.get();
if (systemTray == null) {
@ -97,12 +104,14 @@ class TestTray {
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
entry.setTooltip(null); // remove the tooltip
// systemTray.remove(menuEntry);
}
});
greenEntry.setImage(GREEN_MAIL);
// case does not matter
greenEntry.setShortcut('G');
greenEntry.setTooltip("This means you have green mail!");
mainMenu.add(greenEntry);
@ -118,10 +127,35 @@ class TestTray {
mainMenu.add(new Separator());
mainMenu.add(new MenuItem("About", new ActionListener() {
@Override
public
void actionPerformed(final ActionEvent e) {
try {
Desktop.browseURL("https://github.com/dorkbox/SystemTray");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
mainMenu.add(new MenuItem("Temp Directory", new ActionListener() {
@Override
public
void actionPerformed(final ActionEvent e) {
try {
Desktop.browseDirectory(OS.TEMP_DIR.getAbsolutePath());
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
Menu submenu = new Menu("Options", BLUE_CAMPING);
submenu.setShortcut('t');
mainMenu.add(submenu);
MenuItem disableMenu = new MenuItem("Disable menu", BLACK_BUS, new ActionListener() {
@Override
@ -149,7 +183,7 @@ class TestTray {
source.getParent().remove();
}
}));
mainMenu.add(submenu);
systemTray.getMenu().add(new MenuItem("Quit", new ActionListener() {
@Override

View File

@ -17,6 +17,7 @@
package dorkbox;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import dorkbox.systemTray.Checkbox;
@ -24,7 +25,9 @@ import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.util.CacheUtil;
import dorkbox.util.Desktop;
import dorkbox.util.JavaFX;
import dorkbox.util.OS;
import javafx.application.Application;
import javafx.application.Platform;
@ -68,6 +71,10 @@ class TestTrayJavaFX {
@Override
public
void start(final Stage stage) throws Exception {
if (testTrayJavaFX == null) {
testTrayJavaFX = new TestTrayJavaFX();
}
testTrayJavaFX.doJavaFxStuff(stage);
}
}
@ -113,9 +120,11 @@ class TestTrayJavaFX {
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
SystemTray.DEBUG = true; // for test apps, we always want to run in debug mode
CacheUtil.clear(); // for test apps, make sure the cache is always reset. You should never do this in production.
// SwingUtil.setLookAndFeel(null);
// SystemTray.SWING_UI = new CustomSwingUI();
// SwingUtil.setLookAndFeel(null); // set Native L&F (this is the System L&F instead of CrossPlatform L&F)
// SystemTray.SWING_UI = new CustomSwingUI();
this.systemTray = SystemTray.get();
if (systemTray == null) {
@ -155,12 +164,14 @@ class TestTrayJavaFX {
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
entry.setTooltip(null); // remove the tooltip
// systemTray.remove(menuEntry);
}
});
greenEntry.setImage(GREEN_MAIL);
// case does not matter
greenEntry.setShortcut('G');
greenEntry.setTooltip("This means you have green mail!");
mainMenu.add(greenEntry);
@ -176,10 +187,33 @@ class TestTrayJavaFX {
mainMenu.add(new Separator());
mainMenu.add(new MenuItem("About", new ActionListener() {
@Override
public
void actionPerformed(final java.awt.event.ActionEvent e) {
try {
Desktop.browseURL("https://github.com/dorkbox/SystemTray");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
mainMenu.add(new MenuItem("Temp Directory", new ActionListener() {
@Override
public
void actionPerformed(final java.awt.event.ActionEvent e) {
try {
Desktop.browseDirectory(OS.TEMP_DIR.getAbsolutePath());
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
Menu submenu = new Menu("Options", BLUE_CAMPING);
submenu.setShortcut('t');
mainMenu.add(submenu);
MenuItem disableMenu = new MenuItem("Disable menu", BLACK_BUS, new ActionListener() {
@Override
@ -207,7 +241,7 @@ class TestTrayJavaFX {
source.getParent().remove();
}
}));
mainMenu.add(submenu);
systemTray.getMenu().add(new MenuItem("Quit", new ActionListener() {
@Override

View File

@ -18,6 +18,7 @@ package dorkbox;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.URL;
import org.eclipse.swt.SWT;
@ -30,6 +31,9 @@ import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.CacheUtil;
import dorkbox.util.Desktop;
import dorkbox.util.OS;
/**
* Icons from 'SJJB Icons', public domain/CC0 icon set
@ -72,8 +76,11 @@ class TestTraySwt {
helloWorldTest.setText("Hello World SWT ................. ");
helloWorldTest.pack();
// SwingUtil.setLookAndFeel(null);
// SystemTray.SWING_UI = new CustomSwingUI();
SystemTray.DEBUG = true; // for test apps, we always want to run in debug mode
CacheUtil.clear(); // for test apps, make sure the cache is always reset. You should never do this in production.
// SwingUtil.setLookAndFeel(null); // set Native L&F (this is the System L&F instead of CrossPlatform L&F)
// SystemTray.SWING_UI = new CustomSwingUI();
this.systemTray = SystemTray.get();
if (systemTray == null) {
@ -113,12 +120,14 @@ class TestTraySwt {
entry.setCallback(callbackGray);
entry.setImage(BLACK_MAIL);
entry.setText("Delete Mail");
entry.setTooltip(null); // remove the tooltip
// systemTray.remove(menuEntry);
}
});
greenEntry.setImage(GREEN_MAIL);
// case does not matter
greenEntry.setShortcut('G');
greenEntry.setTooltip("This means you have green mail!");
mainMenu.add(greenEntry);
@ -134,10 +143,33 @@ class TestTraySwt {
mainMenu.add(new Separator());
mainMenu.add(new MenuItem("About", new ActionListener() {
@Override
public
void actionPerformed(final ActionEvent e) {
try {
Desktop.browseURL("https://github.com/dorkbox/SystemTray");
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
mainMenu.add(new MenuItem("Temp Directory", new ActionListener() {
@Override
public
void actionPerformed(final ActionEvent e) {
try {
Desktop.browseDirectory(OS.TEMP_DIR.getAbsolutePath());
} catch (IOException e1) {
e1.printStackTrace();
}
}
}));
Menu submenu = new Menu("Options", BLUE_CAMPING);
submenu.setShortcut('t');
mainMenu.add(submenu);
MenuItem disableMenu = new MenuItem("Disable menu", BLACK_BUS, new ActionListener() {
@Override
@ -174,6 +206,7 @@ class TestTraySwt {
systemTray.shutdown();
// necessary to shut down SWT
display.asyncExec(new Runnable() {
@Override
public void run() {
shell.dispose();
}
@ -181,7 +214,7 @@ class TestTraySwt {
//System.exit(0); not necessary if all non-daemon threads have stopped.
}
})).setShortcut('q'); // case does not matter
mainMenu.add(submenu);
shell.pack();
shell.open();

View File

@ -3,11 +3,11 @@
#include <libappindicator/app-indicator.h>
// gcc example.c `pkg-config --cflags --libs gtk+-2.0 appindicator-0.1` -I/usr/include/libappindicator-0.1/ -o example
// gcc example.c `pkg-config --cflags --libs gtk+-2.0 appindicator-0.1` -I/usr/include/libappindicator-0.1/ -o example && ./example
// apt libgtk-3-dev install libappindicator3-dev
// apt-get install libgtk-3-dev libappindicator3-dev
// NOTE: there will be warnings, but the file will build and run. NOTE: this will not run as root on ubuntu (no dbus connection back to the normal user)
// gcc example.c `pkg-config --cflags --libs gtk+-3.0 appindicator3-0.1` -I/usr/include/libappindicator3-0.1/ -o example
// gcc example.c `pkg-config --cflags --libs gtk+-3.0 appindicator3-0.1` -I/usr/include/libappindicator3-0.1/ -o example && ./example
static void activate_action (GtkAction *action);
@ -278,6 +278,15 @@ int main (int argc, char **argv)
menuItem1 = gtk_image_menu_item_new_with_label("menu1");
// double check color info
GtkStyle *style;
style = gtk_rc_get_style(gtk_image_menu_item_new_with_mnemonic("xxx"));
GdkColor color = style->fg[GTK_STATE_NORMAL];
fprintf(stderr, "COLOR %s\n", gdk_color_to_string(&color));
// g_signal_connect(menuItem1, "button_press_event", G_CALLBACK (gtkCallback), NULL);
gtk_menu_shell_insert(GTK_MENU_SHELL(indicator_menu), menuItem1, 0);
gtk_widget_show(menuItem1);