Cleaned up how SWT/JavaFX work with regards to detection and distpatch.

This commit is contained in:
nathan 2019-06-14 23:50:33 +02:00
parent d58812acaf
commit 9f15411205
11 changed files with 593 additions and 476 deletions

View File

@ -16,19 +16,18 @@
package dorkbox.util;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.jna.JNIEnv;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.PointerByReference;
import com.sun.jna.win32.StdCallFunctionMapper;
// http://hg.openjdk.java.net/jdk/jdk10/file/b09e56145e11/src/java.base/share/native/libjava/ClassLoader.c
// http://hg.openjdk.java.net/jdk10/jdk10/jdk/file/777356696811/src/java.base/share/native/libjava/ClassLoader.c
// http://hg.openjdk.java.net/jdk9/jdk9/jdk/file/65464a307408/src/java.base/share/native/libjava/ClassLoader.c
// http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/be698ac28848/src/share/native/java/lang/ClassLoader.c
@ -44,175 +43,185 @@ import com.sun.jna.win32.StdCallFunctionMapper;
*/
@SuppressWarnings("WeakerAccess")
public class ClassLoaderUtil {
private static JVM libjvm;
@SuppressWarnings({"unchecked", "unused"})
public static class Bootstrap {
public static final int JNI_VERSION_1_1 = 0x00010001;
public static final int JNI_VERSION_1_2 = 0x00010002;
public static final int JNI_VERSION_1_4 = 0x00010004;
public static final int JNI_VERSION_1_6 = 0x00010006;
public static final int JNI_VERSION_1_7 = 0x00010007;
public static final int JNI_VERSION_1_8 = 0x00010008;
// Note: this does not work in java8 x86 *on windows XP windows7, etc. It only works on x64
@SuppressWarnings("UnusedReturnValue")
public
interface JVM extends com.sun.jna.Library {
void JVM_DefineClass(JNIEnv env, String name, Object classLoader, byte[] buffer, int length, Object protectionDomain);
Class JVM_FindLoadedClass(JNIEnv env, Object classLoader, String name);
}
// if we want to change the JNI version, this is how we do it.
public static int JNI_VERSION = JNI_VERSION_1_4;
private static JVM libjvm;
public static
class JavaVM extends Structure {
public static
class ByReference extends JavaVM implements Structure.ByReference {}
public volatile JNIInvokeInterface.ByReference functions;
JavaVM() {
}
@Override
protected
List<String> getFieldOrder() {
//noinspection ArraysAsListWithZeroOrOneArgument
return Arrays.asList("functions");
}
}
// Note: this does not work in java8 x86 *on windows XP windows7, etc. It only works on x64
@SuppressWarnings("UnusedReturnValue")
public
interface JVM extends com.sun.jna.Library {
void JVM_DefineClass(Pointer env, String name, Object loader, byte[] buffer, int length, Object protectionDomain);
int JNI_GetCreatedJavaVMs(JavaVM.ByReference[] vmArray, int bufsize, int[] vmCount);
}
@SuppressWarnings("unused")
public static
class JNIInvokeInterface extends Structure {
public static
class ByReference extends JNIInvokeInterface implements Structure.ByReference {}
public volatile Pointer reserved0;
public volatile Pointer reserved1;
public volatile Pointer reserved2;
public volatile Pointer DestroyJavaVM;
public volatile Pointer AttachCurrentThread;
public volatile Pointer DetachCurrentThread;
public volatile GetEnv GetEnv;
public volatile Pointer AttachCurrentThreadAsDaemon;
@Override
protected
List getFieldOrder() {
return Arrays.asList("reserved0",
"reserved1",
"reserved2",
"DestroyJavaVM",
"AttachCurrentThread",
"DetachCurrentThread",
"GetEnv",
"AttachCurrentThreadAsDaemon");
}
public
interface GetEnv extends com.sun.jna.Callback {
int callback(JavaVM.ByReference vm, PointerByReference penv, int version);
}
}
static {
String libName;
if (OS.isMacOsX()) {
if (OS.javaVersion < 7) {
libName = "JavaVM";
} else {
String javaLocation = System.getProperty("java.home");
// have to explicitly specify the JVM library via full path
// this is OK, because for java on MacOSX, this is the only location it can exist
libName = javaLocation + "/lib/server/libjvm.dylib";
}
}
else {
libName = "jvm";
}
// function name is SLIGHTLY different on windows x32 java builds.
// For actual name use: http://www.nirsoft.net/utils/dll_export_viewer.html
if (OS.isWindows() && OS.is32bit()) {
Map options = new HashMap();
options.put(Library.OPTION_FUNCTION_MAPPER, new StdCallFunctionMapper() {
@Override
public
String getFunctionName(NativeLibrary library, Method method) {
String methodName = method.getName();
if (methodName.equals("JVM_DefineClass")) {
// specifically Oracle Java 32bit builds. Tested on XP and Win7
return "_JVM_DefineClass@24";
}
return methodName;
}
});
libjvm = Native.loadLibrary(libName, JVM.class, options);
private static final String libName;
static {
if (OS.isMacOsX()) {
if (OS.javaVersion < 7) {
libName = "JavaVM";
} else {
libjvm = Native.loadLibrary(libName, JVM.class);
String javaLocation = System.getProperty("java.home");
// have to explicitly specify the JVM library via full path
// this is OK, because for java on MacOSX, this is the only location it can exist
libName = javaLocation + "/lib/server/libjvm.dylib";
}
}
else {
libName = "jvm";
}
/**
* Inject class bytes directly into the bootstrap classloader.
* <p>
* This is a VERY DANGEROUS method to use!
*
* @param classBytes
* the bytes to inject
*/
public static
void defineClass(byte[] classBytes) throws Exception {
// get the number of JVM's running
int[] jvmCount = {100};
libjvm.JNI_GetCreatedJavaVMs(null, 0, jvmCount);
// actually get the JVM's
JavaVM.ByReference[] vms = new JavaVM.ByReference[jvmCount[0]];
for (int i = 0, vmsLength = vms.length; i < vmsLength; i++) {
vms[i] = new JavaVM.ByReference();
}
// function name is SLIGHTLY different on windows x32 java builds.
// For actual name use: http://www.nirsoft.net/utils/dll_export_viewer.html
Map options = new HashMap();
options.put(Library.OPTION_ALLOW_OBJECTS, Boolean.TRUE);
// now get the JVM's
libjvm.JNI_GetCreatedJavaVMs(vms, vms.length, jvmCount);
Exception exception = null;
for (int i = 0; i < jvmCount[0]; ++i) {
JavaVM.ByReference vm = vms[i];
PointerByReference penv = new PointerByReference();
vm.functions.GetEnv.callback(vm, penv, ClassLoaderUtil.Bootstrap.JNI_VERSION);
// inject into all JVM's that are started by us (is USUALLY 1, but not always)
try {
libjvm.JVM_DefineClass(penv.getValue(), null, null, classBytes, classBytes.length, null);
} catch (Exception e) {
exception = e;
if (OS.isWindows() && OS.is32bit()) {
options.put(Library.OPTION_FUNCTION_MAPPER, new StdCallFunctionMapper() {
@Override
public
String getFunctionName(NativeLibrary library, Method method) {
String methodName = method.getName();
if (methodName.equals("JVM_DefineClass")) {
// specifically Oracle Java 32bit builds. Tested on XP and Win7
return "_JVM_DefineClass@24";
}
return methodName;
}
}
// something failed, just show us THE LAST of the failures
if (exception != null) {
throw exception;
}
});
libjvm = Native.load(libName, JVM.class, options);
} else {
libjvm = Native.load(libName, JVM.class, options);
}
}
/**
* Defines a class in the current threads class-loader
* @param bytes the bytes of the class to define
*
* @param classBytes the bytes of the class to define
*/
public static
void defineClass(final byte[] bytes) throws Exception {
void defineClass(final byte[] classBytes) throws Exception {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClass.setAccessible(true);
defineClass.invoke(classLoader, bytes, 0, bytes.length);
defineClass(classLoader, classBytes);
}
/**
* Inject class bytes directly into the bootstrap classloader.
* <p>
* This is a VERY DANGEROUS method to use!
*
* @param classLoader the classLoader to use. null will use the BOOTSTRAP classloader
* @param classBytes the bytes to inject
*/
public static
void defineClass(ClassLoader classLoader, byte[] classBytes) throws Exception {
// inject into the FIRST JVM that are started by us (is USUALLY 1, but not always)
libjvm.JVM_DefineClass(JNIEnv.CURRENT, null, classLoader, classBytes, classBytes.length, null);
}
/**
* Check to see if a class is already loaded or not, WITHOUT linking/loading the class!
*
* You should check all accessible classLoaders!
*
* @param classLoader the classLoader to use. null will use the BOOTSTRAP classloader
* @param className the name to check
*/
public static
boolean isClassLoaded(ClassLoader classLoader, String className) {
// Pointer javaEnv = getJavaEnv();
//
// NativeLibrary library = ((Library.Handler) libjvm).getNativeLibrary();
// Function jvm_findLoadedClass = library.getFunction("JVM_FindLoadedClass");
// jvm_findLoadedClass.invokeObject()
// this.peer = library.getSymbolAddress(functionName);
// result = Native.invokeObject(this, this.peer, callFlags, args);
// jstring NewStringUTF(JNIEnv *env, const char *bytes);
// libjvm.JVM_FindLoadedClass(JNIEnv.CURRENT, classLoader, new JString(className));
// try {
// if (OS.javaVersion < 9) {
// // so we can use reflection to check, but only if we are < java9
//
// } else {
// // if we are java9+, we are "screwed" in that we cannot use reflection to tell if any of the SWT classes are ALREADY loaded.
//
// // // there are problems
// //
// // Class c = new ClassLoader() {
// // Class c = findLoadedClass("org.eclipse.swt.widgets.Display");
// //
// // }.c;
// //
// // // isSwtLoaded_ = SWT.isLoadable() && c != null;
// // isSwtLoaded_= null != Class.forName("org.eclipse.swt.SWTError", false, Swt.class.getClassLoader());
// }
// // isSwtLoaded_= null != Class.forName("org.eclipse.swt.widgets.Display", false, Swt.class.getClassLoader());
//
// // final String SWT_INTERNAL_CLASS = "org.eclipse.swt.internal.gtk.OS";
//
// // FindClassLoader cl = (FindClassLoader) FindClassLoader.class.getClassLoader();
// // isSwtLoaded_= null != FindClassLoader.find(cl, "org.eclipse.swt.widgets.Display");
// } catch (Throwable e) {
// LoggerFactory.getLogger(Swt.class).debug("Error detecting if SWT is loaded", e);
// }
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public
Boolean run() {
try {
Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
m.setAccessible(true);
return null != m.invoke(classLoader, className);
} catch (Exception ignored) {
return false;
}
}
});
}
/**
* Check to see if a class is already loaded or not.
*/
public static
boolean isLibraryLoaded(String libraryName) {
// JVM_FindLoadedClass
// JVM_FindLibraryEntry
// /*
// * Class: java_lang_ClassLoader_NativeLibrary
// * Method: find
// * Signature: (Ljava/lang/String;)J
// */
// JNIEXPORT jlong JNICALL
// Java_java_lang_ClassLoader_00024NativeLibrary_find
// (JNIEnv *env, jobject this, jstring name)
// {
// jlong handle;
// const char *cname;
// jlong res;
//
// if (!initIDs(env))
// return jlong_zero;
//
// handle = (*env)->GetLongField(env, this, handleID);
// cname = (*env)->GetStringUTFChars(env, name, 0);
// if (cname == 0)
// return jlong_zero;
// res = ptr_to_jlong(JVM_FindLibraryEntry(jlong_to_ptr(handle), cname));
// (*env)->ReleaseStringUTFChars(env, name, cname);
// return res;
// }
return false;
}
}

View File

@ -1,159 +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.util;
import java.lang.reflect.Method;
import org.slf4j.LoggerFactory;
/**
* 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 {
public final static boolean isLoaded;
public final static boolean isGtk3;
// Methods are cached for performance
private static final Method dispatchMethod;
private static final Method isEventThreadMethod;
private static final Object isEventThreadObject;
static {
boolean isJavaFxLoaded_ = false;
boolean isJavaFxGtk3_ = false;
try {
// 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 = JavaFX.class.getClassLoader();
// 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_ = (null != m.invoke(cl, "com.sun.javafx.tk.Toolkit")) || (null != m.invoke(cl, "javafx.application.Application"));
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+.
isJavaFxGtk3_ = OS.javaVersion >= 9 && System.getProperty("jdk.gtk.version", "2").equals("3");
}
} catch (Throwable e) {
LoggerFactory.getLogger(JavaFX.class).debug("Error detecting if JavaFX is loaded", e);
}
isLoaded = isJavaFxLoaded_;
isGtk3 = isJavaFxGtk3_;
Method _isEventThreadMethod = null;
Method _dispatchMethod = null;
Object _isEventThreadObject = null;
if (isJavaFxLoaded_) {
try {
Class<?> clazz = Class.forName("javafx.application.Platform");
_dispatchMethod = clazz.getMethod("runLater", Runnable.class);
// JAVA 7
// javafx.application.Platform.isFxApplicationThread();
// JAVA 8
// com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread();
if (OS.javaVersion <= 7) {
clazz = Class.forName("javafx.application.Platform");
_isEventThreadMethod = clazz.getMethod("isFxApplicationThread");
_isEventThreadObject = null;
} else {
clazz = Class.forName("com.sun.javafx.tk.Toolkit");
_isEventThreadMethod = clazz.getMethod("getToolkit");
_isEventThreadObject = _isEventThreadMethod.invoke(null);
_isEventThreadMethod = _isEventThreadObject.getClass().getMethod("isFxUserThread", (java.lang.Class<?>[])null);
}
} catch (Throwable e) {
LoggerFactory.getLogger(JavaFX.class).error("Cannot initialize JavaFX", e);
}
}
dispatchMethod = _dispatchMethod;
isEventThreadMethod = _isEventThreadMethod;
isEventThreadObject = _isEventThreadObject;
}
public static
void dispatch(final Runnable runnable) {
// javafx.application.Platform.runLater(runnable);
try {
dispatchMethod.invoke(null, runnable);
} catch (Throwable e) {
LoggerFactory.getLogger(JavaFX.class)
.error("Unable to execute JavaFX runLater(). Please create an issue with your OS and Java " +
"version so we may further investigate this issue.");
}
}
@SuppressWarnings("ConfusingArgumentToVarargsMethod")
public static
boolean isEventThread() {
// JAVA 7
// javafx.application.Platform.isFxApplicationThread();
// JAVA 8
// com.sun.javafx.tk.Toolkit.getToolkit().isFxUserThread();
try {
if (OS.javaVersion <= 7) {
return (Boolean) isEventThreadMethod.invoke(null);
} else {
return (Boolean) isEventThreadMethod.invoke(isEventThreadObject);
}
} catch (Throwable e) {
LoggerFactory.getLogger(JavaFX.class)
.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) {
LoggerFactory.getLogger(JavaFX.class)
.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

@ -1,147 +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.util;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.slf4j.LoggerFactory;
/**
* Utility methods for SWT. Some of the methods will be overwritten via Javassist so we don't require a hard dependency on SWT.
* <p>
* SWT system tray types are GtkStatusIcon trays (so we don't want to use them)
*/
@SuppressWarnings("Convert2Lambda")
public
class Swt {
public final static boolean isLoaded;
public final static boolean isGtk3;
private static final Display currentDisplay;
private static final Thread currentDisplayThread;
private static final int version;
static {
boolean isSwtLoaded_ = false;
boolean isSwtGtk3_ = false;
try {
// maybe we should load the SWT version? (In order for us to work with SWT, BOTH must be the same!!
// SWT is GTK2, but if -DSWT_GTK3=1 is specified, it can be GTK3
isSwtLoaded_ = null != Class.forName("org.eclipse.swt.widgets.Display");
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"); (or -DSWT_GTK3=1)
// was SWT forced?
String swt_gtk3 = System.getProperty("SWT_GTK3");
isSwtGtk3_ = swt_gtk3 != null && !swt_gtk3.equals("0");
if (!isSwtGtk3_) {
// check a different property
String property = System.getProperty("org.eclipse.swt.internal.gtk.version");
isSwtGtk3_ = property != null && !property.startsWith("2.");
}
}
} catch (Throwable e) {
LoggerFactory.getLogger(Swt.class).debug("Error detecting if SWT is loaded", e);
}
int _version = 0;
// we MUST save the currentDisplay now, otherwise it is "null" when methods are run from the swing EDT.
// also save the SWT version
// NOTE: we cannot check if there is a default display, because JUST CHECKING will initialize a new one
Display _currentDisplay = null;
Thread _currentDisplayThread = null;
if (isSwtLoaded_ && SWT.isLoadable()) {
try {
_version = SWT.getVersion();
_currentDisplay = Display.getCurrent();
if (_currentDisplay != null) {
_currentDisplayThread = Display.getCurrent().getThread();
}
} catch (Throwable e) {
LoggerFactory.getLogger(Swt.class).error("Cannot initialize SWT", e);
}
}
// if we use SWT incorrectly (ie, it's available on the classpath, but we didn't start the application) then
// '_currentDisplay' will be null. This is a reasonable way to detect if SWT is being used or not.
if (_currentDisplay == null) {
_version = 0;
isSwtLoaded_ = false;
isSwtGtk3_ = false;
}
currentDisplay = _currentDisplay;
currentDisplayThread = _currentDisplayThread;
version = _version;
isLoaded = isSwtLoaded_;
isGtk3 = isSwtGtk3_;
}
private static
void onShutdown(final Display currentDisplay, final Runnable runnable) {
// currentDisplay.getShells() must only be called inside the event thread!
org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0];
shell.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();
}
});
}
public static
void dispatch(final Runnable runnable) {
currentDisplay.syncExec(runnable);
}
public static
boolean isEventThread() {
return Thread.currentThread() == currentDisplayThread;
}
public static
void onShutdown(final Runnable runnable) {
// currentDisplay.getShells() must only be called inside the event thread!
if (isEventThread()) {
onShutdown(currentDisplay, runnable);
} else {
dispatch(new Runnable() {
@Override
public
void run() {
onShutdown(currentDisplay, runnable);
}
});
}
}
public static
int getVersion() {
return version;
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.util.javaFx;
import java.security.AccessController;
import java.security.PrivilegedAction;
import dorkbox.util.ClassLoaderUtil;
import dorkbox.util.OS;
import dorkbox.util.swt.Swt;
/**
* Utility methods for JavaFX.
*/
public
class JavaFX {
public final static boolean isLoaded;
public final static boolean isGtk3;
static {
// There is a silly amount of redirection, simply because we have to be able to access JavaFX, but only if it's in use.
// Since this class is the place other code interacts with, we can use JavaFX stuff if necessary without loading/linking
// the JavaFX classes by accident
// We cannot use getToolkit(), because if JavaFX is not being used, calling getToolkit() will initialize it...
// see: https://bugs.openjdk.java.net/browse/JDK-8090933
boolean isJavaFxLoaded_ = ClassLoaderUtil.isClassLoaded(ClassLoader.getSystemClassLoader(), "javafx.application.Platform");
if (!isJavaFxLoaded_) {
// check both classloaders
isJavaFxLoaded_ = ClassLoaderUtil.isClassLoaded(Thread.currentThread().getContextClassLoader(), "javafx.application.Platform");
}
boolean isJavaFxGtk3_ = false;
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+.
if (OS.javaVersion >= 9) {
// HILARIOUSLY enough, you can use JavaFX + SWT..... And the javaFX GTK version info SHOULD
// be based on what SWT has loaded
// https://github.com/teamfx/openjfx-9-dev-rt/blob/master/modules/javafx.graphics/src/main/java/com/sun/glass/ui/gtk/GtkApplication.java
if (Swt.isLoaded && !Swt.isGtk3) {
isJavaFxGtk3_ = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public
Boolean run() {
String version = System.getProperty("jdk.gtk.version", "2");
return "3".equals(version) || version.startsWith("3.");
}
});
}
}
}
isLoaded = isJavaFxLoaded_;
isGtk3 = isJavaFxGtk3_;
}
public static
void dispatch(final Runnable runnable) {
JavaFxDispatch.dispatch(runnable);
}
public static
boolean isEventThread() {
return JavaFxDispatch.isEventThread();
}
public static
void onShutdown(final Runnable runnable) {
JavaFxDispatch.onShutdown(runnable);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.util.javaFx;
import javafx.application.Platform;
/**
* Utility methods for JavaFX. Redirection is used here so we don't load/link the JavaFX classes if we aren't using them
*/
class JavaFxDispatch {
static
void dispatch(final Runnable runnable) {
Platform.runLater(runnable);
}
static
boolean isEventThread() {
return Platform.isFxApplicationThread();
}
static
void onShutdown(final Runnable runnable) {
com.sun.javafx.tk.Toolkit.getToolkit().addShutdownHook(runnable);
}
}

View File

@ -15,9 +15,9 @@
*/
package dorkbox.util.jna.linux;
import dorkbox.util.JavaFX;
import dorkbox.util.javaFx.JavaFX;
import dorkbox.util.SwingUtil;
import dorkbox.util.Swt;
import dorkbox.util.swt.Swt;
/**
* Accessor methods/logic for determining if GTK is already loaded by the Swing/JavaFX/SWT, or if GTK has been manually loaded via

View File

@ -26,8 +26,8 @@ import org.slf4j.LoggerFactory;
import com.sun.jna.Pointer;
import dorkbox.util.JavaFX;
import dorkbox.util.Swt;
import dorkbox.util.javaFx.JavaFX;
import dorkbox.util.swt.Swt;
public
class GtkEventDispatch {
@ -282,12 +282,10 @@ class GtkEventDispatch {
return;
}
if (Swt.isLoaded) {
if (Swt.isEventThread()) {
// Run directly on the SWT event thread. If it's not on the dispatch thread, we can use raw GTK to put it there
runnable.run();
return;
}
if (Swt.isLoaded && Swt.isEventThread()) {
// Run directly on the SWT event thread. If it's not on the dispatch thread, we will use GTK to put it there
runnable.run();
return;
}
}

View File

@ -21,7 +21,7 @@ import com.sun.jna.Function;
import com.sun.jna.NativeLibrary;
import dorkbox.util.OS;
import dorkbox.util.Swt;
import dorkbox.util.swt.Swt;
import dorkbox.util.jna.JnaHelper;
/**

View File

@ -37,7 +37,7 @@ import dorkbox.util.FileUtil;
import dorkbox.util.MathUtil;
import dorkbox.util.OS;
import dorkbox.util.OSUtil;
import dorkbox.util.Swt;
import dorkbox.util.swt.Swt;
import dorkbox.util.jna.linux.structs.GtkRequisition;
import dorkbox.util.jna.linux.structs.GtkStyle;
import dorkbox.util.jna.linux.structs.PangoRectangle;

View File

@ -0,0 +1,78 @@
/*
* 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.util.swt;
import dorkbox.util.ClassLoaderUtil;
/**
* Utility methods for SWT.
*/
@SuppressWarnings("Convert2Lambda")
public
class Swt {
public final static boolean isLoaded;
public final static boolean isGtk3;
private static final int version;
static {
// There is a silly amount of redirection, simply because we have to be able to access SWT, but only if it's in use.
// Since this class is the place other code interacts with, we can use SWT stuff if necessary without loading/linking
// the SWT classes by accident
boolean isSwtLoaded_ = ClassLoaderUtil.isClassLoaded(ClassLoader.getSystemClassLoader(), "org.eclipse.swt.widgets.Display");
if (!isSwtLoaded_) {
// check both classloaders
isSwtLoaded_ = ClassLoaderUtil.isClassLoaded(Thread.currentThread().getContextClassLoader(), "org.eclipse.swt.widgets.Display");
}
int _version = 0;
if (isSwtLoaded_) {
_version = SwtDispatch.getVersion();
}
version = _version;
isLoaded = isSwtLoaded_;
isGtk3 = isSwtLoaded_ && SwtDispatch.isGtk3();
}
public static
void main(String[] args) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
boolean classLo2aded = ClassLoaderUtil.isClassLoaded(contextClassLoader, "dorkbox/util/swt/Swt.java");
boolean classLoaded = ClassLoaderUtil.isClassLoaded(contextClassLoader, "org.eclipse.swt.SWTError");
boolean classLo3aded = ClassLoaderUtil.isClassLoaded(contextClassLoader, ":asd");
}
public static
int getVersion() {
return version;
}
public static
void dispatch(final Runnable runnable) {
SwtDispatch.dispatch(runnable);
}
public static
boolean isEventThread() {
return SwtDispatch.isEventThread();
}
public static
void onShutdown(final Runnable runnable) {
SwtDispatch.onShutdown(runnable);
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.util.swt;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.slf4j.LoggerFactory;
import dorkbox.util.OS;
/**
* Utility methods for SWT.
*/
@SuppressWarnings("Convert2Lambda")
class SwtDispatch {
private static final Display currentDisplay;
private static final Thread currentDisplayThread;
private static final int version;
static {
int _version = 0;
// 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"); (or -DSWT_GTK3=1)
// we MUST save the currentDisplay now, otherwise it is "null" when methods are run from the swing EDT.
// also save the SWT version
// NOTE: we cannot check if there is a default display, because JUST CHECKING will initialize a new one
Display _currentDisplay = null;
Thread _currentDisplayThread = null;
try {
_version = SWT.getVersion();
_currentDisplay = Display.getCurrent();
if (_currentDisplay != null) {
_currentDisplayThread = Display.getCurrent().getThread();
}
} catch (Throwable e) {
LoggerFactory.getLogger(SwtDispatch.class).error("Cannot initialize SWT", e);
}
// if we use SWT incorrectly (ie, it's available on the classpath, but we didn't start the application) then
// '_currentDisplay' will be null. This is a reasonable way to detect if SWT is being used or not.
if (_currentDisplay == null) {
_version = 0;
}
currentDisplay = _currentDisplay;
currentDisplayThread = _currentDisplayThread;
version = _version;
}
/**
* This is an indirect reference to SWT, so we only check if SWT is loadable if certain classes are available.
* @return true if SWT is loadable
*/
static boolean isLoadable() {
Class<?> swtErrorClass = AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
@Override
public
Class<?> run() {
try {
return Class.forName("org.eclipse.swt.SWTError", true, ClassLoader.getSystemClassLoader());
} catch (Exception ignored) {
}
try {
return Class.forName("org.eclipse.swt.SWTError", true, Thread.currentThread().getContextClassLoader());
} catch (Exception ignored) {
}
return null;
}
});
return null != swtErrorClass && SWT.isLoadable();
}
/**
* This is only necessary for linux.
*
* @return true if SWT is GTK3. False if SWT is GTK2. If for some reason we DO NOT KNOW, then we return false (GTK2).
*/
static boolean isGtk3() {
if (!OS.isLinux()) {
return false;
}
final String SWT_INTERNAL_CLASS = "org.eclipse.swt.internal.gtk.OS";
Class<?> osClass = AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
@Override
public
Class<?> run() {
try {
return Class.forName(SWT_INTERNAL_CLASS, true, ClassLoader.getSystemClassLoader());
} catch (Exception ignored) {
}
try {
return Class.forName(SWT_INTERNAL_CLASS, true, Thread.currentThread().getContextClassLoader());
} catch (Exception ignored) {
}
return null;
}
});
if (osClass == null) {
return false;
}
Method method = AccessController.doPrivileged(new PrivilegedAction<Method>() {
@Override
public
Method run() {
try {
return osClass.getMethod("gtk_major_version");
} catch (Exception e) {
return null;
}
}
});
if (method == null) {
return false;
}
int version = 0;
try {
version = ((Number)method.invoke(osClass)).intValue();
} catch (Exception ignored) {
// this method doesn't exist.
}
return version == 3;
}
private static
void onShutdown(final Display currentDisplay, final Runnable runnable) {
// currentDisplay.getShells() must only be called inside the event thread!
org.eclipse.swt.widgets.Shell shell = currentDisplay.getShells()[0];
shell.addListener(SWT.Close, new org.eclipse.swt.widgets.Listener() {
@Override
public
void handleEvent(final org.eclipse.swt.widgets.Event event) {
runnable.run();
}
});
}
static
void dispatch(final Runnable runnable) {
currentDisplay.syncExec(runnable);
}
static
boolean isEventThread() {
return Thread.currentThread() == currentDisplayThread;
}
static
void onShutdown(final Runnable runnable) {
// currentDisplay.getShells() must only be called inside the event thread!
if (isEventThread()) {
onShutdown(currentDisplay, runnable);
} else {
dispatch(new Runnable() {
@Override
public
void run() {
onShutdown(currentDisplay, runnable);
}
});
}
}
static
int getVersion() {
return version;
}
}