Refactored out JavaFX (similar to how SWT was done) so that we can

compile to Java 1.6
This commit is contained in:
nathan 2016-09-22 23:56:39 +02:00
parent efeda2fac3
commit 3d8459ecc3
8 changed files with 202 additions and 157 deletions

View File

@ -23,7 +23,6 @@ import java.io.File;
import java.io.FileReader;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@ -41,6 +40,8 @@ import dorkbox.systemTray.linux.GtkSystemTray;
import dorkbox.systemTray.linux.jna.AppIndicator;
import dorkbox.systemTray.linux.jna.Gtk;
import dorkbox.systemTray.swing.SwingSystemTray;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.Swt;
import dorkbox.util.OS;
import dorkbox.util.Property;
import dorkbox.util.process.ShellProcessBuilder;
@ -85,7 +86,7 @@ class SystemTray {
/**
* This property is provided for debugging any errors in the logic used to determine the system-tray type.
*/
public static boolean DEBUG = false;
public static boolean DEBUG = true;
private static volatile SystemTray systemTray = null;
@ -95,21 +96,15 @@ class SystemTray {
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");
// 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_ = JavaFX.isLoaded();
// SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3
isSwtLoaded_ = Swt.isLoaded();
} catch (Throwable e) {
if (DEBUG) {
logger.debug("Error detecting javaFX/SWT mode", e);
@ -547,65 +542,28 @@ class SystemTray {
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
// com.sun.javafx.tk.Toolkit.getToolkit()
// .addShutdownHook(new Runnable() {
// @Override
// public
// void run() {
// systemTray.shutdown();
// }
// });
try {
Class<?> clazz = Class.forName("com.sun.javafx.tk.Toolkit");
Method method = clazz.getMethod("getToolkit");
Object o = method.invoke(null);
Method runnable = o.getClass()
.getMethod("addShutdownHook", Runnable.class);
runnable.invoke(o, new Runnable() {
@Override
public
void run() {
if (systemTray != null) {
systemTray.shutdown();
}
JavaFX.onShutdown(new Runnable() {
@Override
public
void run() {
if (systemTray != null) {
systemTray.shutdown();
}
});
} catch (Throwable e) {
if (DEBUG) {
logger.error("Cannot initialize JavaFX", e);
}
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.");
}
});
}
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() {
if (systemTray != null) {
systemTray.shutdown();
}
Swt.onShutdown(new Runnable() {
@Override
public
void run() {
if (systemTray != null) {
systemTray.shutdown();
}
});
} catch (Throwable e) {
if (DEBUG) {
logger.error("Cannot initialize SWT", e);
e.printStackTrace();
}
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.");
}
});
}
}
}

View File

@ -29,7 +29,7 @@ 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;
import dorkbox.systemTray.util.JavaFX;
/**
* Class for handling all system tray interactions via GTK.
@ -89,7 +89,7 @@ class GtkSystemTray extends GtkTypeSystemTray {
});
if (SystemTray.isJavaFxLoaded) {
if (!Platform.isFxApplicationThread()) {
if (!JavaFX.isEventThread()) {
try {
blockUntilStarted.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {

View File

@ -18,7 +18,6 @@ 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;
@ -27,6 +26,8 @@ import com.sun.jna.Function;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.JavaFX;
import dorkbox.systemTray.util.Swt;
/**
* bindings for gtk 2 or 3
@ -50,8 +51,8 @@ class Gtk {
private static boolean alreadyRunningGTK = false;
private static boolean isLoaded = false;
private static Method swtDispatchMethod = null;
// 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
/**
* We can have GTK v3 or v2.
@ -60,10 +61,6 @@ class Gtk {
* 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.
*/
// 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;
@ -159,8 +156,10 @@ class Gtk {
private static volatile boolean started = false;
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>();
@SuppressWarnings("FieldCanBeLocal")
private static Thread gtkUpdateThread = null;
public static final int FALSE = 0;
@ -228,20 +227,6 @@ class Gtk {
}
}
}
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.");
}
}
}
/**
@ -253,22 +238,16 @@ class Gtk {
// SWT/JavaFX
if (SystemTray.isJavaFxLoaded) {
if (javafx.application.Platform.isFxApplicationThread()) {
if (JavaFX.isEventThread()) {
// Run directly on the JavaFX event thread
runnable.run();
}
else {
javafx.application.Platform.runLater(runnable);
JavaFX.dispatch(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");
}
Swt.dispatch(runnable);
}
}
else {
@ -335,17 +314,6 @@ class Gtk {
*/
private static native void gtk_main_quit();
private static native void gdk_threads_init();
// 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();
@ -371,7 +339,6 @@ 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 label);
@ -397,8 +364,6 @@ class Gtk {
public static native void gtk_container_remove(Pointer menu, Pointer subItem);
public static native void gtk_widget_show(Pointer widget);
public static native void gtk_widget_show_all(Pointer widget);
public static native void gtk_widget_destroy(Pointer widget);

Binary file not shown.

View File

@ -1,46 +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.swt;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
/**
* SWT system tray types are just GTK trays.
* <p>
* This isn't possible with reflection - so we compile it AHEAD of time and save the bytecode (which is then included with the release)
* We **COULD** do some ASM/Bytecode generation, but why bother with even more libraries when it's just as easy to include it pre-compiled.
*/
public
class Swt {
public static
void dispatch(final Runnable runnable) {
Display.getDefault().asyncExec(runnable);
}
public static
void onShutdown(final Runnable runnable) {
Display.getCurrent().getShells()[0].addListener(SWT.Close, new Listener() {
@Override
public
void handleEvent(final Event event) {
runnable.run();
}
});
}
}

View File

@ -0,0 +1,110 @@
/*
* 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;
/**
* Utility methods for JavaFX.
* <p>
* 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 Method isEventThread;
private static Method dispatchMethod;
public static
boolean isLoaded() throws Exception {
// 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
// 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();
return (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application"));
}
public static
void dispatch(final Runnable runnable) {
// javafx.application.Platform.runLater(runnable);
try {
if (dispatchMethod == null) {
Class<?> clazz = Class.forName("javafx.application.Platform");
dispatchMethod = clazz.getMethod("runLater");
}
dispatchMethod.invoke(null, runnable);
} catch (Throwable e) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Cannot initialize JavaFX", 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() {
// javafx.application.Platform.isFxApplicationThread();
try {
if (isEventThread == null) {
Class<?> clazz = Class.forName("javafx.application.Platform");
isEventThread = clazz.getMethod("isFxApplicationThread");
}
return (Boolean) isEventThread.invoke(null);
} catch (Throwable e) {
if (SystemTray.DEBUG) {
SystemTray.logger.error("Cannot initialize JavaFX", 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.");
}
}
}

View File

@ -0,0 +1,58 @@
/*
* 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;
/**
* Utility methods for SWT.
* <p>
* SWT system tray types are just GTK trays.
* <p>
* Creating custom methods for a class is not possible with reflection - so we compile it AHEAD of time as Java 1.6, and save the bytecode
* (which is then included with the release)
* <p>
* We **COULD** do some ASM/Bytecode generation, but why bother with even more libraries when it's just as easy to include it pre-compiled.
*/
public
class Swt {
public static
boolean isLoaded() throws Exception {
// 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
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true);
ClassLoader cl = ClassLoader.getSystemClassLoader();
return null != m.invoke(cl, "org.eclipse.swt.widgets.Display");
}
public static
void dispatch(final Runnable runnable) {
org.eclipse.swt.widgets.Display.getCurrent()
.asyncExec(runnable);
}
public static
void onShutdown(final Runnable runnable) {
org.eclipse.swt.widgets.Display.getCurrent()
.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();
}
});
}
}