Moved Gtk Event Dispatch into it's own class.
This commit is contained in:
parent
876c5bb591
commit
9bf01aaf04
|
@ -17,20 +17,11 @@ package dorkbox.systemTray.jna.linux;
|
||||||
|
|
||||||
import static dorkbox.systemTray.SystemTray.logger;
|
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.Function;
|
||||||
import com.sun.jna.NativeLibrary;
|
import com.sun.jna.NativeLibrary;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.Entry;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.util.JavaFX;
|
|
||||||
import dorkbox.systemTray.util.Swt;
|
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.jna.JnaHelper;
|
import dorkbox.util.jna.JnaHelper;
|
||||||
|
|
||||||
|
@ -86,33 +77,10 @@ class Gtk {
|
||||||
|
|
||||||
public static final int FALSE = 0;
|
public static final int FALSE = 0;
|
||||||
public static final int TRUE = 1;
|
public static final int TRUE = 1;
|
||||||
private static final boolean alreadyRunningGTK;
|
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<Object> gtkCallbacks = new LinkedList<Object>();
|
|
||||||
|
|
||||||
public static Function gtk_status_icon_position_menu = null;
|
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<Boolean> isDispatch = new ThreadLocal<Boolean>() {
|
|
||||||
@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 MAJOR;
|
||||||
public static final int MINOR;
|
public static final int MINOR;
|
||||||
public static final int MICRO;
|
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
|
* Creates a new GtkMenu
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -19,7 +19,7 @@ import com.sun.jna.Pointer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bindings for GTK+ 3.
|
* bindings for GTK+ 3.
|
||||||
*
|
* <p>
|
||||||
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
|
@ -27,9 +27,14 @@ class Gtk3 {
|
||||||
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
|
// 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
|
// objdump -T /usr/local/lib/libgtk-3.so.0 | grep gtk
|
||||||
|
|
||||||
public static native int gtk_get_major_version();
|
public static native
|
||||||
public static native int gtk_get_minor_version();
|
int gtk_get_major_version();
|
||||||
public static native int gtk_get_micro_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.
|
* @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.
|
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
|
||||||
|
*
|
||||||
* @since 3.0
|
* @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.
|
* 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.
|
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
|
||||||
|
*
|
||||||
* @since 3.0
|
* @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.
|
* Converts the provider into a string representation in CSS format.
|
||||||
*
|
* <p>
|
||||||
* Using gtk_css_provider_load_from_data() with the return value from this function on a new provider created with
|
* 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 .
|
* gtk_css_provider_new() will basically create a duplicate of this provider .
|
||||||
*
|
*
|
||||||
* @since 3.2 (released in 2011)
|
* @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.
|
* Gets the foreground color for a given state.
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @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.
|
* Returns the state used for style matching.
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @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.
|
* Looks up and resolves a color name in the context color map.
|
||||||
*
|
*
|
||||||
* @since 3.0 (but not in the documentation...)
|
* @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 .
|
* 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...)
|
* @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(),
|
* 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().
|
* gtk_style_context_set_state(), etc. can quickly be reverted in one go through gtk_style_context_restore().
|
||||||
*
|
* <p>
|
||||||
* The matching call to gtk_style_context_restore() must be done before GTK returns to the main loop.
|
* The matching call to gtk_style_context_restore() must be done before GTK returns to the main loop.
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @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
|
* @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
|
* 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
|
* @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.
|
* 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
|
* @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.
|
* Gets the border for a given state as a GtkBorder.
|
||||||
*
|
* <p>
|
||||||
* See gtk_style_context_get_property() and GTK_STYLE_PROPERTY_BORDER_WIDTH for details.
|
* See gtk_style_context_get_property() and GTK_STYLE_PROPERTY_BORDER_WIDTH for details.
|
||||||
*
|
*
|
||||||
* @since 3.0
|
* @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,
|
* 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).
|
* but on very high density outputs this can be a higher value (often 2).
|
||||||
*
|
* <p>
|
||||||
* A higher value means that drawing is automatically scaled up to a higher resolution, so any code doing drawing will automatically
|
* 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
|
* 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.
|
* resource with higher resolution data.
|
||||||
*
|
* <p>
|
||||||
* The scale of a window may change during runtime, if this happens a configure event will be sent to the toplevel window.
|
* 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
|
* @return the scale factor
|
||||||
|
@ -150,16 +169,15 @@ class Gtk3 {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the minimum and natural size of a widget, taking into account the widget’s preference for height-for-width management.
|
* Retrieves the minimum and natural size of a widget, taking into account the widget’s preference for height-for-width management.
|
||||||
*
|
* <p>
|
||||||
* This is used to retrieve a suitable size by container widgets which do not impose any restrictions on the child placement.
|
* 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.
|
* It can be used to deduce toplevel window and menu sizes as well as child widgets in free-form containers such as GtkLayout.
|
||||||
*
|
* <p>
|
||||||
* Handle with care. Note that the natural height of a height-for-width widget will generally be a smaller size than the minimum
|
* 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.
|
* height, since the required height for the natural width is generally smaller than the required height for the minimum width.
|
||||||
*
|
* <p>
|
||||||
* Use gtk_widget_get_preferred_height_and_baseline_for_width() if you want to support baseline alignment.
|
* Use gtk_widget_get_preferred_height_and_baseline_for_width() if you want to support baseline alignment.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param widget a GtkWidget instance
|
* @param widget a GtkWidget instance
|
||||||
* @param minimum_size location for storing the minimum size, or NULL.
|
* @param minimum_size location for storing the minimum size, or NULL.
|
||||||
* @param natural_size location for storing the natural size, or NULL.
|
* @param natural_size location for storing the natural size, or NULL.
|
||||||
|
|
336
src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java
Normal file
336
src/dorkbox/systemTray/jna/linux/GtkEventDispatch.java
Normal file
|
@ -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<Object> gtkCallbacks = new LinkedList<Object>();
|
||||||
|
|
||||||
|
// 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<Boolean> isDispatch = new ThreadLocal<Boolean>() {
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -114,7 +114,7 @@ class GtkTheme {
|
||||||
int getMenuEntryImageSize() {
|
int getMenuEntryImageSize() {
|
||||||
final AtomicReference<Integer> imageHeight = new AtomicReference<Integer>();
|
final AtomicReference<Integer> imageHeight = new AtomicReference<Integer>();
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -162,7 +162,7 @@ class GtkTheme {
|
||||||
final AtomicReference<Double> screenScale = new AtomicReference<Double>();
|
final AtomicReference<Double> screenScale = new AtomicReference<Double>();
|
||||||
final AtomicInteger screenDPI = new AtomicInteger();
|
final AtomicInteger screenDPI = new AtomicInteger();
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
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
|
// try to use GTK to get the tray icon size
|
||||||
final AtomicInteger traySize = new AtomicInteger();
|
final AtomicInteger traySize = new AtomicInteger();
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
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
|
// 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.
|
// this information insanely difficult to get.
|
||||||
final AtomicReference<Color> color = new AtomicReference<Color>(null);
|
final AtomicReference<Color> color = new AtomicReference<Color>(null);
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@SuppressWarnings("UnusedAssignment")
|
@SuppressWarnings("UnusedAssignment")
|
||||||
@Override
|
@Override
|
||||||
public
|
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) {
|
if (Gtk.isLoaded && Gtk.isGtk3) {
|
||||||
final AtomicReference<String> css_ = new AtomicReference<String>(null);
|
final AtomicReference<String> css_ = new AtomicReference<String>(null);
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
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() {
|
String getThemeName() {
|
||||||
final AtomicReference<String> themeName = new AtomicReference<String>(null);
|
final AtomicReference<String> themeName = new AtomicReference<String>(null);
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.peer.EntryPeer;
|
import dorkbox.systemTray.peer.EntryPeer;
|
||||||
import dorkbox.systemTray.util.ImageResizeUtil;
|
import dorkbox.systemTray.util.ImageResizeUtil;
|
||||||
|
|
||||||
|
@ -102,7 +103,7 @@ class GtkBaseMenuItem implements EntryPeer {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import dorkbox.systemTray.MenuItem;
|
||||||
import dorkbox.systemTray.Separator;
|
import dorkbox.systemTray.Separator;
|
||||||
import dorkbox.systemTray.Status;
|
import dorkbox.systemTray.Status;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.peer.MenuPeer;
|
import dorkbox.systemTray.peer.MenuPeer;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
|
@ -196,7 +197,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
|
||||||
public
|
public
|
||||||
void add(final Menu parentMenu, final Entry entry, final int index) {
|
void add(final Menu parentMenu, final Entry entry, final int index) {
|
||||||
// must always be called on the GTK dispatch. This must be dispatchAndWait
|
// must always be called on the GTK dispatch. This must be dispatchAndWait
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
GtkEventDispatch.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -250,7 +251,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
|
||||||
// is overridden by system tray
|
// is overridden by system tray
|
||||||
setLegitImage(menuItem.getImage() != null);
|
setLegitImage(menuItem.getImage() != null);
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -279,7 +280,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
|
||||||
public
|
public
|
||||||
void setEnabled(final MenuItem menuItem) {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
// is overridden by system tray
|
// is overridden by system tray
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -318,7 +319,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
|
||||||
textWithMnemonic = menuItem.getText();
|
textWithMnemonic = menuItem.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -362,7 +363,7 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -24,6 +24,7 @@ import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.jna.linux.GCallback;
|
import dorkbox.systemTray.jna.linux.GCallback;
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.peer.MenuItemPeer;
|
import dorkbox.systemTray.peer.MenuItemPeer;
|
||||||
|
|
||||||
class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
||||||
|
@ -58,7 +59,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
||||||
final ActionListener cb = menuItemForActionCallback.getCallback();
|
final ActionListener cb = menuItemForActionCallback.getCallback();
|
||||||
if (cb != null) {
|
if (cb != null) {
|
||||||
try {
|
try {
|
||||||
Gtk.proxyClick(menuItemForActionCallback, cb);
|
GtkEventDispatch.proxyClick(menuItemForActionCallback, cb);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
SystemTray.logger.error("Error calling menu entry {} click event.", menuItemForActionCallback.getText(), 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) {
|
void setImage(final MenuItem menuItem) {
|
||||||
setLegitImage(menuItem.getImage() != null);
|
setLegitImage(menuItem.getImage() != null);
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -104,7 +105,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void setEnabled(final MenuItem menuItem) {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -141,7 +142,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
||||||
textWithMnemonic = menuItem.getText();
|
textWithMnemonic = menuItem.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -169,7 +170,7 @@ class GtkMenuItem extends GtkBaseMenuItem implements MenuItemPeer, GCallback {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.jna.linux.GCallback;
|
import dorkbox.systemTray.jna.linux.GCallback;
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.jna.linux.GtkTheme;
|
import dorkbox.systemTray.jna.linux.GtkTheme;
|
||||||
import dorkbox.systemTray.peer.CheckboxPeer;
|
import dorkbox.systemTray.peer.CheckboxPeer;
|
||||||
import dorkbox.systemTray.util.HeavyCheckMark;
|
import dorkbox.systemTray.util.HeavyCheckMark;
|
||||||
|
@ -127,7 +128,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
|
||||||
int callback(final Pointer instance, final Pointer data) {
|
int callback(final Pointer instance, final Pointer data) {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
// this will redispatch to our created callback via `setCallback`
|
// this will redispatch to our created callback via `setCallback`
|
||||||
Gtk.proxyClick(null, callback);
|
GtkEventDispatch.proxyClick(null, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Gtk.TRUE;
|
return Gtk.TRUE;
|
||||||
|
@ -148,7 +149,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void setEnabled(final Checkbox menuItem) {
|
void setEnabled(final Checkbox menuItem) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -180,7 +181,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
|
||||||
textWithMnemonic = menuItem.getText();
|
textWithMnemonic = menuItem.getText();
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -227,7 +228,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
|
||||||
if (checked != this.isChecked) {
|
if (checked != this.isChecked) {
|
||||||
this.isChecked = checked;
|
this.isChecked = checked;
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -283,7 +284,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package dorkbox.systemTray.nativeUI;
|
package dorkbox.systemTray.nativeUI;
|
||||||
|
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.peer.EntryPeer;
|
import dorkbox.systemTray.peer.EntryPeer;
|
||||||
|
|
||||||
class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
|
class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
|
||||||
|
@ -35,7 +36,7 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -46,11 +47,13 @@ class GtkMenuItemSeparator extends GtkBaseMenuItem implements EntryPeer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public
|
public
|
||||||
boolean hasImage() {
|
boolean hasImage() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public
|
public
|
||||||
void setSpacerImage(final boolean everyoneElseHasImages) {
|
void setSpacerImage(final boolean everyoneElseHasImages) {
|
||||||
// no op
|
// no op
|
||||||
|
|
|
@ -17,6 +17,7 @@ package dorkbox.systemTray.nativeUI;
|
||||||
|
|
||||||
import dorkbox.systemTray.Status;
|
import dorkbox.systemTray.Status;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.peer.StatusPeer;
|
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,
|
// 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
|
@Override
|
||||||
public
|
public
|
||||||
void setText(final Status menuItem) {
|
void setText(final Status menuItem) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -59,7 +60,7 @@ class GtkMenuItemStatus extends GtkBaseMenuItem implements StatusPeer {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ import dorkbox.systemTray.gnomeShell.Extension;
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicator;
|
import dorkbox.systemTray.jna.linux.AppIndicator;
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.systemTray.util.ImageResizeUtil;
|
import dorkbox.systemTray.util.ImageResizeUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,7 +133,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void setEnabled(final MenuItem menuItem) {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -160,7 +160,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -195,7 +195,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
|
||||||
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
||||||
appIndicator = null;
|
appIndicator = null;
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -209,12 +209,12 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
|
||||||
super.remove();
|
super.remove();
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
// 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
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -226,7 +226,7 @@ class _AppIndicatorNativeTray extends Tray implements NativeUI {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Gtk.waitForEventsToComplete();
|
GtkEventDispatch.waitForEventsToComplete();
|
||||||
|
|
||||||
bind(gtkMenu, null, systemTray);
|
bind(gtkMenu, null, systemTray);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import dorkbox.systemTray.jna.linux.GEventCallback;
|
||||||
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling all system tray interactions via GTK.
|
* Class for handling all system tray interactions via GTK.
|
||||||
|
@ -68,7 +69,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void setEnabled(final MenuItem menuItem) {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -94,7 +95,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -125,7 +126,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
|
||||||
void remove() {
|
void remove() {
|
||||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||||
if (!shuttingDown.getAndSet(true)) {
|
if (!shuttingDown.getAndSet(true)) {
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -142,12 +143,12 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
|
||||||
super.remove();
|
super.remove();
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
// 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
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
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
|
// 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
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -211,7 +212,7 @@ class _GtkStatusIconNativeTray extends Tray implements NativeUI {
|
||||||
}
|
}
|
||||||
this.tooltipText = tooltipText;
|
this.tooltipText = tooltipText;
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
GtkEventDispatch.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
|
|
@ -28,7 +28,7 @@ import javax.swing.JPopupMenu;
|
||||||
|
|
||||||
import dorkbox.systemTray.MenuItem;
|
import dorkbox.systemTray.MenuItem;
|
||||||
import dorkbox.systemTray.Tray;
|
import dorkbox.systemTray.Tray;
|
||||||
import dorkbox.systemTray.jna.linux.Gtk;
|
import dorkbox.systemTray.jna.linux.GtkEventDispatch;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.SwingUtil;
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ class _SwingTray extends Tray implements SwingUI {
|
||||||
if (OS.isLinux() || OS.isUnix()) {
|
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),
|
// 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)
|
// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue
Block a user