diff --git a/LICENSE b/LICENSE index 8db30fb..13f51fa 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index ef80570..02d15fc 100644 --- a/README.md +++ b/README.md @@ -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 --------- ```` - - com.dorkbox - SystemTray - 3.1 - + + ... + + com.dorkbox + SystemTray + 3.12 + + ```` 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/ diff --git a/SystemTray.iml b/SystemTray.iml index 919d9eb..385980c 100755 --- a/SystemTray.iml +++ b/SystemTray.iml @@ -1,34 +1,15 @@ - - - - - - - - - - - - - - - - - - - - + diff --git a/src/dorkbox/systemTray/Entry.java b/src/dorkbox/systemTray/Entry.java index ce58250..f632d96 100644 --- a/src/dorkbox/systemTray/Entry.java +++ b/src/dorkbox/systemTray/Entry.java @@ -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() { diff --git a/src/dorkbox/systemTray/Menu.java b/src/dorkbox/systemTray/Menu.java index bbc75dd..87eb828 100644 --- a/src/dorkbox/systemTray/Menu.java +++ b/src/dorkbox/systemTray/Menu.java @@ -408,33 +408,15 @@ class Menu extends MenuItem { } } - /** - * This removes all menu entries from this menu - */ - public - void clear() { - List 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(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(); } diff --git a/src/dorkbox/systemTray/MenuItem.java b/src/dorkbox/systemTray/MenuItem.java index a0bc7be..f00e10c 100644 --- a/src/dorkbox/systemTray/MenuItem.java +++ b/src/dorkbox/systemTray/MenuItem.java @@ -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. + *

+ * NOTE: Maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop Environments. + *

+ * 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; } } diff --git a/src/dorkbox/systemTray/Separator.java b/src/dorkbox/systemTray/Separator.java index f4f5e63..894941e 100644 --- a/src/dorkbox/systemTray/Separator.java +++ b/src/dorkbox/systemTray/Separator.java @@ -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. *

@@ -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); + } } diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 478660f..9db6573 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -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. + *

+ * 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 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 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(); } diff --git a/src/dorkbox/systemTray/Tray.java b/src/dorkbox/systemTray/Tray.java index d99cfcd..f0d36af 100644 --- a/src/dorkbox/systemTray/Tray.java +++ b/src/dorkbox/systemTray/Tray.java @@ -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. - *

- * The maximum length is 64 characters long, and it is not supported on all Operating Systems and Desktop - * Environments. - *

- * 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. *

diff --git a/src/dorkbox/systemTray/gnomeShell/Extension.java b/src/dorkbox/systemTray/gnomeShell/Extension.java index c3f1a6b..72e5f19 100644 --- a/src/dorkbox/systemTray/gnomeShell/Extension.java +++ b/src/dorkbox/systemTray/gnomeShell/Extension.java @@ -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 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 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; diff --git a/src/dorkbox/systemTray/jna/linux/AppIndicator.java b/src/dorkbox/systemTray/jna/linux/AppIndicator.java deleted file mode 100644 index 90ebf65..0000000 --- a/src/dorkbox/systemTray/jna/linux/AppIndicator.java +++ /dev/null @@ -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); -} diff --git a/src/dorkbox/systemTray/jna/linux/FuncCallback.java b/src/dorkbox/systemTray/jna/linux/FuncCallback.java deleted file mode 100644 index fb177c1..0000000 --- a/src/dorkbox/systemTray/jna/linux/FuncCallback.java +++ /dev/null @@ -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); -} diff --git a/src/dorkbox/systemTray/jna/linux/GEventCallback.java b/src/dorkbox/systemTray/jna/linux/GEventCallback.java deleted file mode 100644 index 32e2686..0000000 --- a/src/dorkbox/systemTray/jna/linux/GEventCallback.java +++ /dev/null @@ -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); -} diff --git a/src/dorkbox/systemTray/jna/linux/Glib.java b/src/dorkbox/systemTray/jna/linux/Glib.java deleted file mode 100644 index adb17b2..0000000 --- a/src/dorkbox/systemTray/jna/linux/Glib.java +++ /dev/null @@ -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); -} diff --git a/src/dorkbox/systemTray/jna/linux/Gobject.java b/src/dorkbox/systemTray/jna/linux/Gobject.java deleted file mode 100644 index 5d0ed43..0000000 --- a/src/dorkbox/systemTray/jna/linux/Gobject.java +++ /dev/null @@ -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); - -} diff --git a/src/dorkbox/systemTray/jna/linux/Gtk.java b/src/dorkbox/systemTray/jna/linux/Gtk.java deleted file mode 100644 index f4257ea..0000000 --- a/src/dorkbox/systemTray/jna/linux/Gtk.java +++ /dev/null @@ -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 - *

- * 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 item’s 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 isn’t found or can’t be loaded, the resulting GtkImage will - * display a “broken image” icon. This function never returns NULL, it always returns a valid GtkImage widget. - *

- * 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 item’s 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. - *

- * uses '_' to define which key is the mnemonic - *

- * 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. - *

- * 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 - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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 can’t 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. - *

- * 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 don’t want to use widget again it’s 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 - *

- * 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. - *

- * 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. - *

- * 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 style’s 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); - } -} - diff --git a/src/dorkbox/systemTray/jna/linux/Gtk2.java b/src/dorkbox/systemTray/jna/linux/Gtk2.java deleted file mode 100644 index e6e7db6..0000000 --- a/src/dorkbox/systemTray/jna/linux/Gtk2.java +++ /dev/null @@ -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. - *

- * 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 item’s 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 isn’t found or can’t be loaded, the resulting GtkImage will - * display a “broken image” icon. This function never returns NULL, it always returns a valid GtkImage widget. - *

- * 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 item’s 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. - *

- * uses '_' to define which key is the mnemonic - *

- * 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. - *

- * 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 - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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. - *

- * 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 can’t 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. - *

- * 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 don’t want to use widget again it’s 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 - *

- * 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. - *

- * 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. - *

- * 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 style’s 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); -} diff --git a/src/dorkbox/systemTray/jna/linux/Gtk3.java b/src/dorkbox/systemTray/jna/linux/Gtk3.java deleted file mode 100644 index 66e544b..0000000 --- a/src/dorkbox/systemTray/jna/linux/Gtk3.java +++ /dev/null @@ -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. - *

- * 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. - *

- * 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(). - *

- * 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. - *

- * 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). - *

- * 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. - *

- * 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 widget’s preference for height-for-width management. - *

- * 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. - *

- * 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. - *

- * 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); -} diff --git a/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java b/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java deleted file mode 100644 index e728d27..0000000 --- a/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java +++ /dev/null @@ -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 gtkCallbacks = new LinkedList(); - - // 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 isDispatch = new ThreadLocal() { - @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); - } - } -} diff --git a/src/dorkbox/systemTray/jna/linux/GtkTheme.java b/src/dorkbox/systemTray/jna/linux/GtkTheme.java deleted file mode 100644 index afdd638..0000000 --- a/src/dorkbox/systemTray/jna/linux/GtkTheme.java +++ /dev/null @@ -1,1272 +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 static dorkbox.systemTray.util.CssParser.injectAdditionalCss; -import static dorkbox.systemTray.util.CssParser.removeComments; - -import java.awt.Color; -import java.awt.Rectangle; -import java.awt.Toolkit; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import com.sun.jna.Pointer; -import com.sun.jna.ptr.PointerByReference; - -import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.Tray; -import dorkbox.systemTray.jna.linux.structs.GdkColor; -import dorkbox.systemTray.jna.linux.structs.GdkRGBAColor; -import dorkbox.systemTray.jna.linux.structs.GtkRequisition; -import dorkbox.systemTray.jna.linux.structs.GtkStyle; -import dorkbox.systemTray.jna.linux.structs.PangoRectangle; -import dorkbox.systemTray.util.CssParser; -import dorkbox.systemTray.util.CssParser.Css; -import dorkbox.systemTray.util.CssParser.CssNode; -import dorkbox.systemTray.util.CssParser.Entry; -import dorkbox.util.FileUtil; -import dorkbox.util.MathUtil; -import dorkbox.util.OS; -import dorkbox.util.OSUtil; -import dorkbox.util.process.ShellProcessBuilder; - -/** - * Class to contain all of the methods needed to get the text color from the AppIndicator/GtkStatusIcon menu entry. This is primarily - * used to get the color needed for the checkmark icon. In GTK, the checkmark icon can be defined to be it's OWN color and - * shape, however getting/parsing that would be even significantly more difficult -- so we decided to make the icon the same color - * as the text. - *

