From 3d8459ecc377e9f7cb49d4a2856bf4f328607775 Mon Sep 17 00:00:00 2001 From: nathan Date: Thu, 22 Sep 2016 23:56:39 +0200 Subject: [PATCH] Refactored out JavaFX (similar to how SWT was done) so that we can compile to Java 1.6 --- src/dorkbox/systemTray/SystemTray.java | 88 ++++---------- .../systemTray/linux/GtkSystemTray.java | 4 +- src/dorkbox/systemTray/linux/jna/Gtk.java | 53 ++------- src/dorkbox/systemTray/swt/Swt$1.class | Bin 737 -> 0 bytes src/dorkbox/systemTray/swt/Swt.class | Bin 891 -> 0 bytes src/dorkbox/systemTray/swt/Swt.java | 46 -------- src/dorkbox/systemTray/util/JavaFX.java | 110 ++++++++++++++++++ src/dorkbox/systemTray/util/Swt.java | 58 +++++++++ 8 files changed, 202 insertions(+), 157 deletions(-) delete mode 100644 src/dorkbox/systemTray/swt/Swt$1.class delete mode 100644 src/dorkbox/systemTray/swt/Swt.class delete mode 100644 src/dorkbox/systemTray/swt/Swt.java create mode 100644 src/dorkbox/systemTray/util/JavaFX.java create mode 100644 src/dorkbox/systemTray/util/Swt.java diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 3cdf652..1969553 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -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."); - } + }); } } } diff --git a/src/dorkbox/systemTray/linux/GtkSystemTray.java b/src/dorkbox/systemTray/linux/GtkSystemTray.java index 1e9b6c8..3945a80 100644 --- a/src/dorkbox/systemTray/linux/GtkSystemTray.java +++ b/src/dorkbox/systemTray/linux/GtkSystemTray.java @@ -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) { diff --git a/src/dorkbox/systemTray/linux/jna/Gtk.java b/src/dorkbox/systemTray/linux/jna/Gtk.java index 998e8e7..6057a81 100644 --- a/src/dorkbox/systemTray/linux/jna/Gtk.java +++ b/src/dorkbox/systemTray/linux/jna/Gtk.java @@ -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 gtkCallbacks = new LinkedList(); + @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); diff --git a/src/dorkbox/systemTray/swt/Swt$1.class b/src/dorkbox/systemTray/swt/Swt$1.class deleted file mode 100644 index 9b4c3a594a3a2a730d2b1e8636d1762b011c468e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 737 zcmaiy-)a*<6vn@kq}^t-+8ArAwYIg!lBVENuY`&hmI}hEU`p;MyF)T{J0m-j#6Fgw zNPE!-@S(&rn}DEJ7lxVh?K!{mXXg8l%PRm+u^XU5*vL%rVPHPR_S`yoc2bD**iK#C zo4WQvfEuBBDkdV%g&xN52B$K00Tv0{rWnRD&DBRM%YdoMhSJ$MQOwb@AVd?9w~MjX zVvtM1O7aWzey$!87N0Av+zY~5=Wh;seM0TXWU>yzjR*mjLR7IFp^he@+VKv{iPG}z z_-r7HlUcG>Vp5U!MWMVs7i(^$EMYVGSI=BhV1<*p4{HtGBVjJK+vi=aiSc&nRCIU0{vyJEhAx_e;v^%yUlh dybV0y_$oG;TEP}q4b<=ukMNjJzEYo;zn`Mqrk?-+ diff --git a/src/dorkbox/systemTray/swt/Swt.class b/src/dorkbox/systemTray/swt/Swt.class deleted file mode 100644 index 8d51096e387aec8d0926c6be84d0ca88f019010d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 891 zcmah{!EVz)5Ph4Zwc{F+mL`SLQby`$FLd1$;Ux_?y8AO#pzMPC$1W& zZf~3#C@_>y_$Tg0Tn^oLgA?JW49hP=8K$oo@=mkQQ0T;gu#iX5#uAnpN?s_%+w628 zk|REd1jCva`#kFNB-H15u#k>I#jxcqX3%CZgHWCE)E_ZyINlYR!%RvI)^7GKtm2k| zHHMuy8M?xc!ZRgiD#l?j6sdB%1QPLyjdfHQEQ)o-G0!4WQ+Jx)f6ChWg$*0GQDP`^ zHIe@7bKw)YWX^(N{a@VZsaW<#SsKJ+sX?}F+`DAZ$&y6KREO`|cz_yV6S3Y%L=p9+ z<}^PnBsNQG8`!1(EpFfLa|?Ae^jJz39JCBH8MYUxoq^hTj3*2$JP5pyHbRPoq2U}X zq;i=>gCM!RI7@u-Ce%|mX@~ao#2D-YNkE;5DmM-9}jVVJms%^Aks zKNwviqcN%%*qUM|zhAC=L;XF`c{s(RzY!a#;ORBQ>KrjgQO$ji1c4OLj*+MD7uE^L Ay#N3J diff --git a/src/dorkbox/systemTray/swt/Swt.java b/src/dorkbox/systemTray/swt/Swt.java deleted file mode 100644 index 3bdc100..0000000 --- a/src/dorkbox/systemTray/swt/Swt.java +++ /dev/null @@ -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. - *

- * 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(); - } - }); - } -} diff --git a/src/dorkbox/systemTray/util/JavaFX.java b/src/dorkbox/systemTray/util/JavaFX.java new file mode 100644 index 0000000..2c3f6ec --- /dev/null +++ b/src/dorkbox/systemTray/util/JavaFX.java @@ -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. + *

+ * 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."); + } + } + + +} diff --git a/src/dorkbox/systemTray/util/Swt.java b/src/dorkbox/systemTray/util/Swt.java new file mode 100644 index 0000000..72be61f --- /dev/null +++ b/src/dorkbox/systemTray/util/Swt.java @@ -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. + *

+ * SWT system tray types are just GTK trays. + *

+ * 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) + *

+ * 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(); + } + }); + } +}