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_APP_INDICATOR = 2;
public static final int SWING_INDICATOR = 3;
@Property
/** 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;
@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;
@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, 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;
private static volatile SystemTray systemTray = null;
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() {
if (systemTray != null) {
return;
@ -109,52 +134,73 @@ class SystemTray {
Class<? extends SystemTray> trayType = null;
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 compatibility mode", e);
// kablooie if SWT is not configured in a way that works with us.
if (FORCE_LINUX_TYPE != SWING_INDICATOR && OS.isLinux()) {
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");
// 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 && 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) {
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("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.
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;
}
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
// 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
@ -322,8 +370,9 @@ class SystemTray {
}
}
else if ("ubuntu".equalsIgnoreCase(GDM)) {
// have to install the gnome extension
// have to install the gnome extension AND customize the restart command
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.
if (COMPATIBILITY_MODE && ENABLE_SHUTDOWN_HOOK) {
if (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.
// Also, it's nice to have us shutdown at the same time as the main application
@ -518,7 +567,9 @@ class SystemTray {
@Override
public
void run() {
systemTray.shutdown();
if (systemTray != null) {
systemTray.shutdown();
}
}
});
} catch (Throwable e) {
@ -542,7 +593,9 @@ class SystemTray {
@Override
public
void run() {
systemTray.shutdown();
if (systemTray != null) {
systemTray.shutdown();
}
}
});
} catch (Throwable e) {

View File

@ -15,8 +15,13 @@
*/
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.Pointer;
import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.MenuEntry;
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.Gtk;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.atomic.AtomicInteger;
class GtkMenuEntry implements MenuEntry, GCallback {
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
private final int id = ID_COUNTER.getAndIncrement();

View File

@ -17,15 +17,19 @@ package dorkbox.systemTray.linux;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.linux.jna.GEventCallback;
import dorkbox.systemTray.linux.jna.GdkEventButton;
import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk;
import javafx.application.Platform;
/**
* Class for handling all system tray interactions via GTK.
@ -34,7 +38,7 @@ import dorkbox.systemTray.linux.jna.Gtk;
*/
public
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)
private final List<Object> gtkCallbacks = new ArrayList<Object>();
@ -49,23 +53,21 @@ class GtkSystemTray extends GtkTypeSystemTray {
super();
Gtk.startGui();
final CountDownLatch blockUntilStarted = new CountDownLatch(1);
dispatch(new Runnable() {
@Override
public
void run() {
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
// in extension.js, so don't change it
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 gtkCallback = new GEventCallback() {
final GEventCallback gtkCallback2 = new GEventCallback() {
@Override
public
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
gtkCallbacks.add(gtkCallback);
gtkCallbacks.add(gtkCallback2);
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")
@ -118,6 +149,13 @@ class GtkSystemTray extends GtkTypeSystemTray {
if (!isActive) {
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);
}
}

View File

@ -16,16 +16,17 @@
package dorkbox.systemTray.linux;
import java.io.InputStream;
import java.net.URL;
import com.sun.jna.Pointer;
import dorkbox.systemTray.ImageUtil;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.SystemTrayMenuAction;
import dorkbox.systemTray.linux.jna.Gobject;
import dorkbox.systemTray.linux.jna.Gtk;
import java.io.InputStream;
import java.net.URL;
/**
* Derived from
* 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;
}
if (!isLoaded && (SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE)) {
if (!isLoaded && SystemTray.FORCE_GTK2) {
// if specified, try loading appindicator1 first, maybe it's there?
try {
final NativeLibrary library = JnaHelper.register("appindicator1", AppIndicator.class);

View File

@ -31,6 +31,9 @@ class Gobject {
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_object_unref(Pointer object);

View File

@ -16,9 +16,12 @@
package dorkbox.systemTray.linux.jna;
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.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import com.sun.jna.Function;
import com.sun.jna.Pointer;
@ -47,6 +50,8 @@ class Gtk {
private static boolean alreadyRunningGTK = false;
private static boolean isLoaded = false;
private static Method swtDispatchMethod = null;
/**
* 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-3.so.0 | grep gtk
static {
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE;
// 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+.
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2;
// 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 {
JnaHelper.register("gtk-x11-2.0", Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu");
JnaHelper.register(gtk2LibName, Gtk.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'
@ -86,7 +89,7 @@ class Gtk {
isLoaded = true;
if (SystemTray.DEBUG) {
logger.info("GTK: gtk-x11-2.0");
logger.info("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
@ -100,15 +103,15 @@ class Gtk {
// start with version 3
if (!isLoaded) {
try {
JnaHelper.register("libgtk-3.so.0", Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("libgtk-3.so.0", "gtk_status_icon_position_menu");
JnaHelper.register(gtk3LibName, Gtk.class);
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 it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO.
alreadyRunningGTK = gtk_main_level() != 0;
isLoaded = true;
if (SystemTray.DEBUG) {
logger.info("GTK: libgtk-3.so.0");
logger.info("GTK: {}", gtk3LibName);
}
} catch (Throwable e) {
if (SystemTray.DEBUG) {
@ -120,8 +123,8 @@ class Gtk {
// now version 2
if (!isLoaded) {
try {
JnaHelper.register("gtk-x11-2.0", Gtk.class);
gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu");
JnaHelper.register(gtk2LibName, Gtk.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'
@ -130,7 +133,7 @@ class Gtk {
isLoaded = true;
if (SystemTray.DEBUG) {
logger.info("GTK: gtk-x11-2.0");
logger.info("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
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) {
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.
// If JavaFX/SWT is used, this is UNNECESSARY
if (!alreadyRunningGTK) {
// only necessary if we are the only GTK instance running...
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) {
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() {
@Override
public
void run() {
// prep for the event loop.
gdk_threads_init();
gdk_threads_enter();
GThread.g_thread_init(null);
if (!SystemTray.COMPATIBILITY_MODE) {
gtk_init_check(0);
if (!gtk_init_check(0)) {
if (SystemTray.DEBUG) {
logger.error("Error starting GTK");
}
return;
}
// notify our main thread to continue
blockUntilStarted.countDown();
if (!SystemTray.COMPATIBILITY_MODE) {
// blocks unit quit
gtk_main();
}
gdk_threads_leave();
// blocks unit quit
gtk_main();
}
};
gtkUpdateThread.setName("GTK Native Event Loop");
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!
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) {
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
void dispatch(final Runnable runnable) {
if (gtkUpdateThread == Thread.currentThread()) {
// if we are ALREADY inside the native event
runnable.run();
} else {
if (alreadyRunningGTK) {
// SWT/JavaFX
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() {
@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
gtkCallbacks.removeFirst();// now that we've 'handled' it, we can remove it from our callback list
}
runnable.run();
return Gtk.FALSE; // don't want to call this again
@ -240,22 +289,29 @@ class Gtk {
synchronized (gtkCallbacks) {
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
void shutdownGui() {
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown)
if (!(alreadyRunningGTK || SystemTray.COMPATIBILITY_MODE)) {
gtk_main_quit();
}
dispatch(new Runnable() {
@Override
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
* 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();
/**
* 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
*/
@ -288,7 +337,11 @@ class Gtk {
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_leave();
@ -298,8 +351,6 @@ class Gtk {
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);
// 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 Pointer gtk_status_icon_new_from_icon_name(String iconName);;
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);