- * Additionally, CUSTOM, user theme modifications in ~/.gtkrc-2.0 (for example), will be ignored. - * - * Also note: not all themes have CSS or Theme files!!! - */ -@SuppressWarnings({"deprecation", "WeakerAccess"}) -public -class GtkTheme { - private static final boolean DEBUG = false; - private static final boolean DEBUG_SHOW_CSS = false; - private static final boolean DEBUG_VERBOSE = false; - - // CSS nodes that we care about, in oder of preference from left to right. - private static final - String[] cssNodes = new String[] {".menuitem", ".entry", "*"}; - - public static - Rectangle getPixelTextHeight(String text) { - // have to use pango to get the size of text (for the checkmark size) - Pointer offscreen = Gtk.gtk_offscreen_window_new(); - - // we use the size of "X" as the checkmark - Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic(text); - - Gtk.gtk_container_add(offscreen, item); - - // get the text widget (GtkAccelLabel) from inside the GtkMenuItem - Pointer textLabel = Gtk.gtk_bin_get_child(item); - Pointer pangoLayout = Gtk.gtk_label_get_layout(textLabel); - - // ink pixel size is how much exact space it takes on the screen - PangoRectangle ink = new PangoRectangle(); - - Gtk.pango_layout_get_pixel_extents(pangoLayout, ink.getPointer(), null); - ink.read(); - - Rectangle size = new Rectangle(ink.width, ink.height); - - Gtk.gtk_widget_destroy(item); - Gtk.gtk_widget_destroy(offscreen); - - return size; - } - - public static - Rectangle getLogicalTextHeight(String text) { - // have to use pango to get the size of text (for the checkmark size) - Pointer offscreen = Gtk.gtk_offscreen_window_new(); - - // we use the size of "X" as the checkmark - Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic(text); - - Gtk.gtk_container_add(offscreen, item); - - // get the text widget (GtkAccelLabel) from inside the GtkMenuItem - Pointer textLabel = Gtk.gtk_bin_get_child(item); - Pointer pangoLayout = Gtk.gtk_label_get_layout(textLabel); - - // logical pixel size (ascent + descent) - PangoRectangle logical = new PangoRectangle(); - - Gtk.pango_layout_get_pixel_extents(pangoLayout, null, logical.getPointer()); - logical.read(); - - Rectangle size = new Rectangle(logical.width, logical.height); - - Gtk.gtk_widget_destroy(item); - Gtk.gtk_widget_destroy(offscreen); - - return size; - } - - /** - * @return the size of the GTK menu entry's IMAGE, as best as we can tell, for determining how large of icons to use for the menu entry - */ - public static - int getMenuEntryImageSize() { - final AtomicReference imageHeight = new AtomicReference(); - - GtkEventDispatch.dispatchAndWait(new Runnable() { - @Override - public - void run() { - Pointer offscreen = Gtk.gtk_offscreen_window_new(); - - // get the default icon size for the "paste" icon. - Pointer item = Gtk.gtk_image_menu_item_new_from_stock("gtk-paste", null); - - Gtk.gtk_container_add(offscreen, item); - - PointerByReference r = new PointerByReference(); - Gobject.g_object_get(item, "image", r.getPointer(), null); - - Pointer imageWidget = r.getValue(); - GtkRequisition gtkRequisition = new GtkRequisition(); - Gtk.gtk_widget_size_request(imageWidget, gtkRequisition.getPointer()); - gtkRequisition.read(); - - imageHeight.set(gtkRequisition.height); - } - }); - - int height = imageHeight.get(); - if (height > 0) { - return height; - } - else { - return 16; // who knows? - } - } - - /** - * Gets the system tray indicator size. - * - AppIndicator: will properly scale the image if it's not the correct size - * - GtkStatusIndicator: ?? - */ - public static - int getIndicatorSize(final Class trayType) { - // Linux is similar enough, that it just uses this method - // https://wiki.archlinux.org/index.php/HiDPI - - // 96 DPI is the default - final double defaultDPI = 96.0; - - final AtomicReference screenScale = new AtomicReference(); - final AtomicInteger screenDPI = new AtomicInteger(); - - GtkEventDispatch.dispatchAndWait(new Runnable() { - @Override - public - void run() { - // screen DPI - Pointer screen = Gtk.gdk_screen_get_default(); - if (screen != null) { - // this call makes NO SENSE, but reading the documentation shows it is the CORRECT call. - screenDPI.set((int) Gtk.gdk_screen_get_resolution(screen)); - } - - if (Gtk.isGtk3) { - Pointer window = Gtk.gdk_get_default_root_window(); - if (window != null) { - screenScale.set((double) Gtk3.gdk_window_get_scale_factor(window)); - } - } - } - }); - - // fallback - if (screenDPI.get() == 0) { - // GET THE DPI IN LINUX - // https://wiki.archlinux.org/index.php/Talk:GNOME - Object detectedValue = Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/DPI"); - if (detectedValue instanceof Integer) { - int dpi = ((Integer) detectedValue) / 1024; - if (dpi == -1) { - screenDPI.set((int) defaultDPI); - } - if (dpi < 50) { - // 50 dpi is the minimum value gnome allows - screenDPI.set(50); - } - } - } - - - // check system ENV variables. - if (screenScale.get() == 0) { - String envVar = System.getenv("QT_AUTO_SCREEN_SCALE_FACTOR"); - if (envVar != null) { - try { - screenScale.set(Double.parseDouble(envVar)); - } catch (Exception ignored) { - } - } - } - - // check system ENV variables. - if (screenScale.get() == 0) { - String envVar = System.getenv("QT_SCALE_FACTOR"); - if (envVar != null) { - try { - screenScale.set(Double.parseDouble(envVar)); - } catch (Exception ignored) { - } - } - } - - // check system ENV variables. - if (screenScale.get() == 0) { - String envVar = System.getenv("GDK_SCALE"); - if (envVar != null) { - try { - screenScale.set(Double.parseDouble(envVar)); - } catch (Exception ignored) { - } - } - } - - // check system ENV variables. - if (screenScale.get() == 0) { - String envVar = System.getenv("ELM_SCALE"); - if (envVar != null) { - try { - screenScale.set(Double.parseDouble(envVar)); - } catch (Exception ignored) { - } - } - } - - - - OSUtil.DesktopEnv.Env env = OSUtil.DesktopEnv.get(); - // sometimes the scaling-factor is set - if (env == OSUtil.DesktopEnv.Env.Gnome) { - try { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196); - PrintStream outputStream = new PrintStream(byteArrayOutputStream); - - // gsettings get org.gnome.desktop.interface scaling-factor - final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream); - shellVersion.setExecutable("gsettings"); - shellVersion.addArgument("get"); - shellVersion.addArgument("org.gnome.desktop.interface"); - shellVersion.addArgument("scaling-factor"); - shellVersion.start(); - - String output = ShellProcessBuilder.getOutput(byteArrayOutputStream); - - if (!output.isEmpty()) { - // DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well. - // should be: uint32 0 or something - if (output.contains("uint32")) { - String value = output.substring(output.indexOf("uint") + 7, output.length()); - - // 0 is disabled (no scaling) - // 1 is enabled (default scale) - // 2 is 2x scale - // 3 is 3x scale - // etc - - double scalingFactor = Double.parseDouble(value); - if (scalingFactor >= 1) { - screenScale.set(scalingFactor); - } - - // A setting of 2, 3, etc, which is all you can do with scaling-factor - // To enable HiDPI, use gsettings: - // gsettings set org.gnome.desktop.interface scaling-factor 2 - } - } - } catch (Throwable ignore) { - } - } - else if (OSUtil.DesktopEnv.isKDE()) { - // check the custom KDE override file - try { - File customSettings = new File("/usr/bin/startkde-custom"); - if (customSettings.canRead()) { - List lines = FileUtil.readLines(customSettings); - for (String line : lines) { - String str = "export GDK_SCALE="; - int i = line.indexOf(str); - if (i > -1) { - String scale = line.substring(i + str.length()); - double scalingFactor = Double.parseDouble(scale); - if (scalingFactor >= 1) { - screenScale.set(scalingFactor); - break; - } - } - } - } - } catch (Exception ignored) { - } - } - -// System.err.println("screen scale: " + screenScale.get()); -// System.err.println("screen DPI: " + screenDPI.get()); - - if (OSUtil.DesktopEnv.isKDE()) { - /* - * - * Looking in plasma-framework/src/declarativeimports/core/units.cpp: - // Scale the icon sizes up using the devicePixelRatio - // This function returns the next stepping icon size - // and multiplies the global settings with the dpi ratio. - const qreal ratio = devicePixelRatio(); - - if (ratio < 1.5) { - return size; - } else if (ratio < 2.0) { - return size * 1.5; - } else if (ratio < 2.5) { - return size * 2.0; - } else if (ratio < 3.0) { - return size * 2.5; - } else if (ratio < 3.5) { - return size * 3.0; - } else { - return size * ratio; - } -My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 factor existing. Is it reasonable? Wouldn't it make more sense to use the factor the closest to the ratio rather than what is done here? - - */ - - File mainFile = new File("/usr/share/plasma/plasmoids/org.kde.plasma.private.systemtray/contents/config/main.xml"); - if (mainFile.canRead()) { - List lines = FileUtil.readLines(mainFile); - boolean found = false; - int index = 0; - for (final String line : lines) { - if (line.contains("")) { - found = true; - // have to get the "default" line value - } - - String str = ""; - if (found && (index = line.indexOf(str)) > -1) { - // this is our line. now get the value. - String substring = line.substring(index + str.length(), line.indexOf("", index)); - - if (MathUtil.isInteger(substring)) { - // Default icon size for the systray icons, it's an enum which values mean, - // Small, SmallMedium, Medium, Large, Huge, Enormous respectively. - // On low DPI systems they correspond to : - // 16, 22, 32, 48, 64, 128 pixels. - // On high DPI systems those values would be scaled up, depending on the DPI. - int imageSize = 0; - int imageSizeEnum = Integer.parseInt(substring); - switch (imageSizeEnum) { - case 0: - imageSize = 16; - break; - case 1: - imageSize = 22; - break; - case 2: - imageSize = 32; - break; - case 3: - imageSize = 48; - break; - case 4: - imageSize = 64; - break; - case 5: - imageSize = 128; - break; - } - - if (imageSize > 0) { - double scaleRatio = screenDPI.get() / defaultDPI; - - return (int) (scaleRatio * imageSize); - } - } - } - } - } - } - else { - if (OSUtil.Linux.isUbuntu() && env == OSUtil.DesktopEnv.Env.Unity) { - // if we measure on ubuntu unity using a screen shot (using swing, so....) , the max size was 24, HOWEVER this goes from - // the top->bottom of the indicator bar -- and since it was swing, it uses a different rendering method and it (honestly) - // looks weird, because there is no padding for the icon. The official AppIndicator size is hardcoded... - // http://bazaar.launchpad.net/~indicator-applet-developers/libindicator/trunk.16.10/view/head:/libindicator/indicator-image-helper.c - - return 22; - } - else { - // xfce is easy, because it's not a GTK setting for the size (xfce notification area maximum icon size) - if (env == OSUtil.DesktopEnv.Env.XFCE) { - String properties = OSUtil.DesktopEnv.queryXfce("xfce4-panel", null); - List propertiesAsList = Arrays.asList(properties.split(OS.LINE_SEPARATOR)); - for (String prop : propertiesAsList) { - if (prop.startsWith("/plugins/") && prop.endsWith("/size-max")) { - // this is the property we are looking for (we just don't know which panel it's on) - - String size = OSUtil.DesktopEnv.queryXfce("xfce4-panel", prop); - try { - return Integer.parseInt(size); - } catch (Exception e) { - SystemTray.logger.error("Unable to get XFCE notification panel size for channel '{}', property '{}'", - "xfce4-panel", prop, e); - } - } - } - - // default... - return 22; - } - - - // try to use GTK to get the tray icon size - final AtomicInteger traySize = new AtomicInteger(); - - GtkEventDispatch.dispatchAndWait(new Runnable() { - @Override - public - void run() { - Pointer screen = Gtk.gdk_screen_get_default(); - Pointer settings = null; - - if (screen != null) { - settings = Gtk.gtk_settings_get_for_screen(screen); - } - - if (settings != null) { - PointerByReference pointer = new PointerByReference(); - - // https://wiki.archlinux.org/index.php/GTK%2B - // To use smaller icons, use a line like this: - // gtk-icon-sizes = "panel-menu=16,16:panel=16,16:gtk-menu=16,16:gtk-large-toolbar=16,16:gtk-small-toolbar=16,16:gtk-button=16,16" - - // this gets icon sizes. On XFCE, ubuntu, it returns "panel-menu-bar=24,24" - // NOTE: gtk-icon-sizes is deprecated and ignored since GTK+ 3.10. - - // A list of icon sizes. The list is separated by colons, and item has the form: size-name = width , height - Gobject.g_object_get(settings, "gtk-icon-sizes", pointer.getPointer(), null); - - Pointer value = pointer.getValue(); - if (value != null) { - String iconSizes = value.getString(0); - String[] strings = new String[] {"panel-menu-bar=", "panel=", "gtk-large-toolbar=", "gtk-small-toolbar="}; - for (String var : strings) { - int i = iconSizes.indexOf(var); - if (i >= 0) { - String size = iconSizes.substring(i + var.length(), iconSizes.indexOf(",", i)); - - if (MathUtil.isInteger(size)) { - traySize.set(Integer.parseInt(size)); - return; - } - } - } - } - } - } - }); - - int i = traySize.get(); - if (i != 0) { - return i; - } - } - } - - // sane default - return 22; - } - - /** - * @return the widget color of text for the current theme, or black. It is important that this is called AFTER GTK has been initialized. - */ - public static - Color getTextColor() { - // NOTE: when getting CSS, we redirect STDERR to null (via GTK), so that we won't spam the console if there are parse errors. - // this is a horrid hack, but the only way to work around the errors we have no control over. The parse errors, if bad enough - // just mean that we are unable to get the CSS as we want. - - // these methods are from most accurate (but limited in support) to compatible across Linux OSes.. Strangely enough, GTK makes - // this information insanely difficult to get. - final AtomicReference color = new AtomicReference(null); - GtkEventDispatch.dispatchAndWait(new Runnable() { - @SuppressWarnings("UnusedAssignment") - @Override - public - void run() { - Color c; - - // see if we can get the info via CSS properties (> GTK+ 3.2 uses an API, GTK2 gets it from disk). - // This is often the BEST way to get information, since GTK **DOES NOT** make it easy to get widget information BEFORE - // the widget is realized -- which in our case, we must do. - c = getColorFromCss(); - if (c != null) { - if (DEBUG) { - System.err.println("Got from CSS"); - } - color.set(c); - return; - } - - - // try to get via the color scheme. - c = getFromColorScheme(); - if (c != null) { - if (DEBUG) { - System.err.println("Got from color scheme"); - } - color.set(c); - return; - } - - - // if we get here, this means that there was NO "gtk-color-scheme" value in the theme file. - // This usually happens when the theme does not have @fg_color (for example), but instead has each color explicitly - // defined for every widget instance in the theme file. Old/bizzare themes tend to do it this way... - if (Gtk.isGtk2) { - c = getFromGtk2ThemeText(); - if (c != null) { - if (DEBUG) { - System.err.println("Got from gtk2 color theme file"); - } - color.set(c); - return; - } - } - - - // the following methods all require an offscreen widget to get the style information from. - - // create an off-screen widget (don't forget to destroy everything!) - Pointer offscreen = Gtk.gtk_offscreen_window_new(); - Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("a"); - - Gtk.gtk_container_add(offscreen, item); - Gtk.gtk_widget_show_all(item); - - // Try to get via RC style... Sometimes this works (sometimes it does not...) - { - Pointer style = Gtk.gtk_rc_get_style(item); - - GdkColor gdkColor = new GdkColor(); - boolean success = false; - - success = Gtk.gtk_style_lookup_color(style, "menu_fg_color", gdkColor.getPointer()); - if (!success) { - success = Gtk.gtk_style_lookup_color(style, "text_color", gdkColor.getPointer()); - if (success) { - System.err.println("a"); - } - } - if (!success) { - success = Gtk.gtk_style_lookup_color(style, "theme_text_color", gdkColor.getPointer()); - if (success) { - System.err.println("a"); - } - } - if (success) { - c = gdkColor.getColor(); - } - } - - if (c != null) { - if (DEBUG) { - System.err.println("Got from gtk offscreen gtk_style_lookup_color"); - } - color.set(c); - Gtk.gtk_widget_destroy(item); - return; - } - - - - if (Gtk.isGtk3) { - Pointer context = Gtk3.gtk_widget_get_style_context(item); - - GdkRGBAColor gdkColor = new GdkRGBAColor(); - boolean success = false; - - success = Gtk3.gtk_style_context_lookup_color(context, "fg_color", gdkColor.getPointer()); - if (!success) { - success = Gtk3.gtk_style_context_lookup_color(context, "text_color", gdkColor.getPointer()); - } - if (!success) { - success = Gtk3.gtk_style_context_lookup_color(context, "menu_fg_color", gdkColor.getPointer()); - } - - if (!success) { - success = Gtk3.gtk_style_context_lookup_color(context, "color", gdkColor.getPointer()); - } - - if (success) { - c = gdkColor.getColor(); - } - } - - if (c != null) { - color.set(c); - if (DEBUG) { - System.err.println("Got from gtk3 offscreen gtk_widget_get_style_context"); - } - Gtk.gtk_widget_destroy(item); - return; - } - - // this doesn't always work... - GtkStyle.ByReference style = Gtk.gtk_widget_get_style(item); - color.set(style.text[Gtk.State.NORMAL].getColor()); - - if (DEBUG) { - System.err.println("Got from gtk gtk_widget_get_style"); - } - - Gtk.gtk_widget_destroy(item); - } - }); - - - Color c = color.get(); - if (c != null) { - if (DEBUG) { - System.err.println("COLOR FOUND: " + c); - } - return c; - } - - 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)."); - - // who knows WHAT the color is supposed to be. This is just a "best guess" default value. - return Color.BLACK; - } - - /** - * get the color we are interested in via raw CSS parsing. This is specifically to get the color of the text of the - * appindicator/gtk-status-icon menu. - *

- * > GTK+ 3.2 uses an API, GTK2 gets it from disk - * - * @return the color string, parsed from CSS, - */ - private static - Color getColorFromCss() { - Css css = getCss(); - if (css != null) { - if (DEBUG_SHOW_CSS) { - System.err.println(css); - } - - try { - // collect a list of all of the sections that have what we are interested in. - List sections = CssParser.getSections(css, cssNodes, null); - List colorStrings = CssParser.getAttributeFromSections(sections, "color", true); - - String colorString = CssParser.selectMostRelevantAttribute(cssNodes, colorStrings); - - if (colorString != null) { - if (colorString.startsWith("@")) { - // it's a color definition - String colorSubString = css.getColorDefinition(colorString.substring(1)); - return parseColor(colorSubString); - } - else { - return parseColor(colorString); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - return null; - } - - /** - * @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized. - */ - public static - Css getCss() { - String css; - if (Gtk.isLoaded && Gtk.isGtk3) { - final AtomicReference css_ = new AtomicReference(null); - - GtkEventDispatch.dispatchAndWait(new Runnable() { - @Override - public - void run() { - String themeName = getThemeName(); - - if (themeName != null) { - Pointer value = Gtk3.gtk_css_provider_get_named(themeName, null); - if (value != null) { - // we have the css provider! - - // NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them - Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null); - - css_.set(Gtk3.gtk_css_provider_to_string(value)); - - Glib.g_log_set_default_handler(orig, null); - } - } - else { - Pointer value = Gtk3.gtk_css_provider_get_default(); - if (value != null) { - // we have the css provider! - - // NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them - Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null); - - css_.set(Gtk3.gtk_css_provider_to_string(value)); - - Glib.g_log_set_default_handler(orig, null); - } - } - } - }); - - // will be either the string, or null. - css = css_.get(); - } - else { - // GTK2 has to get the GTK3 theme text a different way (parsing it from disk). SOMETIMES, the app must be GTK2, even though - // the system is GTK3. This works around the API restriction if we are an APP in GTK2 mode. This is not done ALL the time, - // because this is not as accurate as using the GTK3 API. - // This can also be a requirement if GTK is not loaded - css = getGtk3ThemeCssViaFile(); - } - - return CssParser.parse(css); - } - - /** - * this works for GtkStatusIcon menus. - * - * @return the menu_fg/fg/text color from gtk-color-scheme or null - */ - public static - Color getFromColorScheme() { - Pointer screen = Gtk.gdk_screen_get_default(); - Pointer settings = null; - - if (screen != null) { - settings = Gtk.gtk_settings_get_for_screen(screen); - } - - if (settings != null) { - // see if we can get the info we want the EASY way (likely only when GTK+ 2 is used, but can be < GTK+ 3.2)... - - // been deprecated since version 3.8 - PointerByReference pointer = new PointerByReference(); - Gobject.g_object_get(settings, "gtk-color-scheme", pointer.getPointer(), null); - - - // A palette of named colors for use in themes. The format of the string is - // name1: color1 - // name2: color2 - // - // Color names must be acceptable as identifiers in the gtkrc syntax, and color specifications must be in the format - // accepted by gdk_color_parse(). - // - // Note that due to the way the color tables from different sources are merged, color specifications will be converted - // to hexadecimal form when getting this property. - // - // Starting with GTK+ 2.12, the entries can alternatively be separated by ';' instead of newlines: - // name1: color1; name2: color2; ... - // - // GtkSettings:gtk-color-scheme has been deprecated since version 3.8 and should not be used in newly-written code. - // Color scheme support was dropped and is no longer supported. You can still set this property, but it will be ignored. - - - Pointer value = pointer.getValue(); - if (value != null) { - String s = value.getString(0); - if (!s.isEmpty()) { - if (DEBUG) { - System.out.println("\t string: " + s); - } - - // Note: these are the values on my system when forcing GTK+ 2 (XUbuntu 16.04) with GtkStatusIcon and Aidwata theme - // bg_color_dark: #686868686868 - // fg_color: #3c3c3c3c3c3c - // fm_color: #f7f7f7f7f7f7 - // selected_fg_color: #ffffffffffff - // panel_bg: #686868686868 - // text_color: #212121212121 - // text_color_dark: #ffffffffffff - // tooltip_bg_color: #000000000000 - // link_color: #2d2d7171b8b8 - // tooltip_fg_color: #e1e1e1e1e1e1 - // base_color: #fcfcfcfcfcfc - // bg_color: #cececececece - // selected_bg_color: #39398e8ee7e7 - - // list of colors, in order of importance, that we want to parse. - String colors[] = new String[] {"menu_fg_color", "fg_color", "text_color"}; - - for (String colorName : colors) { - int i = 0; - while (i != -1) { - i = s.indexOf(colorName, i); - if (i >= 0) { - try { - // the color will ALWAYS be in hex notation - - // it is also possible to be separated by ; instead of newline - int endIndex = s.indexOf(';', i); - if (endIndex == -1) { - endIndex = s.indexOf('\n', i); - } - - if (s.charAt(i - 1) == '_') { - i = endIndex; - continue; - } - - int startIndex = s.indexOf('#', i); - String colorString = s.substring(startIndex, endIndex) - .trim(); - - if (DEBUG_VERBOSE) { - System.out.println("Color string: " + colorString); - } - return parseColor(colorString); - } catch (Exception ignored) { - } - } - } - } - } - } - } - - return null; - } - - /** - * Checks in the following locations for the current GTK3 theme. - *

- * /usr/share/themes - * /opt/gnome/share/themes - */ - private static - String getGtk3ThemeCssViaFile() { - File themeDirectory = getThemeDirectory(true); - - if (themeDirectory == null) { - return null; - } - - File gtkFile = new File(themeDirectory, "gtk.css"); - try { - StringBuilder stringBuilder = new StringBuilder((int) (gtkFile.length())); - FileUtil.read(gtkFile, stringBuilder); - - removeComments(stringBuilder); - - // only comments in the file - if (stringBuilder.length() < 2) { - return null; - } - - injectAdditionalCss(themeDirectory, stringBuilder); - - return stringBuilder.toString(); - } catch (IOException e) { - // cant read the file or something else. - if (SystemTray.DEBUG) { - SystemTray.logger.error("Error getting RAW GTK3 theme file.", e); - } - } - - return null; - } - - /** - * @return the discovered fg[NORMAL] or text[NORMAL] color for this theme or null - */ - public static - Color getFromGtk2ThemeText() { - String gtk2ThemeText = getGtk2ThemeText(); - - if (gtk2ThemeText != null) { - String[] colorText = new String[] {"fg[NORMAL]", "text[NORMAL]"}; - for (String text : colorText) { - int i = 0; - while (i != -1) { - i = gtk2ThemeText.indexOf(text, i); - if (i != -1) { - if (i > 0 && gtk2ThemeText.charAt(i - 1) != '_') { - i += text.length(); - continue; - } - - - int j = gtk2ThemeText.indexOf("=", i); - if (j != -1) { - int lineEnd = gtk2ThemeText.indexOf('\n', j); - - if (lineEnd != -1) { - String colorName = gtk2ThemeText.substring(j + 1, lineEnd) - .trim(); - - colorName = colorName.replaceAll("\"", ""); - return parseColor(colorName); - } - } - } - } - } - } - - return null; - } - - - /** - * Checks in the following locations for the current GTK2 theme. - *

- * /usr/share/themes - * /opt/gnome/share/themes - */ - private static - String getGtk2ThemeText() { - File themeDirectory = getThemeDirectory(false); - - if (themeDirectory == null) { - return null; - } - - - // ie: /usr/share/themes/Numix/gtk-2.0/gtkrc - File gtkFile = new File(themeDirectory, "gtkrc"); - - try { - StringBuilder stringBuilder = new StringBuilder((int) (gtkFile.length())); - FileUtil.read(gtkFile, stringBuilder); - - removeComments(stringBuilder); - - // only comments in the file - if (stringBuilder.length() < 2) { - return null; - } - - return stringBuilder.toString(); - } catch (IOException ignored) { - // cant read the file or something else. - } - - return null; - } - - - /** - * Figures out what the directory is for the specified type of GTK theme files (css/gtkrc/etc) - * - * @param gtk3 true if you want to look for the GTK3 theme dir, false if you want the GTK2 theme dir - * - * @return the directory or null if it cannot be found - */ - public static - File getThemeDirectory(boolean gtk3) { - String themeName = getThemeName(); - - if (themeName == null) { - return null; - } - - String gtkType; - if (gtk3) { - gtkType = "gtk-3.0"; - } - else { - gtkType = "gtk-2.0"; - } - - - String[] dirs = new String[] {"/usr/share/themes", "/opt/gnome/share/themes"}; - - // ie: /usr/share/themes - for (String dirName : dirs) { - File themesDir = new File(dirName); - - File[] themeDirs = themesDir.listFiles(); - if (themeDirs != null) { - // ie: /usr/share/themes/Numix - for (File themeDir : themeDirs) { - File[] files1 = themeDir.listFiles(); - if (files1 != null) { - boolean isCorrectTheme; - - File themeIndexFile = new File(themeDir, "index.theme"); - try { - List read = FileUtil.read(themeIndexFile, false); - for (String s : read) { - if (s.startsWith("GtkTheme=")) { - String calculatedThemeName = s.substring("GtkTheme=".length()); - - isCorrectTheme = calculatedThemeName.equals(themeName); - - if (isCorrectTheme) { - // ie: /usr/share/themes/Numix/gtk-3.0/gtk.css - // the DARK variant is only used by some apps. The dark variant is NOT SYSTEM-WIDE! - return new File(themeDir, gtkType); - } - - break; - } - } - } catch (IOException ignored) { - } - } - } - } - } - - return null; - } - - /** - * Parses out the color from a color: - *

- * - the word "transparent" - * - hex 12 digit #ffffaaaaffff - * - hex 6 digit #ffaaff - * - hex 3 digit #faf - * - rgb(r, g, b) rgb(33, 33, 33) - * - rgb(r, g, b) rgb(.6, .3, .3) - * - rgb(r%, g%, b%) rgb(10%, 20%, 30%) - * - rgba(r, g, b, a) rgb(33, 33, 33, 0.53) - * - rgba(r, g, b, a) rgb(.33, .33, .33, 0.53) - * - rgba(r, g, b, a) rgb(10%, 20%, 30%, 0.53) - *

- * Notes: - * - rgb(), when an int, is between 0-255 - * - rgb(), when a float, is between 0.0-1.0 - * - rgb(), when a percent, is between 0-100 - * - alpha is always a float - * - * @return the parsed color - */ - @SuppressWarnings("Duplicates") - private static - Color parseColor(String colorString) { - if (colorString == null) { - return null; - } - - int red = 0; - int green = 0; - int blue = 0; - int alpha = 255; - - if (colorString.startsWith("#")) { - colorString = colorString.substring(1); - - if (colorString.length() > 11) { - red = Integer.parseInt(colorString.substring(0, 4), 16); - green = Integer.parseInt(colorString.substring(4, 8), 16); - blue = Integer.parseInt(colorString.substring(8), 16); - - // Have to convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255 - red = red & 0x0000FFFF; - green = green & 0x0000FFFF; - blue = blue & 0x0000FFFF; - - red = (red >> 8) & 0xFF; - green = (green >> 8) & 0xFF; - blue = (blue >> 8) & 0xFF; - } - else if (colorString.length() > 5) { - red = Integer.parseInt(colorString.substring(0, 2), 16); - green = Integer.parseInt(colorString.substring(2, 4), 16); - blue = Integer.parseInt(colorString.substring(4), 16); - } - else { - red = Integer.parseInt(colorString.substring(0, 1), 16); - green = Integer.parseInt(colorString.substring(1, 2), 16); - blue = Integer.parseInt(colorString.substring(2), 16); - } - } - else if (colorString.startsWith("rgba")) { - colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')')); - String[] split = colorString.split(","); - - String trim1 = split[0].trim(); - String trim2 = split[1].trim(); - String trim3 = split[2].trim(); - String trim4 = split[3].trim(); - - if (colorString.contains("%")) { - trim1 = trim1.replace("%", ""); - trim2 = trim2.replace("%", ""); - trim3 = trim3.replace("%", ""); - - red = Integer.parseInt(trim1) * 255; - green = Integer.parseInt(trim2) * 255; - blue = Integer.parseInt(trim3) * 255; - } - else if (colorString.contains(".")) { - red = (int) (Float.parseFloat(trim1) * 255); - green = (int) (Float.parseFloat(trim2) * 255); - blue = (int) (Float.parseFloat(trim3) * 255); - } - else { - red = Integer.parseInt(trim1); - green = Integer.parseInt(trim2); - blue = Integer.parseInt(trim3); - } - - float alphaF = Float.parseFloat(trim4); - alpha = (int) (alphaF * 255); - } - else if (colorString.startsWith("rgb")) { - colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')')); - String[] split = colorString.split(","); - - String trim1 = split[0].trim(); - String trim2 = split[1].trim(); - String trim3 = split[2].trim(); - - if (colorString.contains("%")) { - trim1 = trim1.replace("%", ""); - trim2 = trim2.replace("%", ""); - trim3 = trim3.replace("%", ""); - - red = Integer.parseInt(trim1) * 255; - green = Integer.parseInt(trim2) * 255; - blue = Integer.parseInt(trim3) * 255; - } - else if (colorString.contains(".")) { - red = (int) (Float.parseFloat(trim1) * 255); - green = (int) (Float.parseFloat(trim2) * 255); - blue = (int) (Float.parseFloat(trim3) * 255); - } - else { - red = Integer.parseInt(trim1); - green = Integer.parseInt(trim2); - blue = Integer.parseInt(trim3); - } - } - else if (colorString.contains("transparent")) { - alpha = 0; - } - else { - int index = colorString.indexOf(";"); - if (index > 0) { - colorString = colorString.substring(0, index); - } - colorString = colorString.replaceAll("\"", ""); - colorString = colorString.replaceAll("'", ""); - - // maybe it's just a "color" description, such as "red"? - try { - return Color.decode(colorString); - } catch (Exception e) { - return null; - } - } - - return new Color(red, green, blue, alpha); - } - - /** - * https://wiki.archlinux.org/index.php/GTK%2B - *

- * gets the name of the currently loaded theme - * GTK+ 2: - * ~/.gtkrc-2.0 - * gtk-icon-theme-name = "Adwaita" - * gtk-theme-name = "Adwaita" - * gtk-font-name = "DejaVu Sans 11" - *

- *

- * GTK+ 3: - * $XDG_CONFIG_HOME/gtk-3.0/settings.ini - * [Settings] - * gtk-icon-theme-name = Adwaita - * gtk-theme-name = Adwaita - * gtk-font-name = DejaVu Sans 11 - *

- *

- * Note: The icon theme name is the name defined in the theme's index file, not the name of its directory. - *

- * directories: - * /usr/share/themes - * /opt/gnome/share/themes - *

- * GTK+ 2 user specific: ~/.gtkrc-2.0 - * GTK+ 2 system wide: /etc/gtk-2.0/gtkrc - *

- * GTK+ 3 user specific: $XDG_CONFIG_HOME/gtk-3.0/settings.ini, or $HOME/.config/gtk-3.0/settings.ini if $XDG_CONFIG_HOME is not set - * GTK+ 3 system wide: /etc/gtk-3.0/settings.ini - * - * @return the theme name, or null if it cannot find it. - */ - public static - String getThemeName() { - final AtomicReference themeName = new AtomicReference(null); - - GtkEventDispatch.dispatchAndWait(new Runnable() { - @Override - public - void run() { - Pointer screen = Gtk.gdk_screen_get_default(); - Pointer settings = null; - - if (screen != null) { - settings = Gtk.gtk_settings_get_for_screen(screen); - } - - if (settings != null) { - PointerByReference pointer = new PointerByReference(); - Gobject.g_object_get(settings, "gtk-theme-name", pointer.getPointer(), null); - - Pointer value = pointer.getValue(); - if (value != null) { - themeName.set(value.getString(0)); - } - } - - if (DEBUG) { - System.err.println("Theme name: " + themeName); - } - } - }); - - // will be either the string, or null. - return themeName.get(); - - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/AppIndicatorInstanceStruct.java b/src/dorkbox/systemTray/jna/linux/structs/AppIndicatorInstanceStruct.java deleted file mode 100644 index 4d97eb1..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/AppIndicatorInstanceStruct.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("parent", "priv"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GObjectStruct.java b/src/dorkbox/systemTray/jna/linux/structs/GObjectStruct.java deleted file mode 100644 index 14d74d5..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GObjectStruct.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("g_type_instance", "ref_count", "qdata"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GParamSpecStruct.java b/src/dorkbox/systemTray/jna/linux/structs/GParamSpecStruct.java deleted file mode 100644 index 62daede..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GParamSpecStruct.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("g_type_instance", "name"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GTypeInstanceStruct.java b/src/dorkbox/systemTray/jna/linux/structs/GTypeInstanceStruct.java deleted file mode 100644 index 6f27fbb..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GTypeInstanceStruct.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("g_class"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GdkColor.java b/src/dorkbox/systemTray/jna/linux/structs/GdkColor.java deleted file mode 100644 index c22a18a..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GdkColor.java +++ /dev/null @@ -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 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 {} -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GdkEventButton.java b/src/dorkbox/systemTray/jna/linux/structs/GdkEventButton.java deleted file mode 100644 index e74510d..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GdkEventButton.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GdkRGBAColor.java b/src/dorkbox/systemTray/jna/linux/structs/GdkRGBAColor.java deleted file mode 100644 index 9729496..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GdkRGBAColor.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("red", "green", "blue", "alpha"); - } - - - public - class ByValue extends GdkRGBAColor implements Structure.ByValue {} - - - public - class ByReference extends GdkRGBAColor implements Structure.ByReference {} -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GtkRequisition.java b/src/dorkbox/systemTray/jna/linux/structs/GtkRequisition.java deleted file mode 100644 index faac052..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GtkRequisition.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("width", "height"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/GtkStyle.java b/src/dorkbox/systemTray/jna/linux/structs/GtkStyle.java deleted file mode 100644 index b0b99c6..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/GtkStyle.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("fg", - "bg", - "light", - "dark", - "mid", - "text", - "base", - "text_aa", - "black", - "white", - "font_desc", - "xthickness", - "ythickness", - "background"); - } -} diff --git a/src/dorkbox/systemTray/jna/linux/structs/PangoRectangle.java b/src/dorkbox/systemTray/jna/linux/structs/PangoRectangle.java deleted file mode 100644 index 1f58343..0000000 --- a/src/dorkbox/systemTray/jna/linux/structs/PangoRectangle.java +++ /dev/null @@ -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 getFieldOrder() { - return Arrays.asList("x", "y", "width", "height"); - } -} diff --git a/src/dorkbox/systemTray/nativeUI/NativeUI.java b/src/dorkbox/systemTray/nativeUI/NativeUI.java deleted file mode 100644 index 2c300af..0000000 --- a/src/dorkbox/systemTray/nativeUI/NativeUI.java +++ /dev/null @@ -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. - *

- * 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). - *

- * 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 {} diff --git a/src/dorkbox/systemTray/peer/MenuItemPeer.java b/src/dorkbox/systemTray/peer/MenuItemPeer.java index aa45c0d..db57442 100644 --- a/src/dorkbox/systemTray/peer/MenuItemPeer.java +++ b/src/dorkbox/systemTray/peer/MenuItemPeer.java @@ -31,4 +31,6 @@ interface MenuItemPeer extends EntryPeer { void setCallback(MenuItem menuItem); void setShortcut(MenuItem menuItem); + + void setTooltip(MenuItem menuItem); } diff --git a/src/dorkbox/systemTray/jna/linux/GCallback.java b/src/dorkbox/systemTray/peer/SeparatorPeer.java similarity index 64% rename from src/dorkbox/systemTray/jna/linux/GCallback.java rename to src/dorkbox/systemTray/peer/SeparatorPeer.java index 53344e0..6898b6d 100644 --- a/src/dorkbox/systemTray/jna/linux/GCallback.java +++ b/src/dorkbox/systemTray/peer/SeparatorPeer.java @@ -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 { } diff --git a/src/dorkbox/systemTray/swingUI/SwingUI.java b/src/dorkbox/systemTray/swingUI/SwingUI.java deleted file mode 100644 index 9eead8d..0000000 --- a/src/dorkbox/systemTray/swingUI/SwingUI.java +++ /dev/null @@ -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. - *

- * 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). - *

- * 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 {} diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenu.java b/src/dorkbox/systemTray/ui/awt/AwtMenu.java similarity index 96% rename from src/dorkbox/systemTray/nativeUI/AwtMenu.java rename to src/dorkbox/systemTray/ui/awt/AwtMenu.java index 55e5e22..98f07e4 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenu.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenu.java @@ -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() { diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenuItem.java b/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java similarity index 74% rename from src/dorkbox/systemTray/nativeUI/AwtMenuItem.java rename to src/dorkbox/systemTray/ui/awt/AwtMenuItem.java index b4385fc..5ca7523 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenuItem.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenuItem.java @@ -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 diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenuItemCheckbox.java b/src/dorkbox/systemTray/ui/awt/AwtMenuItemCheckbox.java similarity index 71% rename from src/dorkbox/systemTray/nativeUI/AwtMenuItemCheckbox.java rename to src/dorkbox/systemTray/ui/awt/AwtMenuItemCheckbox.java index 1d14512..50e3182 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenuItemCheckbox.java @@ -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); diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenuItemSeparator.java b/src/dorkbox/systemTray/ui/awt/AwtMenuItemSeparator.java similarity index 97% rename from src/dorkbox/systemTray/nativeUI/AwtMenuItemSeparator.java rename to src/dorkbox/systemTray/ui/awt/AwtMenuItemSeparator.java index 76b6d68..459ccf0 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenuItemSeparator.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenuItemSeparator.java @@ -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; diff --git a/src/dorkbox/systemTray/nativeUI/AwtMenuItemStatus.java b/src/dorkbox/systemTray/ui/awt/AwtMenuItemStatus.java similarity index 98% rename from src/dorkbox/systemTray/nativeUI/AwtMenuItemStatus.java rename to src/dorkbox/systemTray/ui/awt/AwtMenuItemStatus.java index 870f32f..e3ee146 100644 --- a/src/dorkbox/systemTray/nativeUI/AwtMenuItemStatus.java +++ b/src/dorkbox/systemTray/ui/awt/AwtMenuItemStatus.java @@ -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; diff --git a/src/dorkbox/systemTray/nativeUI/_AwtTray.java b/src/dorkbox/systemTray/ui/awt/_AwtTray.java similarity index 90% rename from src/dorkbox/systemTray/nativeUI/_AwtTray.java rename to src/dorkbox/systemTray/ui/awt/_AwtTray.java index ff7dabb..883233c 100644 --- a/src/dorkbox/systemTray/nativeUI/_AwtTray.java +++ b/src/dorkbox/systemTray/ui/awt/_AwtTray.java @@ -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() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java b/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java similarity index 69% rename from src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java rename to src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java index 7403c0d..72b69c9 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkBaseMenuItem.java @@ -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. + *

+ * 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. + *

+ * 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; } } diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenu.java b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java similarity index 77% rename from src/dorkbox/systemTray/nativeUI/GtkMenu.java rename to src/dorkbox/systemTray/ui/gtk/GtkMenu.java index f911061..2de5462 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenu.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenu.java @@ -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 diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java similarity index 57% rename from src/dorkbox/systemTray/nativeUI/GtkMenuItem.java rename to src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java index 70110a1..f68277d 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItem.java @@ -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; } diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java similarity index 75% rename from src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java rename to src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java index 1a130c9..222bfe2 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemCheckbox.java @@ -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; } diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java similarity index 76% rename from src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java rename to src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java index 1cde789..bbd16fe 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemSeparator.java @@ -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); } diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java similarity index 80% rename from src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java rename to src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java index a85241d..5ea179e 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java +++ b/src/dorkbox/systemTray/ui/gtk/GtkMenuItemStatus.java @@ -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); } }); diff --git a/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java similarity index 83% rename from src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java rename to src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java index abf6d07..b9efc95 100644 --- a/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_AppIndicatorNativeTray.java @@ -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() { diff --git a/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java similarity index 78% rename from src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java rename to src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java index 994da4b..ee4b95f 100644 --- a/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java +++ b/src/dorkbox/systemTray/ui/gtk/_GtkStatusIconNativeTray.java @@ -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 diff --git a/src/dorkbox/systemTray/swingUI/SwingMenu.java b/src/dorkbox/systemTray/ui/swing/SwingMenu.java similarity index 98% rename from src/dorkbox/systemTray/swingUI/SwingMenu.java rename to src/dorkbox/systemTray/ui/swing/SwingMenu.java index a234ec0..59350ea 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenu.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenu.java @@ -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 */ diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItem.java b/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java similarity index 80% rename from src/dorkbox/systemTray/swingUI/SwingMenuItem.java rename to src/dorkbox/systemTray/ui/swing/SwingMenuItem.java index 75b9f8b..555b3ad 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItem.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenuItem.java @@ -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() { diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java b/src/dorkbox/systemTray/ui/swing/SwingMenuItemCheckbox.java similarity index 85% rename from src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java rename to src/dorkbox/systemTray/ui/swing/SwingMenuItemCheckbox.java index 848264f..a72e590 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenuItemCheckbox.java @@ -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); + } } - } + }); } }; diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java b/src/dorkbox/systemTray/ui/swing/SwingMenuItemSeparator.java similarity index 97% rename from src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java rename to src/dorkbox/systemTray/ui/swing/SwingMenuItemSeparator.java index 330273e..ebf0d0a 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemSeparator.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenuItemSeparator.java @@ -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; diff --git a/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java b/src/dorkbox/systemTray/ui/swing/SwingMenuItemStatus.java similarity index 98% rename from src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java rename to src/dorkbox/systemTray/ui/swing/SwingMenuItemStatus.java index 8b76e32..e1bb46c 100644 --- a/src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java +++ b/src/dorkbox/systemTray/ui/swing/SwingMenuItemStatus.java @@ -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; diff --git a/src/dorkbox/systemTray/swingUI/SwingUIFactory.java b/src/dorkbox/systemTray/ui/swing/SwingUIFactory.java similarity index 98% rename from src/dorkbox/systemTray/swingUI/SwingUIFactory.java rename to src/dorkbox/systemTray/ui/swing/SwingUIFactory.java index 5bbff7c..ec28c3c 100644 --- a/src/dorkbox/systemTray/swingUI/SwingUIFactory.java +++ b/src/dorkbox/systemTray/ui/swing/SwingUIFactory.java @@ -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; diff --git a/src/dorkbox/systemTray/swingUI/TrayPopup.java b/src/dorkbox/systemTray/ui/swing/TrayPopup.java similarity index 98% rename from src/dorkbox/systemTray/swingUI/TrayPopup.java rename to src/dorkbox/systemTray/ui/swing/TrayPopup.java index e5b37a9..072478e 100644 --- a/src/dorkbox/systemTray/swingUI/TrayPopup.java +++ b/src/dorkbox/systemTray/ui/swing/TrayPopup.java @@ -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) { } }); diff --git a/src/dorkbox/systemTray/swingUI/_SwingTray.java b/src/dorkbox/systemTray/ui/swing/_SwingTray.java similarity index 88% rename from src/dorkbox/systemTray/swingUI/_SwingTray.java rename to src/dorkbox/systemTray/ui/swing/_SwingTray.java index b0350bf..0255957 100644 --- a/src/dorkbox/systemTray/swingUI/_SwingTray.java +++ b/src/dorkbox/systemTray/ui/swing/_SwingTray.java @@ -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() { diff --git a/src/dorkbox/systemTray/util/CssParser.java b/src/dorkbox/systemTray/util/CssParser.java deleted file mode 100644 index 1fc8c06..0000000 --- a/src/dorkbox/systemTray/util/CssParser.java +++ /dev/null @@ -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 colorDefinitions; - List cssNodes; - - Css(final List colorDefinitions, final List 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 attributes; - - CssNode(final String label, final List 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 getSections(Css css, String[] nodes, String[] states) { - if (states == null) { - states = new String[0]; - } - - List sections = new ArrayList(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 getAttributeFromSections(final List sections, final String attributeName, boolean equalsOrContained) { - - // a list of sections that contains the exact attribute we are looking for - List sectionsWithAttribute = new ArrayList(); - 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 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 sections = new ArrayList(); - - 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 attributes = new ArrayList(); - - // 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 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 attributes = section.attributes; - for (int i1 = 0; i1 < attributes.size(); i1++) { - final Entry attribute = attributes.get(i1); - for (Iterator 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 iterator = sections.iterator(); iterator.hasNext(); ) { - final CssNode section = iterator.next(); - - for (Iterator 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 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 defines = new ArrayList(); - { - // 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 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; - } -} diff --git a/src/dorkbox/systemTray/util/EventDispatch.java b/src/dorkbox/systemTray/util/EventDispatch.java new file mode 100644 index 0000000..9ae2818 --- /dev/null +++ b/src/dorkbox/systemTray/util/EventDispatch.java @@ -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; + } + } +} diff --git a/src/dorkbox/systemTray/util/ImageResizeUtil.java b/src/dorkbox/systemTray/util/ImageResizeUtil.java index bd7f718..307c849 100644 --- a/src/dorkbox/systemTray/util/ImageResizeUtil.java +++ b/src/dorkbox/systemTray/util/ImageResizeUtil.java @@ -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()); diff --git a/src/dorkbox/systemTray/util/JavaFX.java b/src/dorkbox/systemTray/util/JavaFX.java deleted file mode 100644 index 9aeb79a..0000000 --- a/src/dorkbox/systemTray/util/JavaFX.java +++ /dev/null @@ -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. - *

- * 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."); - } - } -} diff --git a/src/dorkbox/systemTray/util/LinuxSwingUI.java b/src/dorkbox/systemTray/util/LinuxSwingUI.java index 94047ac..6e10387 100644 --- a/src/dorkbox/systemTray/util/LinuxSwingUI.java +++ b/src/dorkbox/systemTray/util/LinuxSwingUI.java @@ -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) diff --git a/src/dorkbox/systemTray/util/SizeAndScalingUtil.java b/src/dorkbox/systemTray/util/SizeAndScalingUtil.java index e67cef7..1ff4a8b 100644 --- a/src/dorkbox/systemTray/util/SizeAndScalingUtil.java +++ b/src/dorkbox/systemTray/util/SizeAndScalingUtil.java @@ -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 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 diff --git a/src/dorkbox/systemTray/util/Swt.java b/src/dorkbox/systemTray/util/Swt.java deleted file mode 100644 index 1f71429..0000000 --- a/src/dorkbox/systemTray/util/Swt.java +++ /dev/null @@ -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. - *

- * 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(); - } - }); - } - }); - } - } -} diff --git a/src/dorkbox/systemTray/util/SystemTrayFixes.java b/src/dorkbox/systemTray/util/SystemTrayFixes.java index 7628af0..fe1b69e 100644 --- a/src/dorkbox/systemTray/util/SystemTrayFixes.java +++ b/src/dorkbox/systemTray/util/SystemTrayFixes.java @@ -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"); diff --git a/src/dorkbox/systemTray/util/WindowsSwingUI.java b/src/dorkbox/systemTray/util/WindowsSwingUI.java index f9187ed..cde539d 100644 --- a/src/dorkbox/systemTray/util/WindowsSwingUI.java +++ b/src/dorkbox/systemTray/util/WindowsSwingUI.java @@ -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); + } + }; + } } /** diff --git a/test/dorkbox/CustomSwingUI.java b/test/dorkbox/CustomSwingUI.java index 1aaec98..ce4fbb2 100644 --- a/test/dorkbox/CustomSwingUI.java +++ b/test/dorkbox/CustomSwingUI.java @@ -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; diff --git a/test/dorkbox/TestTray.java b/test/dorkbox/TestTray.java index 914a97e..4ac7123 100644 --- a/test/dorkbox/TestTray.java +++ b/test/dorkbox/TestTray.java @@ -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 diff --git a/test/dorkbox/TestTrayJavaFX.java b/test/dorkbox/TestTrayJavaFX.java index d61fdc6..18b510f 100644 --- a/test/dorkbox/TestTrayJavaFX.java +++ b/test/dorkbox/TestTrayJavaFX.java @@ -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 diff --git a/test/dorkbox/TestTraySwt.java b/test/dorkbox/TestTraySwt.java index a9cd626..1371480 100644 --- a/test/dorkbox/TestTraySwt.java +++ b/test/dorkbox/TestTraySwt.java @@ -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(); diff --git a/test/example.c b/test/example.c index d4d9295..8dc318a 100644 --- a/test/example.c +++ b/test/example.c @@ -3,11 +3,11 @@ #include -// 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);