Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2014 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;
import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.stream.ImageInputStream;
import javax.swing.Icon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dorkbox.systemTray.gnomeShell.Extension;
import dorkbox.systemTray.ui.awt._AwtTray;
import dorkbox.systemTray.ui.gtk._AppIndicatorNativeTray;
import dorkbox.systemTray.ui.gtk._GtkStatusIconNativeTray;
import dorkbox.systemTray.ui.swing.SwingUIFactory;
import dorkbox.systemTray.ui.swing._SwingTray;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.systemTray.util.LinuxSwingUI;
import dorkbox.systemTray.util.SizeAndScalingUtil;
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 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;
/**
* Professional, cross-platform **SystemTray**, **AWT**, **GtkStatusIcon**, and **AppIndicator** support for Java applications.
*
* This library provides **OS native** menus and **Swing** menus.
*
*
Swing menus are the default preferred type because they offer more features (images attached to menu entries, text styling, etc) and
* a consistent look & feel across all platforms.
*
*
Native menus, should one want them, follow the specified look and feel of that OS, and thus are limited by what is supported on the
* OS and consequently not consistent across all platforms.
*
*
*/
@SuppressWarnings({"unused", "Duplicates", "DanglingJavadoc", "WeakerAccess"})
public final
class SystemTray {
public static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
public enum TrayType {
/** Will choose as a 'best guess' which tray type to use */
AutoDetect,
GtkStatusIcon,
AppIndicator,
Swing,
AWT
}
@Property
/** Enables auto-detection for the system tray. This should be mostly successful. */
public static boolean AUTO_SIZE = true;
@Property
/** 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.
*
* This is an advanced feature, and it is recommended to leave at AutoDetect.
*/
public static TrayType FORCE_TRAY_TYPE = TrayType.AutoDetect;
@Property
/**
* When in compatibility mode, and the JavaFX/SWT primary windows are closed, we want to make sure that the SystemTray is also closed.
* Additionally, when using the Swing tray type, Windows does not always remove the tray icon if the JVM is stopped, and this makes
* sure that the tray is also removed from the notification area.
*
* This property is available to disable this functionality in situations where you don't want this to happen.
*
* This is an advanced feature, and it is recommended to leave as true.
*/ public static boolean ENABLE_SHUTDOWN_HOOK = true;
@Property
/**
* Allows the SystemTray logic to resolve OS inconsistencies for the SystemTray.
*
* This is an advanced feature, and it is recommended to leave as true
*/
public static boolean AUTO_FIX_INCONSISTENCIES = true;
@Property
/**
* Allows a custom look and feel for the Swing UI, if defined. See the test example for specific use.
*/
public static SwingUIFactory SWING_UI = null;
@Property
/**
* This property is provided for debugging any errors in the logic used to determine the system-tray type.
*/
public static boolean DEBUG = false;
private static volatile SystemTray systemTray = null;
private static volatile Tray systemTrayMenu = null;
private static
boolean isTrayType(final Class tray, final TrayType trayType) {
switch (trayType) {
case GtkStatusIcon: return tray == _GtkStatusIconNativeTray.class;
case AppIndicator: return tray == _AppIndicatorNativeTray.class;
case Swing: return tray == _SwingTray.class;
case AWT: return tray == _AwtTray.class;
}
return false;
}
private static
Class selectType(final TrayType trayType) throws Exception {
if (trayType == TrayType.GtkStatusIcon) {
return _GtkStatusIconNativeTray.class;
}
else if (trayType == TrayType.AppIndicator) {
return _AppIndicatorNativeTray.class;
}
else if (trayType == TrayType.Swing) {
return _SwingTray.class;
}
else if (trayType == TrayType.AWT) {
return _AwtTray.class;
}
return null;
}
private static
Class selectTypeQuietly(final TrayType trayType) {
try {
return selectType(trayType);
} catch (Throwable t) {
if (DEBUG) {
logger.error("Cannot initialize {}", trayType.name(), t);
}
}
return null;
}
// This will return what the default "autodetect" tray type should be
private static
Class getAutoDetectTrayType() {
if (OS.isWindows()) {
try {
return selectType(TrayType.Swing);
} catch (Throwable e) {
logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager.", e);
}
}
else if (OS.isMacOsX()) {
// macos can ONLY use the AWT if you want it to follow the L&F of the OS. It is the default.
try {
return selectType(TrayType.AWT);
} catch (Throwable e) {
logger.error("You might need to grant the AWTPermission `accessSystemTray` to the SecurityManager.");
}
}
else if ((OS.isLinux() || OS.isUnix())) {
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
// For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
// https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
OSUtil.DesktopEnv.Env de = OSUtil.DesktopEnv.get();
if (DEBUG) {
logger.debug("Currently using the '{}' desktop environment", de);
}
switch (de) {
case Gnome: {
// check other DE / OS combos that are based on gnome
String GDM = System.getenv("GDMSESSION");
if (DEBUG) {
logger.debug("Currently using the '{}' session type", GDM);
}
if ("gnome".equalsIgnoreCase(GDM)) {
Tray.usingGnome = true;
// are we fedora? If so, what version?
// now, what VERSION of fedora? 23/24/25/? don't have AppIndicator installed, so we have to use GtkStatusIcon
if (OSUtil.Linux.isFedora()) {
if (DEBUG) {
logger.debug("Running Fedora");
}
// 23 is gtk, 24/25 is gtk (but also wrong size unless we adjust it. ImageUtil automatically does this)
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if (OSUtil.Linux.isUbuntu()) {
// so far, because of the interaction between gnome3 + ubuntu, the GtkStatusIcon miraculously works.
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if (OSUtil.Unix.isFreeBSD()) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else {
// arch likely will have problems unless the correct/appropriate libraries are installed.
return selectTypeQuietly(TrayType.AppIndicator);
}
}
else if ("cinnamon".equalsIgnoreCase(GDM)) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("default".equalsIgnoreCase(GDM)) {
Tray.usingGnome = true;
// this can be gnome3 on debian/kali
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);
}
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else if ("ubuntu".equalsIgnoreCase(GDM)) {
return selectTypeQuietly(TrayType.AppIndicator);
}
break;
}
case KDE: {
if (OSUtil.Linux.isFedora()) {
// Fedora KDE requires GtkStatusIcon
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
else {
// kde (at least, plasma 5.5.6) requires appindicator
return selectTypeQuietly(TrayType.AppIndicator);
}
// kde 5.8+ is "high DPI", so we need to adjust the scale. Image resize will do that
}
case Unity: {
// Ubuntu Unity is a weird combination. It's "Gnome", but it's not "Gnome Shell".
return selectTypeQuietly(TrayType.AppIndicator);
}
case Unity7: {
// Ubuntu Unity7 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/
// see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25
// so far, it is OK to use GtkStatusIcon on XFCE <-> XFCE4 inclusive
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
case LXDE: {
return selectTypeQuietly(TrayType.GtkStatusIcon);
}
case Pantheon: {
// elementaryOS. It only supports appindicator (not gtkstatusicon)
// http://bazaar.launchpad.net/~wingpanel-devs/wingpanel/trunk/view/head:/sample/SampleIndicator.vala
// ElementaryOS shows the checkbox on the right, everyone else is on the left.
// 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)
BufferedReader bin = null;
try {
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
// is to look through all /proc//status, and first line should be Name:\tindicator-appli
File proc = new File("/proc");
File[] listFiles = proc.listFiles();
if (listFiles != null) {
for (File procs : listFiles) {
String name = procs.getName();
if (!Character.isDigit(name.charAt(0))) {
continue;
}
File status = new File(procs, "status");
if (!status.canRead()) {
continue;
}
try {
bin = new BufferedReader(new FileReader(status));
String readLine = bin.readLine();
if (readLine != null && readLine.contains("indicator-app")) {
// make sure we can also load the library (it might be the wrong version)
try {
return selectType(TrayType.AppIndicator);
} catch (Exception e) {
if (DEBUG) {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK",
e);
}
else {
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
}
}
break;
}
} finally {
IO.closeQuietly(bin);
}
}
}
} catch (Throwable e) {
if (DEBUG) {
logger.error("Error detecting gnome version", e);
}
}
}
return null;
}
@SuppressWarnings({"ConstantConditions", "StatementWithEmptyBody"})
private static
void init() {
if (systemTray != null) {
return;
}
// if (DEBUG) {
// Properties properties = System.getProperties();
// for (Map.Entry