Added support (and configuration info) for using the SystemTray with SWT

This commit is contained in:
nathan 2016-02-15 00:55:03 +01:00
parent 6fac9be88e
commit 85ba4c9380
4 changed files with 224 additions and 174 deletions

View File

@ -21,7 +21,6 @@ import dorkbox.systemTray.linux.GtkSystemTray;
import dorkbox.systemTray.linux.jna.AppIndicator;
import dorkbox.systemTray.linux.jna.GtkSupport;
import dorkbox.systemTray.swing.SwingSystemTray;
import dorkbox.systemTray.swt.SwtSystemTray;
import dorkbox.util.OS;
import dorkbox.util.Property;
import dorkbox.util.process.ShellProcessBuilder;
@ -54,11 +53,33 @@ class SystemTray {
/** Size of the tray, so that the icon can properly scale based on OS. (if it's not exact) */
public static int TRAY_SIZE = 22;
private static final SystemTray systemTray;
@Property
/** Forces the system to always choose GTK2 (even when GTK3 might be available). JavaFX uses GTK2! */
public static boolean FORCE_GTK2 = false;
@Property
/**
* Forces the system to enter into JavaFX/Swt compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop.
* This is only necessary if autodetection fails.
*/
public static boolean COMPATIBILITY_MODE = false;
@Property
/**
* When in compatibility mode, when JavaFX/SWT primary windows are close, we want to make sure that the SystemTray is also closed.
* This property is available to disable this functionality in the situations where you don' want this to happen.
*/
public static boolean ENABLE_SHUTDOWN_HOOK = true;
private static volatile SystemTray systemTray = null;
static boolean isKDE = false;
static {
private static void init() {
if (systemTray != null) {
return;
}
Class<? extends SystemTray> trayType = null;
boolean isJavaFxLoaded = false;
@ -74,15 +95,30 @@ class SystemTray {
} catch (Throwable ignored) {
}
// maybe we should load the SWT version? (SWT's use of GTK is incompatible with how we use GTK)
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be GTK2!!
COMPATIBILITY_MODE = isJavaFxLoaded || isSwtLoaded;
// kablooie if SWT is not configured in a way that works with us.
if (isSwtLoaded) {
try {
trayType = SwtSystemTray.class;
} catch (Throwable ignored) {
// Necessary for us to work with SWT
// System.setProperty("SWT_GTK3", "0"); // Necessary for us to work with SWT
// was SWT forced?
boolean isSwt_GTK3 = !System.getProperty("SWT_GTK3").equals("0");
if (!isSwt_GTK3) {
// check a different property
isSwt_GTK3 = !System.getProperty("org.eclipse.swt.internal.gtk.version").startsWith("2.");
}
if (isSwt_GTK3) {
logger.error("Unable to use the SystemTray when SWT is configured to use GTK3. Please configure SWT to use GTK2, one such " +
"example is to set the system property `System.setProperty(\"SWT_GTK3\", \"0\");` before SWT is initialized");
throw new RuntimeException("SWT configured to use GTK3 and is incompatible with the SystemTray.");
}
}
else {
// Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
@ -95,11 +131,6 @@ class SystemTray {
if (OS.isLinux()) {
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
if (isJavaFxLoaded) {
// we MUST use GTK2 with javaFX!
GtkSupport.FORCE_GTK2 = isJavaFxLoaded;
}
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
if ("Unity".equalsIgnoreCase(XDG)) {
@ -252,7 +283,6 @@ class SystemTray {
"configuration");
}
}
}
// this is windows OR mac
if (trayType == null && java.awt.SystemTray.isSupported()) {
@ -308,10 +338,12 @@ class SystemTray {
systemTray = systemTray_;
// These install a shutdown hook in JavaFX/SWT, so that when the main window is closed -- the system tray is ALSO closed.
if (COMPATIBILITY_MODE && ENABLE_SHUTDOWN_HOOK) {
if (isJavaFxLoaded) {
// Necessary because javaFX **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive.
// we ONLY need this on linux for compatibility with JavaFX... (windows/mac don't use gtk)
if (OS.isLinux()) {
if (isJavaFxLoaded || GtkSupport.JAVAFX_COMPATIBILITY_MODE) {
// Also, it's nice to have us shutdown at the same time as the main application
// com.sun.javafx.tk.Toolkit.getToolkit()
// .addShutdownHook(new Runnable() {
// @Override
@ -336,13 +368,35 @@ class SystemTray {
});
} catch (Throwable ignored) {
logger.error("Unable to insert shutdown hook into JavaFX. Please create an issue with your OS and Java " +
"configuration so we may further investigate this issue.");
"version so we may further investigate this issue.");
}
}
else if (isSwtLoaded) {
// 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
// During compile time (for production), this class is not compiled, and instead is copied over as a pre-compiled file
// This is so we don't have to rely on having SWT as part of the classpath during build.
try {
Class<?> clazz = Class.forName("dorkbox.systemTray.swt.Swt");
Method method = clazz.getMethod("onShutdown", Runnable.class);
Object o = method.invoke(null, new Runnable() {
@Override
public
void run() {
systemTray.shutdown();
}
});
} catch (Throwable ignored) {
logger.error("Unable to insert shutdown hook into SWT. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
}
}
}
/**
* Gets the version number.
*/
@ -360,6 +414,7 @@ class SystemTray {
*/
public static
SystemTray getSystemTray() {
init();
return systemTray;
}

View File

@ -17,6 +17,7 @@
package dorkbox.systemTray.linux.jna;
import com.sun.jna.Native;
import dorkbox.systemTray.SystemTray;
/**
* Helper for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that standard
@ -44,7 +45,7 @@ class AppIndicatorQuery {
// NOTE: GtkSupport uses this info to figure out WHAT VERSION OF GTK to use: appindiactor1 -> GTk2, appindicator3 -> GTK3.
if (GtkSupport.FORCE_GTK2) {
if (SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE) {
// try loading appindicator1 first, maybe it's there?
try {
@ -117,7 +118,7 @@ class AppIndicatorQuery {
} catch (Throwable ignored) {
}
throw new RuntimeException("We apologize for this, but we are unable to determine the appIndicator library is in use, if " +
throw new RuntimeException("We apologize for this, but we are unable to determine which the appIndicator library is in use, if " +
"or even if it is in use... Please create an issue for this and include your OS type and configuration.");
}
}

View File

@ -17,54 +17,37 @@ package dorkbox.systemTray.linux.jna;
import com.sun.jna.Function;
import com.sun.jna.Native;
import dorkbox.util.Property;
import dorkbox.systemTray.SystemTray;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
public
class GtkSupport {
// RE: SWT
// https://developer.gnome.org/glib/stable/glib-Deprecated-Thread-APIs.html#g-thread-init
// Since version >= 2.24, threads can only init once. Multiple calls do nothing, and we can nest gtk_main()
// in a nested loop.
private static volatile boolean started = false;
private static final ArrayBlockingQueue<Runnable> dispatchEvents = new ArrayBlockingQueue<Runnable>(256);
private static volatile Thread gtkDispatchThread;
@Property
/** Forces the system to always choose GTK2 (even when GTK3 might be available). JavaFX uses GTK2! */
public static boolean FORCE_GTK2 = false;
@Property
/**
* Forces the system to enter into JavaFX compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop.
* This is only necessary if autodetection fails
*/
public static boolean JAVAFX_COMPATIBILITY_MODE = false;
/**
* must call get() before accessing this! Only "Gtk" interface should access this!
*/
static volatile Function gtk_status_icon_position_menu = null;
public static volatile boolean isGtk2 = false;
private static volatile boolean alreadyRunningGTK = false;
/**
* Helper for GTK, because we could have 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.
*/
@SuppressWarnings("Duplicates")
public static
Gtk get() {
Gtk library;
boolean shouldUseGtk2 = GtkSupport.FORCE_GTK2 || JAVAFX_COMPATIBILITY_MODE;
alreadyRunningGTK = JAVAFX_COMPATIBILITY_MODE;
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE;
alreadyRunningGTK = SystemTray.COMPATIBILITY_MODE;
// for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm

View File

@ -24,6 +24,7 @@ import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import java.io.File;
import java.net.URL;
/**
@ -41,6 +42,10 @@ class TestTraySwt {
public static
void main(String[] args) {
System.setProperty("SWT_GTK3", "0"); // Necessary for us to work with SWT
System.load(new File("../../resources/Dependencies/jna/linux_64/libjna.so").getAbsolutePath()); //64bit linux library
new TestTraySwt();
}
@ -102,7 +107,13 @@ class TestTraySwt {
public
void onClick(final SystemTray systemTray, final MenuEntry menuEntry) {
systemTray.shutdown();
Display.getDefault().asyncExec(new Runnable() {
public void run() {
shell.close(); // close down SWT shell
}
});
//System.exit(0); not necessary if all non-daemon threads have stopped.
}
});