Better support for JavaFX/SWT/GTK2/GTK3/AppIndicators. Fixed various

GTK error messages during initialization.
This commit is contained in:
nathan 2016-09-22 22:36:04 +02:00
parent 3488646034
commit efeda2fac3
7 changed files with 287 additions and 139 deletions

View File

@ -56,6 +56,7 @@ class SystemTray {
public static final int LINUX_GTK = 1; public static final int LINUX_GTK = 1;
public static final int LINUX_APP_INDICATOR = 2; public static final int LINUX_APP_INDICATOR = 2;
public static final int SWING_INDICATOR = 3;
@Property @Property
/** How long to wait when updating menu entries before the request times-out */ /** How long to wait when updating menu entries before the request times-out */
@ -70,16 +71,9 @@ class SystemTray {
public static boolean FORCE_GTK2 = false; public static boolean FORCE_GTK2 = false;
@Property @Property
/** If != 0, forces the system tray in linux to be GTK (1) or AppIndicator (2). This is an advanced feature. */ /** If != 0, forces the system tray in linux to be GTK (1), AppIndicator (2), or Swing (3). This is an advanced feature. */
public static int FORCE_LINUX_TYPE = 0; public static int FORCE_LINUX_TYPE = 0;
@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 @Property
/** /**
* When in compatibility mode, and the JavaFX/SWT primary windows are closed, we want to make sure that the SystemTray is also closed. * When in compatibility mode, and the JavaFX/SWT primary windows are closed, we want to make sure that the SystemTray is also closed.
@ -93,9 +87,40 @@ class SystemTray {
*/ */
public static boolean DEBUG = false; public static boolean DEBUG = false;
private static volatile SystemTray systemTray = null; private static volatile SystemTray systemTray = null;
static boolean isKDE = false; static boolean isKDE = false;
public final static boolean isJavaFxLoaded;
public final static boolean isSwtLoaded;
static {
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be GTK2!!
// SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3
// 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
boolean isJavaFxLoaded_ = false;
boolean isSwtLoaded_ = false;
try {
// First check if JavaFX is loaded - if it's NOT LOADED, then we only proceed if COMPATIBILITY_MODE is enabled.
// this is important, 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();
isJavaFxLoaded_ = (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application"));
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 void init() { private static void init() {
if (systemTray != null) { if (systemTray != null) {
return; return;
@ -109,52 +134,73 @@ class SystemTray {
Class<? extends SystemTray> trayType = null; Class<? extends SystemTray> trayType = null;
boolean isJavaFxLoaded = false; // kablooie if SWT is not configured in a way that works with us.
boolean isSwtLoaded = false; if (FORCE_LINUX_TYPE != SWING_INDICATOR && OS.isLinux()) {
try { if (isSwtLoaded) {
// First check if JavaFX is loaded - if it's NOT LOADED, then we only proceed if COMPATIBILITY_MODE is enabled. // 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
// this is important, because if JavaFX is not being used, calling getToolkit() will initialize it... // System.setProperty("SWT_GTK3", "0");
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true); // was SWT forced?
ClassLoader cl = ClassLoader.getSystemClassLoader(); boolean isSwt_GTK3 = !System.getProperty("SWT_GTK3").equals("0");
isJavaFxLoaded = (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application")); if (!isSwt_GTK3) {
isSwtLoaded = null != m.invoke(cl, "org.eclipse.swt.widgets.Display"); // check a different property
} catch (Throwable e) { isSwt_GTK3 = !System.getProperty("org.eclipse.swt.internal.gtk.version").startsWith("2.");
if (DEBUG) { }
logger.debug("Error detecting compatibility mode", e);
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;`");
throw new RuntimeException("SWT configured to use GTK3 and is incompatible with the SystemTray GTK2.");
} else if (!isSwt_GTK3 && !FORCE_GTK2) {
// we must use GTK2, because SWT is GTK2
if (DEBUG) {
logger.debug("Forcing GTK2 because SWT is GTK2");
}
FORCE_GTK2 = true;
}
}
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 isJFX_GTK3 = System.getProperty("jdk.gtk.version", "2").equals("3");
if (isJFX_GTK3 && 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;`");
throw new RuntimeException("JavaFX configured to use GTK3 and is incompatible with the SystemTray GTK2.");
} 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.");
throw new RuntimeException("JavaFX configured to use GTK3 and is incompatible with the SystemTray GTK2.");
}
} else if (!isJFX_GTK3 && !FORCE_GTK2) {
// we must use GTK2, because JavaFX is GTK2
if (DEBUG) {
logger.debug("Forcing GTK2 because JavaFX is GTK2");
}
FORCE_GTK2 = true;
}
} }
} }
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be GTK2!!
// SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3
// 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
COMPATIBILITY_MODE = OS.isLinux() && (isJavaFxLoaded || isSwtLoaded);
if (DEBUG) { if (DEBUG) {
switch (FORCE_LINUX_TYPE) {
case 1: logger.debug("Forcing GTK type"); break;
case 2: logger.debug("Forcing AppIndicator type"); break;
case 3: logger.debug("Forcing Swing type"); break;
default: logger.debug("Auto-detecting indicator type"); break;
}
logger.debug("FORCE_GTK2: {}", FORCE_GTK2); logger.debug("FORCE_GTK2: {}", FORCE_GTK2);
logger.debug("COMPATIBILITY_MODE: {}", COMPATIBILITY_MODE);
}
// kablooie if SWT is not configured in a way that works with us.
if (OS.isLinux() && isSwtLoaded) {
// 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.");
}
} }
@ -163,11 +209,11 @@ class SystemTray {
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all. // all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
if (OS.isWindows()) { if (OS.isWindows()) {
// the tray icon size in windows is DIFFERENT than on Mac (TODO: test on mac with retina stuff). // the tray icon size in windows is DIFFERENT than on Mac (TODO: test on mac with retina stuff. Also check HiDpi setups).
TRAY_SIZE -= 4; TRAY_SIZE -= 4;
} }
if (OS.isLinux()) { if (FORCE_LINUX_TYPE != SWING_INDICATOR && OS.isLinux()) {
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running // see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
// For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python. // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
@ -201,6 +247,8 @@ class SystemTray {
} }
} }
} }
// don't check for SWING type at this spot, it is done elsewhere.
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
@ -322,8 +370,9 @@ class SystemTray {
} }
} }
else if ("ubuntu".equalsIgnoreCase(GDM)) { else if ("ubuntu".equalsIgnoreCase(GDM)) {
// have to install the gnome extension // have to install the gnome extension AND customize the restart command
trayType = null; trayType = null;
GnomeShellExtension.SHELL_RESTART_COMMAND = "unity --replace &";
} }
} }
@ -494,7 +543,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. // 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 (ENABLE_SHUTDOWN_HOOK) {
if (isJavaFxLoaded) { if (isJavaFxLoaded) {
// Necessary because javaFX **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive. // 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 // Also, it's nice to have us shutdown at the same time as the main application
@ -518,7 +567,9 @@ class SystemTray {
@Override @Override
public public
void run() { void run() {
systemTray.shutdown(); if (systemTray != null) {
systemTray.shutdown();
}
} }
}); });
} catch (Throwable e) { } catch (Throwable e) {
@ -542,7 +593,9 @@ class SystemTray {
@Override @Override
public public
void run() { void run() {
systemTray.shutdown(); if (systemTray != null) {
systemTray.shutdown();
}
} }
}); });
} catch (Throwable e) { } catch (Throwable e) {

View File

@ -15,8 +15,13 @@
*/ */
package dorkbox.systemTray.linux; package dorkbox.systemTray.linux;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
import com.sun.jna.NativeLong; import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.MenuEntry;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
@ -24,10 +29,6 @@ import dorkbox.systemTray.linux.jna.GCallback;
import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
class GtkMenuEntry implements MenuEntry, GCallback { class GtkMenuEntry implements MenuEntry, GCallback {
private static final AtomicInteger ID_COUNTER = new AtomicInteger(); private static final AtomicInteger ID_COUNTER = new AtomicInteger();
private final int id = ID_COUNTER.getAndIncrement(); private final int id = ID_COUNTER.getAndIncrement();

View File

@ -17,15 +17,19 @@ package dorkbox.systemTray.linux;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.jna.NativeLong; import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.linux.jna.GEventCallback; import dorkbox.systemTray.linux.jna.GEventCallback;
import dorkbox.systemTray.linux.jna.GdkEventButton; import dorkbox.systemTray.linux.jna.GdkEventButton;
import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import javafx.application.Platform;
/** /**
* Class for handling all system tray interactions via GTK. * Class for handling all system tray interactions via GTK.
@ -34,7 +38,7 @@ import dorkbox.systemTray.linux.jna.Gtk;
*/ */
public public
class GtkSystemTray extends GtkTypeSystemTray { class GtkSystemTray extends GtkTypeSystemTray {
private Pointer trayIcon; private volatile Pointer trayIcon;
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
private final List<Object> gtkCallbacks = new ArrayList<Object>(); private final List<Object> gtkCallbacks = new ArrayList<Object>();
@ -49,23 +53,21 @@ class GtkSystemTray extends GtkTypeSystemTray {
super(); super();
Gtk.startGui(); Gtk.startGui();
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
final Pointer trayIcon_ = Gtk.gtk_status_icon_new(); final Pointer trayIcon_ = Gtk.gtk_status_icon_new();
trayIcon = trayIcon_;
// necessary for gnome icon detection/placement because we move tray icons around by name. The name is hardcoded // necessary for gnome icon detection/placement because we move tray icons around by name. The name is hardcoded
// in extension.js, so don't change it // in extension.js, so don't change it
Gtk.gtk_status_icon_set_title(trayIcon_, "SystemTray"); Gtk.gtk_status_icon_set_title(trayIcon_, "SystemTray");
// can cause on fedora 23
// // Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
Gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray");
trayIcon = trayIcon_; final GEventCallback gtkCallback2 = new GEventCallback() {
final GEventCallback gtkCallback = new GEventCallback() {
@Override @Override
public public
void callback(Pointer notUsed, final GdkEventButton event) { void callback(Pointer notUsed, final GdkEventButton event) {
@ -75,13 +77,42 @@ class GtkSystemTray extends GtkTypeSystemTray {
} }
} }
}; };
final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, null, 0); final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon_, "button_press_event", gtkCallback2,
null, 0);
// have to do this to prevent GC on these objects // have to do this to prevent GC on these objects
gtkCallbacks.add(gtkCallback); gtkCallbacks.add(gtkCallback2);
gtkCallbacks.add(button_press_event); gtkCallbacks.add(button_press_event);
blockUntilStarted.countDown();
} }
}); });
if (SystemTray.isJavaFxLoaded) {
if (!Platform.isFxApplicationThread()) {
try {
blockUntilStarted.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else if (SystemTray.isSwtLoaded) {
if (SystemTray.FORCE_LINUX_TYPE != SystemTray.LINUX_GTK) {
// GTK system tray has threading issues if we block here (because it is likely in the event thread)
// AppIndicator version doesn't have this problem
try {
blockUntilStarted.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
try {
blockUntilStarted.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} }
@SuppressWarnings("FieldRepeatedlyAccessedInMethod") @SuppressWarnings("FieldRepeatedlyAccessedInMethod")
@ -118,6 +149,13 @@ class GtkSystemTray extends GtkTypeSystemTray {
if (!isActive) { if (!isActive) {
isActive = true; isActive = true;
// can cause
// Gdk-CRITICAL **: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed
// Gdk-CRITICAL **: IA__gdk_window_thaw_toplevel_updates_libgtk_only: assertion 'private->update_and_descendants_freeze_count > 0' failed
// it THIS PLACE IN CODE, these errors don't appear to happen. Nobody knows wtf why this is... This was trial and error
Gtk.gtk_status_icon_set_name(trayIcon, "SystemTray");
Gtk.gtk_status_icon_set_visible(trayIcon, true); Gtk.gtk_status_icon_set_visible(trayIcon, true);
} }
} }

View File

@ -16,16 +16,17 @@
package dorkbox.systemTray.linux; package dorkbox.systemTray.linux;
import java.io.InputStream;
import java.net.URL;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.linux.jna.Gtk;
import java.io.InputStream;
import java.net.URL;
/** /**
* Derived from * Derived from
* Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc. * Lantern: https://github.com/getlantern/lantern/ Apache 2.0 License Copyright 2010 Brave New Software Project, Inc.

View File

@ -58,7 +58,7 @@ class AppIndicator {
isLoaded = true; isLoaded = true;
} }
if (!isLoaded && (SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE)) { if (!isLoaded && SystemTray.FORCE_GTK2) {
// if specified, try loading appindicator1 first, maybe it's there? // if specified, try loading appindicator1 first, maybe it's there?
try { try {
final NativeLibrary library = JnaHelper.register("appindicator1", AppIndicator.class); final NativeLibrary library = JnaHelper.register("appindicator1", AppIndicator.class);

View File

@ -31,6 +31,9 @@ class Gobject {
JnaHelper.register("gobject-2.0", Gobject.class); JnaHelper.register("gobject-2.0", Gobject.class);
} }
public static native void g_idle_add(FuncCallback callback, Pointer data);
public static native void g_free(Pointer object); public static native void g_free(Pointer object);
public static native void g_object_unref(Pointer object); public static native void g_object_unref(Pointer object);

View File

@ -16,9 +16,12 @@
package dorkbox.systemTray.linux.jna; package dorkbox.systemTray.linux.jna;
import static dorkbox.systemTray.SystemTray.logger; import static dorkbox.systemTray.SystemTray.logger;
import static dorkbox.systemTray.linux.jna.Gobject.g_idle_add;
import java.lang.reflect.Method;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.sun.jna.Function; import com.sun.jna.Function;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
@ -47,6 +50,8 @@ class Gtk {
private static boolean alreadyRunningGTK = false; private static boolean alreadyRunningGTK = false;
private static boolean isLoaded = false; private static boolean isLoaded = false;
private static Method swtDispatchMethod = null;
/** /**
* We can have GTK v3 or v2. * We can have GTK v3 or v2.
@ -58,26 +63,24 @@ 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-x11-2.0.so.0 | grep gtk
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
static { static {
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE; boolean shouldUseGtk2 = SystemTray.FORCE_GTK2;
// 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
if (SystemTray.COMPATIBILITY_MODE && System.getProperty("jdk.gtk.version", "2").equals("3") && !SystemTray.FORCE_GTK2) {
// the user specified to use GTK3, so we should honor that
shouldUseGtk2 = false;
}
// for more info on JavaFX: 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+.
// in some cases, we ALWAYS want to try GTK2 first // in some cases, we ALWAYS want to try GTK2 first
if (shouldUseGtk2) { String gtk2LibName = "gtk-x11-2.0";
String gtk3LibName = "libgtk-3.so.0";
// we can force the system to use the swing indicator, which WORKS, but doesn't support transparency in the icon.
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.SWING_INDICATOR) {
isLoaded = true;
}
if (!isLoaded && shouldUseGtk2) {
try { try {
JnaHelper.register("gtk-x11-2.0", Gtk.class); JnaHelper.register(gtk2LibName, Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); gtk_status_icon_position_menu = Function.getFunction(gtk2LibName, "gtk_status_icon_position_menu");
isGtk2 = true; isGtk2 = true;
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
@ -86,7 +89,7 @@ class Gtk {
isLoaded = true; isLoaded = true;
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
logger.info("GTK: gtk-x11-2.0"); logger.info("GTK: {}", gtk2LibName);
} }
} catch (Throwable e) { } catch (Throwable e) {
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
@ -100,15 +103,15 @@ class Gtk {
// start with version 3 // start with version 3
if (!isLoaded) { if (!isLoaded) {
try { try {
JnaHelper.register("libgtk-3.so.0", Gtk.class); JnaHelper.register(gtk3LibName, Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("libgtk-3.so.0", "gtk_status_icon_position_menu"); gtk_status_icon_position_menu = Function.getFunction(gtk3LibName, "gtk_status_icon_position_menu");
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // 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 stared GTK -- so we DO NOT NEED TO. // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK = gtk_main_level() != 0; alreadyRunningGTK = gtk_main_level() != 0;
isLoaded = true; isLoaded = true;
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
logger.info("GTK: libgtk-3.so.0"); logger.info("GTK: {}", gtk3LibName);
} }
} catch (Throwable e) { } catch (Throwable e) {
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
@ -120,8 +123,8 @@ class Gtk {
// now version 2 // now version 2
if (!isLoaded) { if (!isLoaded) {
try { try {
JnaHelper.register("gtk-x11-2.0", Gtk.class); JnaHelper.register(gtk2LibName, Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); gtk_status_icon_position_menu = Function.getFunction(gtk2LibName, "gtk_status_icon_position_menu");
isGtk2 = true; isGtk2 = true;
// when running inside of JavaFX, this will be '1'. All other times this should be '0' // when running inside of JavaFX, this will be '1'. All other times this should be '0'
@ -130,7 +133,7 @@ class Gtk {
isLoaded = true; isLoaded = true;
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
logger.info("GTK: gtk-x11-2.0"); logger.info("GTK: {}", gtk2LibName);
} }
} catch (Throwable e) { } catch (Throwable e) {
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
@ -139,6 +142,10 @@ class Gtk {
} }
} }
// 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) { if (SystemTray.DEBUG) {
logger.info("Is the system already running GTK? {}", alreadyRunningGTK); logger.info("Is the system already running GTK? {}", alreadyRunningGTK);
} }
@ -168,51 +175,73 @@ class Gtk {
// startup the GTK GUI event loop. There can be multiple/nested loops. // startup the GTK GUI event loop. There can be multiple/nested loops.
// If JavaFX/SWT is used, this is UNNECESSARY // only necessary if we are the only GTK instance running...
if (!alreadyRunningGTK) { final CountDownLatch blockUntilStarted = new CountDownLatch(1);
if (!alreadyRunningGTK ) {
// If JavaFX/SWT is used, this is UNNECESSARY (we can detect if the GTK main_loop is running)
if (SystemTray.DEBUG) { if (SystemTray.DEBUG) {
logger.error("Running GTK Native Event Loop"); logger.error("Running GTK Native Event Loop");
} }
// only necessary if we are the only GTK instance running...
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
gtkUpdateThread = new Thread() { gtkUpdateThread = new Thread() {
@Override @Override
public public
void run() { void run() {
// prep for the event loop. // prep for the event loop.
gdk_threads_init();
gdk_threads_enter();
GThread.g_thread_init(null); GThread.g_thread_init(null);
if (!SystemTray.COMPATIBILITY_MODE) { if (!gtk_init_check(0)) {
gtk_init_check(0); if (SystemTray.DEBUG) {
logger.error("Error starting GTK");
}
return;
} }
// notify our main thread to continue // blocks unit quit
blockUntilStarted.countDown(); gtk_main();
if (!SystemTray.COMPATIBILITY_MODE) {
// blocks unit quit
gtk_main();
}
gdk_threads_leave();
} }
}; };
gtkUpdateThread.setName("GTK Native Event Loop"); gtkUpdateThread.setName("GTK Native Event Loop");
gtkUpdateThread.start(); gtkUpdateThread.start();
try {
// notify our main thread to continue
// Please note: we don't need to do this on SWT/JavaFX, because they startup the main_loop BEFORE the app starts.
dispatch(new Runnable() {
@Override
public
void run() {
blockUntilStarted.countDown();
}
});
try {
// we CANNOT continue until the GTK thread has started! // we CANNOT continue until the GTK thread has started!
blockUntilStarted.await(); boolean await = blockUntilStarted.await(10, TimeUnit.SECONDS);
if (!await) {
throw new RuntimeException("Unable to initialize GTK. Something is HORRIBLY wrong, aborting.");
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
if (SystemTray.isSwtLoaded) {
try {
Class<?> clazz = Class.forName("dorkbox.systemTray.swt.Swt");
swtDispatchMethod = clazz.getMethod("dispatch", Runnable.class);
} catch (Throwable e) {
if (SystemTray.DEBUG) {
logger.error("Cannot initialize SWT dispatch", e);
e.printStackTrace();
}
logger.error("Unable to call into dispatch method for SWT. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
} }
/** /**
@ -220,17 +249,37 @@ class Gtk {
*/ */
public static public static
void dispatch(final Runnable runnable) { void dispatch(final Runnable runnable) {
if (gtkUpdateThread == Thread.currentThread()) { if (alreadyRunningGTK) {
// if we are ALREADY inside the native event // SWT/JavaFX
runnable.run();
} else { if (SystemTray.isJavaFxLoaded) {
if (javafx.application.Platform.isFxApplicationThread()) {
// Run directly on the JavaFX event thread
runnable.run();
}
else {
javafx.application.Platform.runLater(runnable);
}
}
else if (SystemTray.isSwtLoaded) {
try {
swtDispatchMethod.invoke(null, runnable);
} catch (Exception e) {
logger.error("Unable to call into dispatch method for SWT. Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
throw new RuntimeException("Unable to invoke required SWT method");
}
}
}
else {
final FuncCallback callback = new FuncCallback() { final FuncCallback callback = new FuncCallback() {
@Override @Override
public public
int callback(final Pointer data) { int callback(final Pointer data) {
synchronized (gtkCallbacks) { synchronized (gtkCallbacks) {
gtkCallbacks.removeFirst(); // now that we've 'handled' it, we can remove it from our callback list gtkCallbacks.removeFirst();// now that we've 'handled' it, we can remove it from our callback list
} }
runnable.run(); runnable.run();
return Gtk.FALSE; // don't want to call this again return Gtk.FALSE; // don't want to call this again
@ -240,22 +289,29 @@ class Gtk {
synchronized (gtkCallbacks) { synchronized (gtkCallbacks) {
gtkCallbacks.offer(callback); // prevent GC from collecting this object before it can be called gtkCallbacks.offer(callback); // prevent GC from collecting this object before it can be called
} }
gdk_threads_add_idle(callback, null);
// the correct way to do it
g_idle_add(callback, null);
} }
} }
public static public static
void shutdownGui() { void shutdownGui() {
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown) dispatch(new Runnable() {
if (!(alreadyRunningGTK || SystemTray.COMPATIBILITY_MODE)) { @Override
gtk_main_quit(); public
} void run() {
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown)
if (!alreadyRunningGTK) {
gtk_main_quit();
}
started = false; started = false;
}
});
} }
/** /**
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky * 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. * enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
@ -268,13 +324,6 @@ class Gtk {
*/ */
private static native void gtk_main(); private static native void gtk_main();
/**
* using g_idle_add() instead would require thread protection in the callback
*
* @return TRUE to run this callback again, FALSE to remove from the list of event sources (and not call it again)
*/
private static native int gdk_threads_add_idle(FuncCallback callback, Pointer data);
/** /**
* aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running * aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running
*/ */
@ -288,7 +337,11 @@ class Gtk {
private static native void gdk_threads_init(); private static native void gdk_threads_init();
// tricky business. This should only be in the dispatch thread // tricky business. Only when using SWT/JavaFX, because they do it wrong.
/**
* @return TRUE to run this callback again, FALSE to remove from the list of event sources (and not call it again)
*/
private static native int gdk_threads_add_idle(FuncCallback callback, Pointer data);
private static native void gdk_threads_enter(); private static native void gdk_threads_enter();
private static native void gdk_threads_leave(); private static native void gdk_threads_leave();
@ -298,8 +351,6 @@ class Gtk {
public static native Pointer gtk_menu_new(); public static native Pointer gtk_menu_new();
public static native Pointer gtk_menu_item_new();
public static native Pointer gtk_menu_item_new_with_label(String label); public static native Pointer gtk_menu_item_new_with_label(String label);
// to create a menu entry WITH an icon. // to create a menu entry WITH an icon.
@ -320,9 +371,10 @@ class Gtk {
public static native void gtk_label_set_use_markup(Pointer label, int gboolean); public static native void gtk_label_set_use_markup(Pointer label, int gboolean);
public static native Pointer gtk_status_icon_new_from_icon_name(String iconName);;
public static native Pointer gtk_status_icon_new(); public static native Pointer gtk_status_icon_new();
public static native void gtk_status_icon_set_from_file(Pointer widget, String lablel); public static native void gtk_status_icon_set_from_file(Pointer widget, String label);
public static native void gtk_status_icon_set_visible(Pointer widget, boolean visible); public static native void gtk_status_icon_set_visible(Pointer widget, boolean visible);