From 9bf01aaf0487beb479438e2c68a318360d45e582 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 11 Jul 2017 01:27:19 +0200 Subject: [PATCH] Moved Gtk Event Dispatch into it's own class. --- src/dorkbox/systemTray/jna/linux/Gtk.java | 326 +---------------- src/dorkbox/systemTray/jna/linux/Gtk3.java | 70 ++-- .../jna/linux/GtkEventDispatch.java | 336 ++++++++++++++++++ .../systemTray/jna/linux/GtkTheme.java | 12 +- .../systemTray/nativeUI/GtkBaseMenuItem.java | 3 +- src/dorkbox/systemTray/nativeUI/GtkMenu.java | 11 +- .../systemTray/nativeUI/GtkMenuItem.java | 11 +- .../nativeUI/GtkMenuItemCheckbox.java | 11 +- .../nativeUI/GtkMenuItemSeparator.java | 5 +- .../nativeUI/GtkMenuItemStatus.java | 5 +- .../nativeUI/_AppIndicatorNativeTray.java | 14 +- .../nativeUI/_GtkStatusIconNativeTray.java | 17 +- .../systemTray/swingUI/_SwingTray.java | 4 +- 13 files changed, 432 insertions(+), 393 deletions(-) create mode 100644 src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java diff --git a/src/dorkbox/systemTray/jna/linux/Gtk.java b/src/dorkbox/systemTray/jna/linux/Gtk.java index 8615afe..08decfd 100644 --- a/src/dorkbox/systemTray/jna/linux/Gtk.java +++ b/src/dorkbox/systemTray/jna/linux/Gtk.java @@ -17,20 +17,11 @@ package dorkbox.systemTray.jna.linux; import static dorkbox.systemTray.SystemTray.logger; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.LinkedList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - import com.sun.jna.Function; import com.sun.jna.NativeLibrary; import com.sun.jna.Pointer; -import dorkbox.systemTray.Entry; import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.util.JavaFX; -import dorkbox.systemTray.util.Swt; import dorkbox.util.OS; import dorkbox.util.jna.JnaHelper; @@ -86,33 +77,10 @@ class Gtk { public static final int FALSE = 0; public static final int TRUE = 1; - private static final boolean alreadyRunningGTK; - - // when debugging the EDT, we need a longer timeout. - private static final boolean debugEDT = true; - - // timeout is in seconds - private static final int TIMEOUT = debugEDT ? 10000000 : 2; - - // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) - private static final LinkedList gtkCallbacks = new LinkedList(); + static final boolean alreadyRunningGTK; public static Function gtk_status_icon_position_menu = null; - // This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread - private static ThreadLocal isDispatch = new ThreadLocal() { - @Override - protected - Boolean initialValue() { - return false; - } - }; - - private static volatile boolean started = false; - - @SuppressWarnings("FieldCanBeLocal") - private static Thread gtkUpdateThread = null; - public static final int MAJOR; public static final int MINOR; public static final int MICRO; @@ -274,298 +242,6 @@ class Gtk { } } - public static - void startGui() { - // only permit one startup per JVM instance - if (!started) { - started = true; - - // startup the GTK GUI event loop. There can be multiple/nested loops. - - - if (!alreadyRunningGTK) { - // If JavaFX/SWT is used, this is UNNECESSARY (we can detect if the GTK main_loop is running) - - gtkUpdateThread = new Thread() { - @Override - public - void run() { - Glib.GLogFunc orig = null; - if (SystemTray.DEBUG) { - logger.debug("Running GTK Native Event Loop"); - } else { - // NOTE: This can output warnings, so we suppress them - orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null); - } - - - // prep for the event loop. - // GThread.g_thread_init(null); would be needed for g_idle_add() - - if (!Gtk2.gtk_init_check(0)) { - if (SystemTray.DEBUG) { - logger.error("Error starting GTK"); - } - return; - } - - // gdk_threads_enter(); would be needed for g_idle_add() - - if (orig != null) { - Glib.g_log_set_default_handler(orig, null); - } - - // blocks unit quit - Gtk2.gtk_main(); - - // clean up threads - // gdk_threads_leave(); would be needed for g_idle_add() - } - }; - gtkUpdateThread.setDaemon(false); // explicitly NOT daemon so that this will hold the JVM open as necessary - gtkUpdateThread.setName("GTK Native Event Loop"); - gtkUpdateThread.start(); - } - } - } - - /** - * Waits for the all posted events to GTK to finish loading - */ - public static - void waitForEventsToComplete() { - final CountDownLatch blockUntilStarted = new CountDownLatch(1); - - dispatch(new Runnable() { - @Override - public - void run() { - blockUntilStarted.countDown(); - } - }); - - if (SystemTray.isJavaFxLoaded) { - if (!JavaFX.isEventThread()) { - try { - if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { - if (SystemTray.DEBUG) { - SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", - new Exception("")); - } - } - - // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues - while (true) { - Thread.sleep(100); - - synchronized (gtkCallbacks) { - if (gtkCallbacks.isEmpty()) { - break; - } - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - else if (SystemTray.isSwtLoaded) { - if (!Swt.isEventThread()) { - // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues - try { - if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { - if (SystemTray.DEBUG) { - SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", - new Exception("")); - } - } - - while (true) { - Thread.sleep(100); - - synchronized (gtkCallbacks) { - if (gtkCallbacks.isEmpty()) { - break; - } - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - else { - try { - if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { - if (SystemTray.DEBUG) { - SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", - new Exception("")); - } - } - - // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues - while (true) { - Thread.sleep(100); - - synchronized (gtkCallbacks) { - if (gtkCallbacks.isEmpty()) { - break; - } - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - /** - * Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that. - */ - public static - void dispatch(final Runnable runnable) { - if (alreadyRunningGTK) { - if (SystemTray.isJavaFxLoaded) { - // JavaFX only - if (JavaFX.isEventThread()) { - // Run directly on the JavaFX event thread - runnable.run(); - } - else { - JavaFX.dispatch(runnable); - } - - return; - } - - if (SystemTray.isSwtLoaded) { - 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; - } - } - } - - // not javafx - // gtk/swt are **mostly** the same in how events are dispatched, so we can use "raw" gtk methods for SWT - if (isDispatch.get()) { - // Run directly on the dispatch thread - runnable.run(); - } - 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 - } - - isDispatch.set(true); - - try { - runnable.run(); - } finally { - isDispatch.set(false); - } - - return Gtk.FALSE; // don't want to call this again - } - }; - - synchronized (gtkCallbacks) { - gtkCallbacks.offer(callback); // prevent GC from collecting this object before it can be called - } - - // the correct way to do it. Add with a slightly higher value - Gtk2.gdk_threads_add_idle_full(100, callback, null, null); - } - } - - public static - void shutdownGui() { - dispatchAndWait(new Runnable() { - @Override - public - void run() { - // If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown) - if (!alreadyRunningGTK) { - Gtk2.gtk_main_quit(); - } - - started = false; - } - }); - } - - public static - void dispatchAndWait(final Runnable runnable) { - if (isDispatch.get()) { - // Run directly on the dispatch thread (should not "redispatch" this again) - runnable.run(); - } - else { - final CountDownLatch countDownLatch = new CountDownLatch(1); - - Gtk.dispatch(new Runnable() { - @Override - public - void run() { - try { - runnable.run(); - } catch (Exception e) { - SystemTray.logger.error("Error during GTK run loop: ", e); - } finally { - countDownLatch.countDown(); - } - } - }); - - // this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI - // thread occur in REASONABLE time-frames, and alert the user if not. - try { - if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) { - if (SystemTray.DEBUG) { - SystemTray.logger.error( - "Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT + " seconds " + - "to complete.", new Exception("")); - } - else { - throw new RuntimeException("Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT + - " seconds " + "to complete."); - } - } - } catch (InterruptedException e) { - SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception("")); - } - } - } - - /** - * required to properly setup the dispatch flag when using native menus - * - * @param callback will never be null. - */ - public static - void proxyClick(final Entry menuEntry, final ActionListener callback) { - Gtk.isDispatch.set(true); - - try { - if (menuEntry != null) { - callback.actionPerformed(new ActionEvent(menuEntry, ActionEvent.ACTION_PERFORMED, "")); - } - else { - // checkbox entries will not pass the menuEntry in, because they redispatch the click event so that the checkbox state is - // toggled - callback.actionPerformed(null); - } - } finally { - Gtk.isDispatch.set(false); - } - } - /** * Creates a new GtkMenu */ diff --git a/src/dorkbox/systemTray/jna/linux/Gtk3.java b/src/dorkbox/systemTray/jna/linux/Gtk3.java index d8ae0fa..66e544b 100644 --- a/src/dorkbox/systemTray/jna/linux/Gtk3.java +++ b/src/dorkbox/systemTray/jna/linux/Gtk3.java @@ -19,7 +19,7 @@ import com.sun.jna.Pointer; /** * bindings for GTK+ 3. - * + *

* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md */ public @@ -27,9 +27,14 @@ class Gtk3 { // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk // objdump -T /usr/local/lib/libgtk-3.so.0 | grep gtk - public static native int gtk_get_major_version(); - public static native int gtk_get_minor_version(); - public static native int gtk_get_micro_version(); + public static native + int gtk_get_major_version(); + + public static native + int gtk_get_minor_version(); + + public static native + int gtk_get_micro_version(); /** @@ -39,65 +44,75 @@ class Gtk3 { * @param variant variant to load, for example, "dark", or NULL for the default. * * @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it. + * * @since 3.0 */ - public static native Pointer gtk_css_provider_get_named(String name, String variant); + public static native + Pointer gtk_css_provider_get_named(String name, String variant); /** * Returns the provider containing the style settings used as a fallback for all widgets. * * @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it. + * * @since 3.0 */ - public static native Pointer gtk_css_provider_get_default(); + public static native + Pointer gtk_css_provider_get_default(); /** * Converts the provider into a string representation in CSS format. - * + *

* Using gtk_css_provider_load_from_data() with the return value from this function on a new provider created with * gtk_css_provider_new() will basically create a duplicate of this provider . * * @since 3.2 (released in 2011) */ - public static native String gtk_css_provider_to_string(Pointer provider); + public static native + String gtk_css_provider_to_string(Pointer provider); /** * Gets the foreground color for a given state. * * @since 3.0 */ - public static native void gtk_style_context_get_color(Pointer context, int stateFlags, Pointer color); + public static native + void gtk_style_context_get_color(Pointer context, int stateFlags, Pointer color); /** * Returns the state used for style matching. * * @since 3.0 */ - public static native int gtk_style_context_get_state(Pointer context); + public static native + int gtk_style_context_get_state(Pointer context); /** * Looks up and resolves a color name in the context color map. * * @since 3.0 (but not in the documentation...) */ - public static native boolean gtk_style_context_lookup_color(Pointer widget, String name, Pointer color); + public static native + boolean gtk_style_context_lookup_color(Pointer widget, String name, Pointer color); /** * Returns the style context associated to widget . The returned object is guaranteed to be the same for the lifetime of widget . * * @since 3.0 (but not in the documentation...) */ - public static native Pointer gtk_widget_get_style_context(Pointer widget); + public static native + Pointer gtk_widget_get_style_context(Pointer widget); /** * Saves the context state, so temporary modifications done through gtk_style_context_add_class(), gtk_style_context_remove_class(), * gtk_style_context_set_state(), etc. can quickly be reverted in one go through gtk_style_context_restore(). - * + *

* The matching call to gtk_style_context_restore() must be done before GTK returns to the main loop. * * @since 3.0 */ - public static native void gtk_style_context_save(Pointer context); + public static native + void gtk_style_context_save(Pointer context); /** @@ -105,7 +120,8 @@ class Gtk3 { * * @since 3.0 */ - public static native void gtk_style_context_restore(Pointer context); + public static native + void gtk_style_context_restore(Pointer context); /** * Adds a style class to context , so posterior calls to gtk_style_context_get() or any of the gtk_render_*() functions will make @@ -113,32 +129,35 @@ class Gtk3 { * * @since 3.0 */ - public static native void gtk_style_context_add_class(Pointer context, String className); + public static native + void gtk_style_context_add_class(Pointer context, String className); /** * Gets the padding for a given state as a GtkBorder. See gtk_style_context_get() and GTK_STYLE_PROPERTY_PADDING for details. * * @since 3.0 */ - public static native void gtk_style_context_get_padding(Pointer context, int state, Pointer border); + public static native + void gtk_style_context_get_padding(Pointer context, int state, Pointer border); /** * Gets the border for a given state as a GtkBorder. - * + *

* See gtk_style_context_get_property() and GTK_STYLE_PROPERTY_BORDER_WIDTH for details. * * @since 3.0 */ - public static native void gtk_style_context_get_border(Pointer context, int state, Pointer border); + public static native + void gtk_style_context_get_border(Pointer context, int state, Pointer border); /** * Returns the internal scale factor that maps from window coordinates to the actual device pixels. On traditional systems this is 1, * but on very high density outputs this can be a higher value (often 2). - * + *

* A higher value means that drawing is automatically scaled up to a higher resolution, so any code doing drawing will automatically * look nicer. However, if you are supplying pixel-based data the scale value can be used to determine whether to use a pixel * resource with higher resolution data. - * + *

* The scale of a window may change during runtime, if this happens a configure event will be sent to the toplevel window. * * @return the scale factor @@ -146,20 +165,19 @@ class Gtk3 { * @since 3.10 */ public static native - int gdk_window_get_scale_factor (Pointer window); + int gdk_window_get_scale_factor(Pointer window); /** * Retrieves the minimum and natural size of a widget, taking into account the widget’s preference for height-for-width management. - * + *

* This is used to retrieve a suitable size by container widgets which do not impose any restrictions on the child placement. * It can be used to deduce toplevel window and menu sizes as well as child widgets in free-form containers such as GtkLayout. - * + *

* Handle with care. Note that the natural height of a height-for-width widget will generally be a smaller size than the minimum * height, since the required height for the natural width is generally smaller than the required height for the minimum width. - * + *

* Use gtk_widget_get_preferred_height_and_baseline_for_width() if you want to support baseline alignment. * - * * @param widget a GtkWidget instance * @param minimum_size location for storing the minimum size, or NULL. * @param natural_size location for storing the natural size, or NULL. diff --git a/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java b/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java new file mode 100644 index 0000000..ce73dbc --- /dev/null +++ b/src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java @@ -0,0 +1,336 @@ +package dorkbox.systemTray.jna.linux; + +import static dorkbox.systemTray.SystemTray.logger; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.sun.jna.Pointer; + +import dorkbox.systemTray.Entry; +import dorkbox.systemTray.SystemTray; +import dorkbox.systemTray.util.JavaFX; +import dorkbox.systemTray.util.Swt; + +public +class GtkEventDispatch { + // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) + private static final LinkedList gtkCallbacks = new LinkedList(); + + // This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread + private static ThreadLocal isDispatch = new ThreadLocal() { + @Override + protected + Boolean initialValue() { + return false; + } + }; + + private static volatile boolean started = false; + + @SuppressWarnings("FieldCanBeLocal") + private static Thread gtkUpdateThread = null; + + // when debugging the EDT, we need a longer timeout. + private static final boolean debugEDT = true; + + // timeout is in seconds + private static final int TIMEOUT = debugEDT ? 10000000 : 2; + + + public static + void startGui() { + // only permit one startup per JVM instance + if (!started) { + started = true; + + // startup the GTK GUI event loop. There can be multiple/nested loops. + + + if (!Gtk.alreadyRunningGTK) { + // If JavaFX/SWT is used, this is UNNECESSARY (we can detect if the GTK main_loop is running) + + gtkUpdateThread = new Thread() { + @Override + public + void run() { + Glib.GLogFunc orig = null; + if (SystemTray.DEBUG) { + logger.debug("Running GTK Native Event Loop"); + } else { + // NOTE: This can output warnings, so we suppress them + orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null); + } + + + // prep for the event loop. + // GThread.g_thread_init(null); would be needed for g_idle_add() + + if (!Gtk2.gtk_init_check(0)) { + if (SystemTray.DEBUG) { + logger.error("Error starting GTK"); + } + return; + } + + // gdk_threads_enter(); would be needed for g_idle_add() + + if (orig != null) { + Glib.g_log_set_default_handler(orig, null); + } + + // blocks unit quit + Gtk2.gtk_main(); + + // clean up threads + // gdk_threads_leave(); would be needed for g_idle_add() + } + }; + gtkUpdateThread.setDaemon(false); // explicitly NOT daemon so that this will hold the JVM open as necessary + gtkUpdateThread.setName("GTK Native Event Loop"); + gtkUpdateThread.start(); + } + } + } + + /** + * Waits for the all posted events to GTK to finish loading + */ + @SuppressWarnings("Duplicates") + public static + void waitForEventsToComplete() { + final CountDownLatch blockUntilStarted = new CountDownLatch(1); + + dispatch(new Runnable() { + @Override + public + void run() { + blockUntilStarted.countDown(); + } + }); + + if (SystemTray.isJavaFxLoaded) { + if (!JavaFX.isEventThread()) { + try { + if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { + if (SystemTray.DEBUG) { + SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", + new Exception("")); + } + } + + // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues + while (true) { + Thread.sleep(100); + + synchronized (gtkCallbacks) { + if (gtkCallbacks.isEmpty()) { + break; + } + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + else if (SystemTray.isSwtLoaded) { + if (!Swt.isEventThread()) { + // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues + try { + if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { + if (SystemTray.DEBUG) { + SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", + new Exception("")); + } + } + + while (true) { + Thread.sleep(100); + + synchronized (gtkCallbacks) { + if (gtkCallbacks.isEmpty()) { + break; + } + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + else { + try { + if (!blockUntilStarted.await(10, TimeUnit.SECONDS)) { + if (SystemTray.DEBUG) { + SystemTray.logger.error("Something is very wrong. The waitForEventsToComplete took longer than expected.", + new Exception("")); + } + } + + // we have to WAIT until all events are done processing, OTHERWISE we have initialization issues + while (true) { + Thread.sleep(100); + + synchronized (gtkCallbacks) { + if (gtkCallbacks.isEmpty()) { + break; + } + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + /** + * Best practices for GTK, is to call EVERYTHING for it on the GTK THREAD. This accomplishes that. + */ + public static + void dispatch(final Runnable runnable) { + if (Gtk.alreadyRunningGTK) { + if (SystemTray.isJavaFxLoaded) { + // JavaFX only + if (JavaFX.isEventThread()) { + // Run directly on the JavaFX event thread + runnable.run(); + } + else { + JavaFX.dispatch(runnable); + } + + return; + } + + if (SystemTray.isSwtLoaded) { + 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; + } + } + } + + // not javafx + // gtk/swt are **mostly** the same in how events are dispatched, so we can use "raw" gtk methods for SWT + if (isDispatch.get()) { + // Run directly on the dispatch thread + runnable.run(); + } + 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 + } + + isDispatch.set(true); + + try { + runnable.run(); + } finally { + isDispatch.set(false); + } + + return Gtk.FALSE; // don't want to call this again + } + }; + + synchronized (gtkCallbacks) { + gtkCallbacks.offer(callback); // prevent GC from collecting this object before it can be called + } + + // the correct way to do it. Add with a slightly higher value + Gtk2.gdk_threads_add_idle_full(100, callback, null, null); + } + } + + public static + void shutdownGui() { + dispatchAndWait(new Runnable() { + @Override + public + void run() { + // If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown) + if (!Gtk.alreadyRunningGTK) { + Gtk2.gtk_main_quit(); + } + + started = false; + } + }); + } + + public static + void dispatchAndWait(final Runnable runnable) { + if (isDispatch.get()) { + // Run directly on the dispatch thread (should not "redispatch" this again) + runnable.run(); + } + else { + final CountDownLatch countDownLatch = new CountDownLatch(1); + + dispatch(new Runnable() { + @Override + public + void run() { + try { + runnable.run(); + } catch (Exception e) { + SystemTray.logger.error("Error during GTK run loop: ", e); + } finally { + countDownLatch.countDown(); + } + } + }); + + // this is slightly different than how swing does it. We have a timeout here so that we can make sure that updates on the GUI + // thread occur in REASONABLE time-frames, and alert the user if not. + try { + if (!countDownLatch.await(TIMEOUT, TimeUnit.SECONDS)) { + if (SystemTray.DEBUG) { + SystemTray.logger.error( + "Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT + " seconds " + + "to complete.", new Exception("")); + } + else { + throw new RuntimeException("Something is very wrong. The Event Dispatch Queue took longer than " + TIMEOUT + + " seconds " + "to complete."); + } + } + } catch (InterruptedException e) { + SystemTray.logger.error("Error waiting for dispatch to complete.", new Exception("")); + } + } + } + + /** + * required to properly setup the dispatch flag when using native menus + * + * @param callback will never be null. + */ + public static + void proxyClick(final Entry menuEntry, final ActionListener callback) { + isDispatch.set(true); + + try { + if (menuEntry != null) { + callback.actionPerformed(new ActionEvent(menuEntry, ActionEvent.ACTION_PERFORMED, "")); + } + else { + // checkbox entries will not pass the menuEntry in, because they redispatch the click event so that the checkbox state is + // toggled + callback.actionPerformed(null); + } + } finally { + isDispatch.set(false); + } + } +} diff --git a/src/dorkbox/systemTray/jna/linux/GtkTheme.java b/src/dorkbox/systemTray/jna/linux/GtkTheme.java index 150c3d4..9e5985e 100644 --- a/src/dorkbox/systemTray/jna/linux/GtkTheme.java +++ b/src/dorkbox/systemTray/jna/linux/GtkTheme.java @@ -114,7 +114,7 @@ class GtkTheme { int getMenuEntryImageSize() { final AtomicReference imageHeight = new AtomicReference(); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { @@ -162,7 +162,7 @@ class GtkTheme { final AtomicReference screenScale = new AtomicReference(); final AtomicInteger screenDPI = new AtomicInteger(); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { @@ -431,7 +431,7 @@ My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 fac // try to use GTK to get the tray icon size final AtomicInteger traySize = new AtomicInteger(); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { @@ -498,7 +498,7 @@ My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 fac // these methods are from most accurate (but limited in support) to compatible across Linux OSes.. Strangely enough, GTK makes // this information insanely difficult to get. final AtomicReference color = new AtomicReference(null); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @SuppressWarnings("UnusedAssignment") @Override public @@ -700,7 +700,7 @@ My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 fac if (Gtk.isLoaded && Gtk.isGtk3) { final AtomicReference css_ = new AtomicReference(null); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { @@ -1218,7 +1218,7 @@ My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 fac String getThemeName() { final AtomicReference themeName = new AtomicReference(null); - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java b/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java index e1382a6..7403c0d 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java +++ b/src/dorkbox/systemTray/nativeUI/GtkBaseMenuItem.java @@ -21,6 +21,7 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.peer.EntryPeer; import dorkbox.systemTray.util.ImageResizeUtil; @@ -102,7 +103,7 @@ class GtkBaseMenuItem implements EntryPeer { @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenu.java b/src/dorkbox/systemTray/nativeUI/GtkMenu.java index 92058de..f911061 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenu.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenu.java @@ -28,6 +28,7 @@ import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.Separator; import dorkbox.systemTray.Status; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.peer.MenuPeer; @SuppressWarnings("deprecation") @@ -196,7 +197,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { public void add(final Menu parentMenu, final Entry entry, final int index) { // must always be called on the GTK dispatch. This must be dispatchAndWait - Gtk.dispatchAndWait(new Runnable() { + GtkEventDispatch.dispatchAndWait(new Runnable() { @Override public void run() { @@ -250,7 +251,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { // is overridden by system tray setLegitImage(menuItem.getImage() != null); - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -279,7 +280,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { public void setEnabled(final MenuItem menuItem) { // is overridden by system tray - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -318,7 +319,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { textWithMnemonic = menuItem.getText(); } - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -362,7 +363,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer { @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java b/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java index 8c5229c..70110a1 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenuItem.java @@ -24,6 +24,7 @@ import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.jna.linux.GCallback; import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.peer.MenuItemPeer; class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { @@ -58,7 +59,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { final ActionListener cb = menuItemForActionCallback.getCallback(); if (cb != null) { try { - Gtk.proxyClick(menuItemForActionCallback, cb); + GtkEventDispatch.proxyClick(menuItemForActionCallback, cb); } catch (Exception e) { SystemTray.logger.error("Error calling menu entry {} click event.", menuItemForActionCallback.getText(), e); } @@ -77,7 +78,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { void setImage(final MenuItem menuItem) { setLegitImage(menuItem.getImage() != null); - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -104,7 +105,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { @Override public void setEnabled(final MenuItem menuItem) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -141,7 +142,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { textWithMnemonic = menuItem.getText(); } - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -169,7 +170,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback { @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java b/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java index e5bed0f..1a130c9 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenuItemCheckbox.java @@ -27,6 +27,7 @@ import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.jna.linux.GCallback; import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.jna.linux.GtkTheme; import dorkbox.systemTray.peer.CheckboxPeer; import dorkbox.systemTray.util.HeavyCheckMark; @@ -127,7 +128,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall int callback(final Pointer instance, final Pointer data) { if (callback != null) { // this will redispatch to our created callback via `setCallback` - Gtk.proxyClick(null, callback); + GtkEventDispatch.proxyClick(null, callback); } return Gtk.TRUE; @@ -148,7 +149,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall @Override public void setEnabled(final Checkbox menuItem) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -180,7 +181,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall textWithMnemonic = menuItem.getText(); } - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -227,7 +228,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall if (checked != this.isChecked) { this.isChecked = checked; - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -283,7 +284,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java b/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java index feee363..1cde789 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenuItemSeparator.java @@ -16,6 +16,7 @@ package dorkbox.systemTray.nativeUI; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.peer.EntryPeer; class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { @@ -35,7 +36,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -46,11 +47,13 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer { }); } + @Override public boolean hasImage() { return false; } + @Override public void setSpacerImage(final boolean everyoneElseHasImages) { // no op diff --git a/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java b/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java index a20171f..a85241d 100644 --- a/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java +++ b/src/dorkbox/systemTray/nativeUI/GtkMenuItemStatus.java @@ -17,6 +17,7 @@ package dorkbox.systemTray.nativeUI; import dorkbox.systemTray.Status; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.peer.StatusPeer; // you might wonder WHY this extends MenuEntryItem -- the reason is that an AppIndicator "status" will be offset from everyone else, @@ -40,7 +41,7 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer { @Override public void setText(final Status menuItem) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -59,7 +60,7 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer { @Override public void remove() { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java b/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java index 3345e3b..37a1500 100644 --- a/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java +++ b/src/dorkbox/systemTray/nativeUI/_AppIndicatorNativeTray.java @@ -27,7 +27,7 @@ import dorkbox.systemTray.gnomeShell.Extension; import dorkbox.systemTray.jna.linux.AppIndicator; import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct; import dorkbox.systemTray.jna.linux.Gobject; -import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.systemTray.util.ImageResizeUtil; /** @@ -133,7 +133,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI { @Override public void setEnabled(final MenuItem menuItem) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -160,7 +160,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI { return; } - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -195,7 +195,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI { final AppIndicatorInstanceStruct savedAppIndicator = appIndicator; appIndicator = null; - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -209,12 +209,12 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI { super.remove(); // does not need to be called on the dispatch (it does that) - Gtk.shutdownGui(); + GtkEventDispatch.shutdownGui(); } } }; - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -226,7 +226,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI { } }); - Gtk.waitForEventsToComplete(); + GtkEventDispatch.waitForEventsToComplete(); bind(gtkMenu, null, systemTray); } diff --git a/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java b/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java index 44c95eb..5ee1f4f 100644 --- a/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java +++ b/src/dorkbox/systemTray/nativeUI/_GtkStatusIconNativeTray.java @@ -28,6 +28,7 @@ import dorkbox.systemTray.jna.linux.GEventCallback; import dorkbox.systemTray.jna.linux.GdkEventButton; import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; /** * Class for handling all system tray interactions via GTK. @@ -68,7 +69,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { @Override public void setEnabled(final MenuItem menuItem) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -94,7 +95,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { return; } - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -125,7 +126,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { void remove() { // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) if (!shuttingDown.getAndSet(true)) { - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -142,12 +143,12 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { super.remove(); // does not need to be called on the dispatch (it does that) - Gtk.shutdownGui(); + GtkEventDispatch.shutdownGui(); } } }; - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -169,10 +170,10 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { } }); - Gtk.waitForEventsToComplete(); + GtkEventDispatch.waitForEventsToComplete(); // we have to be able to set our title, otherwise the gnome-shell extension WILL NOT work - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { @@ -211,7 +212,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI { } this.tooltipText = tooltipText; - Gtk.dispatch(new Runnable() { + GtkEventDispatch.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/swingUI/_SwingTray.java b/src/dorkbox/systemTray/swingUI/_SwingTray.java index a9c4c38..b0350bf 100644 --- a/src/dorkbox/systemTray/swingUI/_SwingTray.java +++ b/src/dorkbox/systemTray/swingUI/_SwingTray.java @@ -28,7 +28,7 @@ import javax.swing.JPopupMenu; import dorkbox.systemTray.MenuItem; import dorkbox.systemTray.Tray; -import dorkbox.systemTray.jna.linux.Gtk; +import dorkbox.systemTray.jna.linux.GtkEventDispatch; import dorkbox.util.OS; import dorkbox.util.SwingUtil; @@ -191,7 +191,7 @@ class _SwingTray extends Tray implements SwingUI { if (OS.isLinux() || OS.isUnix()) { // does not need to be called on the dispatch (it does that). Startup happens in the SystemTray (in a special block), // because we MUST startup the system tray BEFORE to access GTK before we create the swing version (to get size info) - Gtk.shutdownGui(); + GtkEventDispatch.shutdownGui(); } } };