From 495973d3a70d08f995098b93f41d86c2532f932f Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 8 May 2016 15:51:07 +0200 Subject: [PATCH] Switched JNA mode from Proxy -> Direct-Mapping. Direct-Mapping is significantly faster than Proxy, approaching that of JNI performance. --- README.md | 6 +- src/dorkbox/systemTray/SystemTray.java | 182 ++++++----- .../systemTray/linux/AppIndicatorTray.java | 29 +- .../systemTray/linux/GtkMenuEntry.java | 46 ++- .../systemTray/linux/GtkSystemTray.java | 26 +- .../systemTray/linux/GtkTypeSystemTray.java | 70 ++--- .../systemTray/linux/jna/AppIndicator.java | 191 +++++++++--- .../linux/jna/AppIndicatorInstanceStruct.java | 36 +++ .../linux/jna/AppIndicatorQuery.java | 124 -------- .../systemTray/linux/jna/FuncCallback.java | 28 ++ .../systemTray/linux/jna/GCallback.java | 29 ++ .../systemTray/linux/jna/GEventCallback.java | 26 ++ .../systemTray/linux/jna/GObjectStruct.java | 45 +++ src/dorkbox/systemTray/linux/jna/GThread.java | 15 +- .../linux/jna/GTypeInstanceStruct.java | 43 +++ .../systemTray/linux/jna/GdkEventButton.java | 46 +++ src/dorkbox/systemTray/linux/jna/Gobject.java | 169 +--------- src/dorkbox/systemTray/linux/jna/Gtk.java | 295 ++++++++++++++---- .../systemTray/linux/jna/GtkSupport.java | 236 -------------- .../systemTray/linux/jna/JnaHelper.java | 43 +++ 20 files changed, 902 insertions(+), 783 deletions(-) create mode 100644 src/dorkbox/systemTray/linux/jna/AppIndicatorInstanceStruct.java delete mode 100644 src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java create mode 100644 src/dorkbox/systemTray/linux/jna/FuncCallback.java create mode 100644 src/dorkbox/systemTray/linux/jna/GCallback.java create mode 100644 src/dorkbox/systemTray/linux/jna/GEventCallback.java create mode 100644 src/dorkbox/systemTray/linux/jna/GObjectStruct.java create mode 100644 src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java create mode 100644 src/dorkbox/systemTray/linux/jna/GdkEventButton.java delete mode 100644 src/dorkbox/systemTray/linux/jna/GtkSupport.java create mode 100644 src/dorkbox/systemTray/linux/jna/JnaHelper.java diff --git a/README.md b/README.md index 70c4350..8b36d5a 100644 --- a/README.md +++ b/README.md @@ -62,8 +62,12 @@ SystemTray.TRAY_SIZE (type int, default value '22') SystemTray.FORCE_GTK2 (type boolean, default value 'false') - - Forces the system tray to always choose GTK2 (even when GTK3 might be available). + - Forces the system tray to always choose GTK2 (even when GTK3 might be available). + +SystemTray.FORCE_LINUX_TYPE (type int, default value '0') + - If != 0, forces the system tray in linux to be GTK (1) or AppIndicator (2). This is an advanced feature. + SystemTray.COMPATIBILITY_MODE (type boolean, default value 'false') - Forces the system to enter into JavaFX/SWT compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop. diff --git a/src/dorkbox/systemTray/SystemTray.java b/src/dorkbox/systemTray/SystemTray.java index 6b50651..821ec2a 100644 --- a/src/dorkbox/systemTray/SystemTray.java +++ b/src/dorkbox/systemTray/SystemTray.java @@ -19,7 +19,7 @@ import dorkbox.systemTray.linux.AppIndicatorTray; import dorkbox.systemTray.linux.GnomeShellExtension; import dorkbox.systemTray.linux.GtkSystemTray; import dorkbox.systemTray.linux.jna.AppIndicator; -import dorkbox.systemTray.linux.jna.GtkSupport; +import dorkbox.systemTray.linux.jna.Gtk; import dorkbox.systemTray.swing.SwingSystemTray; import dorkbox.util.OS; import dorkbox.util.Property; @@ -53,6 +53,9 @@ public abstract class SystemTray { protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class); + public static final int LINUX_GTK = 1; + public static final int LINUX_APP_INDICATOR = 2; + @Property /** How long to wait when updating menu entries before the request times-out */ public static final int TIMEOUT = 2; @@ -65,6 +68,10 @@ class SystemTray { /** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */ public static boolean FORCE_GTK2 = false; + @Property + /** If != 0, forces the system tray in linux to be GTK (1) or AppIndicator (2). This is an advanced feature. */ + public static int FORCE_LINUX_TYPE = 0; + @Property /** * Forces the system to enter into JavaFX/SWT compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop. @@ -152,87 +159,118 @@ class SystemTray { // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python. // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py - // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least - String XDG = System.getenv("XDG_CURRENT_DESKTOP"); - if ("Unity".equalsIgnoreCase(XDG)) { - try { - trayType = AppIndicatorTray.class; - } catch (Throwable e) { - if (DEBUG) { - e.printStackTrace(); - } + // load up our libraries + // NOTE: + // ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3. + // appindicator3 doesn't support menu icons via GTK2!! + if (Gtk.isGtk2 || AppIndicator.isVersion3) { + if (DEBUG) { + logger.trace("Loading libraries"); } } - else if ("XFCE".equalsIgnoreCase(XDG)) { - try { - trayType = AppIndicatorTray.class; - } catch (Throwable e) { - if (DEBUG) { - e.printStackTrace(); - } - // we can fail on AppIndicator, so this is the fallback - //noinspection EmptyCatchBlock - try { - trayType = GtkSystemTray.class; - } catch (Throwable e1) { - if (DEBUG) { - e1.printStackTrace(); - } - } - } - } - else if ("LXDE".equalsIgnoreCase(XDG)) { + if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) { try { trayType = GtkSystemTray.class; - } catch (Throwable e) { + } catch (Throwable e1) { if (DEBUG) { - e.printStackTrace(); + e1.printStackTrace(); } } } - else if ("KDE".equalsIgnoreCase(XDG)) { - isKDE = true; + else if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_APP_INDICATOR) { try { trayType = AppIndicatorTray.class; - } catch (Throwable e) { + } catch (Throwable e1) { if (DEBUG) { - e.printStackTrace(); + e1.printStackTrace(); } } } - else if ("GNOME".equalsIgnoreCase(XDG)) { - // check other DE - String GDM = System.getenv("GDMSESSION"); - if ("cinnamon".equalsIgnoreCase(GDM)) { - try { - trayType = GtkSystemTray.class; - } catch (Throwable e) { - if (DEBUG) { - e.printStackTrace(); - } - } - } - else if ("gnome-classic".equalsIgnoreCase(GDM)) { - try { - trayType = GtkSystemTray.class; - } catch (Throwable e) { - if (DEBUG) { - e.printStackTrace(); - } - } - } - else if ("gnome-fallback".equalsIgnoreCase(GDM)) { - try { - trayType = GtkSystemTray.class; - } catch (Throwable e) { - if (DEBUG) { - e.printStackTrace(); - } - } - } + // quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least + if (trayType == null) { + String XDG = System.getenv("XDG_CURRENT_DESKTOP"); + if ("Unity".equalsIgnoreCase(XDG)) { + try { + trayType = AppIndicatorTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + else if ("XFCE".equalsIgnoreCase(XDG)) { + try { + trayType = AppIndicatorTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + + // we can fail on AppIndicator, so this is the fallback + //noinspection EmptyCatchBlock + try { + trayType = GtkSystemTray.class; + } catch (Throwable e1) { + if (DEBUG) { + e1.printStackTrace(); + } + } + } + } + else if ("LXDE".equalsIgnoreCase(XDG)) { + try { + trayType = GtkSystemTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + else if ("KDE".equalsIgnoreCase(XDG)) { + isKDE = true; + try { + trayType = AppIndicatorTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + else if ("GNOME".equalsIgnoreCase(XDG)) { + // check other DE + String GDM = System.getenv("GDMSESSION"); + + if ("cinnamon".equalsIgnoreCase(GDM)) { + try { + trayType = GtkSystemTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + else if ("gnome-classic".equalsIgnoreCase(GDM)) { + try { + trayType = GtkSystemTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + else if ("gnome-fallback".equalsIgnoreCase(GDM)) { + try { + trayType = GtkSystemTray.class; + } catch (Throwable e) { + if (DEBUG) { + e.printStackTrace(); + } + } + } + } // is likely 'gnome', but it can also be unknown (or something completely different), install extension and go from there if (trayType == null) { @@ -292,8 +330,6 @@ class SystemTray { if (readLine != null && readLine.contains("indicator-app")) { // make sure we can also load the library (it might be the wrong version) try { - //noinspection unused - final AppIndicator instance = AppIndicator.INSTANCE; trayType = AppIndicatorTray.class; } catch (Throwable e) { logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK"); @@ -364,15 +400,11 @@ class SystemTray { try { ImageUtil.init(); - // the order of checking here is critical -- AppIndicator.IS_VERSION_3 initializes `appindicator` and `gtk` if (OS.isLinux() && trayType == AppIndicatorTray.class && - AppIndicator.IS_VERSION_3 && // this initializes the appindicator (since we specified that via the trayType) - GtkSupport.isGtk2) { + Gtk.isGtk2 && + AppIndicator.isVersion3) { - // NOTE: - // ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3. - // appindicator3 doesn't support menu icons via GTK2. AT THIS POINT, we DO NOT have GTK3 try { trayType = GtkSystemTray.class; logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " + @@ -382,8 +414,8 @@ class SystemTray { e.printStackTrace(); } logger.error("AppIndicator3 detected with GTK2 and unable to fallback to using GTK2 system tray type." + - "AppIndicator3 requires GTK3 to be fully functional, and while this will work -- the menu icons WILL " + - "NOT be visible." + + "AppIndicator3 requires GTK3 to be fully functional, and while this will work -- " + + "the menu icons WILL NOT be visible." + " Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'"); } } diff --git a/src/dorkbox/systemTray/linux/AppIndicatorTray.java b/src/dorkbox/systemTray/linux/AppIndicatorTray.java index e5bed2b..f4c4792 100644 --- a/src/dorkbox/systemTray/linux/AppIndicatorTray.java +++ b/src/dorkbox/systemTray/linux/AppIndicatorTray.java @@ -16,8 +16,11 @@ package dorkbox.systemTray.linux; import com.sun.jna.Pointer; +import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.linux.jna.AppIndicator; -import dorkbox.systemTray.linux.jna.GtkSupport; +import dorkbox.systemTray.linux.jna.AppIndicatorInstanceStruct; +import dorkbox.systemTray.linux.jna.Gobject; +import dorkbox.systemTray.linux.jna.Gtk; import java.util.concurrent.atomic.AtomicBoolean; @@ -31,9 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean; */ public class AppIndicatorTray extends GtkTypeSystemTray { - private static final AppIndicator appindicator = AppIndicator.INSTANCE; - - private AppIndicator.AppIndicatorInstanceStruct appIndicator; + private AppIndicatorInstanceStruct appIndicator; private boolean isActive = false; // This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...) @@ -41,14 +42,18 @@ class AppIndicatorTray extends GtkTypeSystemTray { public AppIndicatorTray() { - GtkSupport.startGui(); + if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) { + // if we force GTK type system tray, don't attempt to load AppIndicator libs + throw new IllegalArgumentException("Unable to start AppIndicator if 'SystemTray.FORCE_LINUX_TYPE' is set to GTK"); + } + + Gtk.startGui(); dispatch(new Runnable() { @Override public void run() { - appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "", - AppIndicator.CATEGORY_APPLICATION_STATUS); + appIndicator = AppIndicator.app_indicator_new(System.nanoTime() + "DBST", "", AppIndicator.CATEGORY_APPLICATION_STATUS); } }); } @@ -62,9 +67,9 @@ class AppIndicatorTray extends GtkTypeSystemTray { public void run() { // STATUS_PASSIVE hides the indicator - appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); + AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE); Pointer p = appIndicator.getPointer(); - gobject.g_object_unref(p); + Gobject.g_object_unref(p); appIndicator = null; } @@ -81,12 +86,12 @@ class AppIndicatorTray extends GtkTypeSystemTray { @Override public void run() { - appindicator.app_indicator_set_icon(appIndicator, iconPath); + AppIndicator.app_indicator_set_icon(appIndicator, iconPath); if (!isActive) { isActive = true; - appindicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); + AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE); } } }); @@ -98,6 +103,6 @@ class AppIndicatorTray extends GtkTypeSystemTray { protected void onMenuAdded(final Pointer menu) { // see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247 - appindicator.app_indicator_set_menu(appIndicator, menu); + AppIndicator.app_indicator_set_menu(appIndicator, menu); } } diff --git a/src/dorkbox/systemTray/linux/GtkMenuEntry.java b/src/dorkbox/systemTray/linux/GtkMenuEntry.java index 318f870..082e8a5 100644 --- a/src/dorkbox/systemTray/linux/GtkMenuEntry.java +++ b/src/dorkbox/systemTray/linux/GtkMenuEntry.java @@ -20,10 +20,9 @@ import com.sun.jna.Pointer; import dorkbox.systemTray.ImageUtil; import dorkbox.systemTray.MenuEntry; import dorkbox.systemTray.SystemTrayMenuAction; +import dorkbox.systemTray.linux.jna.GCallback; import dorkbox.systemTray.linux.jna.Gobject; -import dorkbox.systemTray.linux.jna.Gobject.GCallback; import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.linux.jna.GtkSupport; import java.io.InputStream; import java.net.URL; @@ -33,9 +32,6 @@ class GtkMenuEntry implements MenuEntry, GCallback { private static final AtomicInteger ID_COUNTER = new AtomicInteger(); private final int id = ID_COUNTER.getAndIncrement(); - private static final Gtk gtk = Gtk.INSTANCE; - private static final Gobject gobject = Gobject.INSTANCE; - final Pointer menuItem; final GtkTypeSystemTray parent; @@ -56,20 +52,20 @@ class GtkMenuEntry implements MenuEntry, GCallback { this.text = label; this.callback = callback; - menuItem = gtk.gtk_image_menu_item_new_with_label(label); + menuItem = Gtk.gtk_image_menu_item_new_with_label(label); if (imagePath != null && !imagePath.isEmpty()) { // NOTE: XFCE uses appindicator3, which DOES NOT support images in the menu. This change was reverted. // see: https://ask.fedoraproject.org/en/question/23116/how-to-fix-missing-icons-in-program-menus-and-context-menus/ // see: https://git.gnome.org/browse/gtk+/commit/?id=627a03683f5f41efbfc86cc0f10e1b7c11e9bb25 - image = gtk.gtk_image_new_from_file(imagePath); + image = Gtk.gtk_image_new_from_file(imagePath); - gtk.gtk_image_menu_item_set_image(menuItem, image); + Gtk.gtk_image_menu_item_set_image(menuItem, image); // must always re-set always-show after setting the image - gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); + Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); } - nativeLong = gobject.g_signal_connect_object(menuItem, "activate", this, null, 0); + nativeLong = Gobject.g_signal_connect_object(menuItem, "activate", this, null, 0); } @@ -98,41 +94,41 @@ class GtkMenuEntry implements MenuEntry, GCallback { @Override public void setText(final String newText) { - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { text = newText; - gtk.gtk_menu_item_set_label(menuItem, newText); + Gtk.gtk_menu_item_set_label(menuItem, newText); - gtk.gtk_widget_show_all(menuItem); + Gtk.gtk_widget_show_all(menuItem); } }); } private void setImage_(final String imagePath) { - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { if (image != null) { - gtk.gtk_widget_destroy(image); + Gtk.gtk_widget_destroy(image); image = null; } - gtk.gtk_widget_show_all(menuItem); + Gtk.gtk_widget_show_all(menuItem); if (imagePath != null && !imagePath.isEmpty()) { - image = gtk.gtk_image_new_from_file(imagePath); - gtk.gtk_image_menu_item_set_image(menuItem, image); - gobject.g_object_ref_sink(image); + image = Gtk.gtk_image_new_from_file(imagePath); + Gtk.gtk_image_menu_item_set_image(menuItem, image); + Gobject.g_object_ref_sink(image); // must always re-set always-show after setting the image - gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); + Gtk.gtk_image_menu_item_set_always_show_image(menuItem, Gtk.TRUE); } - gtk.gtk_widget_show_all(menuItem); + Gtk.gtk_widget_show_all(menuItem); } }); } @@ -193,7 +189,7 @@ class GtkMenuEntry implements MenuEntry, GCallback { */ public void remove() { - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -208,13 +204,13 @@ class GtkMenuEntry implements MenuEntry, GCallback { void removePrivate() { callback = null; - gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem); + Gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem); if (image != null) { - gtk.gtk_widget_destroy(image); + Gtk.gtk_widget_destroy(image); } - gtk.gtk_widget_destroy(menuItem); + Gtk.gtk_widget_destroy(menuItem); } @Override diff --git a/src/dorkbox/systemTray/linux/GtkSystemTray.java b/src/dorkbox/systemTray/linux/GtkSystemTray.java index 00d39db..c8cad3e 100644 --- a/src/dorkbox/systemTray/linux/GtkSystemTray.java +++ b/src/dorkbox/systemTray/linux/GtkSystemTray.java @@ -17,9 +17,10 @@ package dorkbox.systemTray.linux; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; +import dorkbox.systemTray.linux.jna.GEventCallback; +import dorkbox.systemTray.linux.jna.GdkEventButton; import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.linux.jna.GtkSupport; import java.util.ArrayList; import java.util.List; @@ -45,29 +46,28 @@ class GtkSystemTray extends GtkTypeSystemTray { public GtkSystemTray() { super(); - GtkSupport.startGui(); + Gtk.startGui(); dispatch(new Runnable() { @Override public void run() { - final Pointer trayIcon_ = gtk.gtk_status_icon_new(); - gtk.gtk_status_icon_set_title(trayIcon_, GnomeShellExtension.UID); - gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray"); + final Pointer trayIcon_ = Gtk.gtk_status_icon_new(); + Gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray"); trayIcon = trayIcon_; - final Gobject.GEventCallback gtkCallback = new Gobject.GEventCallback() { + final GEventCallback gtkCallback = new GEventCallback() { @Override public - void callback(Pointer notUsed, final Gtk.GdkEventButton event) { + void callback(Pointer notUsed, final GdkEventButton event) { // BUTTON_PRESS only (any mouse click) if (event.type == 4) { - gtk.gtk_menu_popup(getMenu(), null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); + Gtk.gtk_menu_popup(getMenu(), null, null, Gtk.gtk_status_icon_position_menu, trayIcon, 0, event.time); } } }; - final NativeLong button_press_event = gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, null, 0); + final NativeLong button_press_event = Gobject.g_signal_connect_object(trayIcon, "button_press_event", gtkCallback, null, 0); // have to do this to prevent GC on these objects gtkCallbacks.add(gtkCallback); @@ -86,8 +86,8 @@ class GtkSystemTray extends GtkTypeSystemTray { public void run() { // this hides the indicator - gtk.gtk_status_icon_set_visible(trayIcon, false); - gobject.g_object_unref(trayIcon); + Gtk.gtk_status_icon_set_visible(trayIcon, false); + Gobject.g_object_unref(trayIcon); // mark for GC trayIcon = null; @@ -106,11 +106,11 @@ class GtkSystemTray extends GtkTypeSystemTray { @Override public void run() { - gtk.gtk_status_icon_set_from_file(trayIcon, iconPath); + Gtk.gtk_status_icon_set_from_file(trayIcon, iconPath); if (!isActive) { isActive = true; - gtk.gtk_status_icon_set_visible(trayIcon, true); + Gtk.gtk_status_icon_set_visible(trayIcon, true); } } }); diff --git a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java b/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java index d1883da..c73eaa6 100644 --- a/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java +++ b/src/dorkbox/systemTray/linux/GtkTypeSystemTray.java @@ -22,7 +22,6 @@ import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTrayMenuAction; import dorkbox.systemTray.linux.jna.Gobject; import dorkbox.systemTray.linux.jna.Gtk; -import dorkbox.systemTray.linux.jna.GtkSupport; import java.io.InputStream; import java.net.URL; @@ -33,9 +32,6 @@ import java.net.URL; */ public abstract class GtkTypeSystemTray extends SystemTray { - protected static final Gobject gobject = Gobject.INSTANCE; - protected static final Gtk gtk = Gtk.INSTANCE; - private volatile Pointer menu; private volatile Pointer connectionStatusItem; @@ -44,19 +40,19 @@ class GtkTypeSystemTray extends SystemTray { @Override protected void dispatch(final Runnable runnable) { - GtkSupport.dispatch(runnable); + Gtk.dispatch(runnable); } @Override public void shutdown() { - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { obliterateMenu(); - GtkSupport.shutdownGui(); + Gtk.shutdownGui(); } }); } @@ -72,7 +68,7 @@ class GtkTypeSystemTray extends SystemTray { void setStatus(final String statusText) { this.statusText = statusText; - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { @@ -81,16 +77,16 @@ class GtkTypeSystemTray extends SystemTray { if (connectionStatusItem == null && statusText != null && !statusText.isEmpty()) { deleteMenu(); - connectionStatusItem = gtk.gtk_menu_item_new_with_label(""); + connectionStatusItem = Gtk.gtk_menu_item_new_with_label(""); // evil hacks abound... - Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); - gtk.gtk_label_set_use_markup(label, Gtk.TRUE); - Pointer markup = gobject.g_markup_printf_escaped("%s", statusText); - gtk.gtk_label_set_markup(label, markup); - gobject.g_free(markup); + Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem); + Gtk.gtk_label_set_use_markup(label, Gtk.TRUE); + Pointer markup = Gobject.g_markup_printf_escaped("%s", statusText); + Gtk.gtk_label_set_markup(label, markup); + Gobject.g_free(markup); - gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE); + Gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE); createMenu(); } @@ -98,10 +94,10 @@ class GtkTypeSystemTray extends SystemTray { if (statusText == null || statusText.isEmpty()) { // this means the status text already exists, and we are removing it - gtk.gtk_container_remove(menu, connectionStatusItem); + Gtk.gtk_container_remove(menu, connectionStatusItem); connectionStatusItem = null; // because we manually delete it - gtk.gtk_widget_show_all(menu); + Gtk.gtk_widget_show_all(menu); deleteMenu(); createMenu(); @@ -113,13 +109,13 @@ class GtkTypeSystemTray extends SystemTray { // libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText); // evil hacks abound... - Pointer label = gtk.gtk_bin_get_child(connectionStatusItem); - gtk.gtk_label_set_use_markup(label, Gtk.TRUE); - Pointer markup = gobject.g_markup_printf_escaped("%s", statusText); - gtk.gtk_label_set_markup(label, markup); - gobject.g_free(markup); + Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem); + Gtk.gtk_label_set_use_markup(label, Gtk.TRUE); + Pointer markup = Gobject.g_markup_printf_escaped("%s", statusText); + Gtk.gtk_label_set_markup(label, markup); + Gobject.g_free(markup); - gtk.gtk_widget_show_all(menu); + Gtk.gtk_widget_show_all(menu); } } } @@ -135,8 +131,8 @@ class GtkTypeSystemTray extends SystemTray { if (menu != null) { // have to remove status from menu (but not destroy the object) if (connectionStatusItem != null) { - gobject.g_object_force_floating(connectionStatusItem); - gtk.gtk_container_remove(menu, connectionStatusItem); + Gobject.g_object_force_floating(connectionStatusItem); + Gtk.gtk_container_remove(menu, connectionStatusItem); } // have to remove all other menu entries @@ -144,16 +140,16 @@ class GtkTypeSystemTray extends SystemTray { for (int i = 0; i < menuEntries.size(); i++) { GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); - gobject.g_object_force_floating(menuEntry__.menuItem); - gtk.gtk_container_remove(menu, menuEntry__.menuItem); + Gobject.g_object_force_floating(menuEntry__.menuItem); + Gtk.gtk_container_remove(menu, menuEntry__.menuItem); } - gtk.gtk_widget_destroy(menu); + Gtk.gtk_widget_destroy(menu); } } // makes a new one - menu = gtk.gtk_menu_new(); + menu = Gtk.gtk_menu_new(); } // some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator. @@ -161,8 +157,8 @@ class GtkTypeSystemTray extends SystemTray { void createMenu() { // now add status if (connectionStatusItem != null) { - gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); - gobject.g_object_ref_sink(connectionStatusItem); + Gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem); + Gobject.g_object_ref_sink(connectionStatusItem); } // now add back other menu entries @@ -171,12 +167,12 @@ class GtkTypeSystemTray extends SystemTray { GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i); // will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu' - gtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem); - gobject.g_object_ref_sink(menuEntry__.menuItem); + Gtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem); + Gobject.g_object_ref_sink(menuEntry__.menuItem); } onMenuAdded(menu); - gtk.gtk_widget_show_all(menu); + Gtk.gtk_widget_show_all(menu); } } @@ -188,7 +184,7 @@ class GtkTypeSystemTray extends SystemTray { if (menu != null) { // have to remove status from menu if (connectionStatusItem != null) { - gtk.gtk_widget_destroy(connectionStatusItem); + Gtk.gtk_widget_destroy(connectionStatusItem); connectionStatusItem = null; } @@ -201,7 +197,7 @@ class GtkTypeSystemTray extends SystemTray { } menuEntries.clear(); - gtk.gtk_widget_destroy(menu); + Gtk.gtk_widget_destroy(menu); } } } @@ -226,7 +222,7 @@ class GtkTypeSystemTray extends SystemTray { throw new NullPointerException("Menu text cannot be null"); } - GtkSupport.dispatch(new Runnable() { + Gtk.dispatch(new Runnable() { @Override public void run() { diff --git a/src/dorkbox/systemTray/linux/jna/AppIndicator.java b/src/dorkbox/systemTray/linux/jna/AppIndicator.java index 9106945..13ad8ce 100644 --- a/src/dorkbox/systemTray/linux/jna/AppIndicator.java +++ b/src/dorkbox/systemTray/linux/jna/AppIndicator.java @@ -15,55 +15,176 @@ */ package dorkbox.systemTray.linux.jna; -import com.sun.jna.Library; +import com.sun.jna.NativeLibrary; import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import dorkbox.util.Keep; +import dorkbox.systemTray.SystemTray; -import java.util.Arrays; -import java.util.List; - -/* bindings for libappindicator */ +/** + * bindings for libappindicator + * + * Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md + */ +@SuppressWarnings("Duplicates") public -interface AppIndicator extends Library { - // effing retarded. There are DIFFERENT versions, of which they all share the same basic compatibility (of the methods that - // we use), however -- we cannot just LOAD via the 'base-name', we actually have to try each one. There are bash commands that - // will tell us the linked library name, however - I'd rather not run bash commands to determine this. - // This is so hacky it makes me sick. - AppIndicator INSTANCE = AppIndicatorQuery.get(); +class AppIndicator { + public static boolean isVersion3 = false; - /** Necessary to provide warnings, because libappindicator3 won't properly work with GTK2 */ - boolean IS_VERSION_3 = AppIndicatorQuery.isVersion3; + private static boolean isLoaded = false; - int CATEGORY_APPLICATION_STATUS = 0; - int CATEGORY_COMMUNICATIONS = 1; - int CATEGORY_SYSTEM_SERVICES = 2; - int CATEGORY_HARDWARE = 3; - int CATEGORY_OTHER = 4; + /** + * Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that + * standard library naming convention or features/API set is. We just try until we find one that work, and are able to map the + * symbols we need. There are bash commands that will tell us the linked library name, however - I'd rather not run bash commands + * to determine this. + * + * This is so hacky it makes me sick. + */ + static { + // objdump -T /usr/lib/x86_64-linux-gnu/libappindicator.so.1 | grep foo + // objdump -T /usr/lib/x86_64-linux-gnu/libappindicator3.so.1 | grep foo - int STATUS_PASSIVE = 0; - int STATUS_ACTIVE = 1; - int STATUS_ATTENTION = 2; + // NOTE: + // ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3. + // appindicator3 doesn't support menu icons via GTK2!! + + if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) { + // if we force GTK type system tray, don't attempt to load AppIndicator libs + isLoaded = true; + } + + if (!isLoaded && (SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE)) { + // if specified, try loading appindicator1 first, maybe it's there? + try { + final NativeLibrary library = JnaHelper.register("appindicator1", AppIndicator.class); + if (library != null) { + isLoaded = true; + } + } catch (Throwable ignored) { + } + } + + String nameToCheck1; + String nameToCheck2; + if (Gtk.isGtk2) { + nameToCheck1 = "appindicator"; + } + else { + nameToCheck1 = "appindicator3"; + } + + // start with base version using whatever the OS specifies as the proper symbolic link + if (!isLoaded) { + try { + final NativeLibrary library = JnaHelper.register(nameToCheck1, AppIndicator.class); + String s = library.getName(); + if (s.contains("appindicator3")) { + isVersion3 = true; + } + + isLoaded = true; + } catch (Throwable ignored) { + } + } + + // whoops. Symbolic links are bugged out. Look manually for it... + // Super hacky way to do this. + if (!isLoaded) { + if (Gtk.isGtk2) { + // have to check gtk2 first + for (int i = 0; i <= 10; i++) { + if (!isLoaded) { + try { + final NativeLibrary library = JnaHelper.register("appindicator" + i, AppIndicator.class); + + String s = library.getName(); + // version 3 WILL NOT work with icons in the menu. This allows us to show a warning (in the System tray initialization) + if (i == 3 || s.contains("appindicator3")) { + isVersion3 = true; + } + + isLoaded = true; + break; + } catch (Throwable ignored) { + } + } + } + + } else { + // have to check gtk3 first (maybe it's there?) + for (int i = 10; i >= 0; i--) { + if (!isLoaded) { + try { + final NativeLibrary library = JnaHelper.register("appindicator" + i, AppIndicator.class); + + String s = library.getName(); + // version 3 WILL NOT work with icons in the menu. This allows us to show a warning (in the System tray initialization) + if (i == 3 || s.contains("appindicator3")) { + isVersion3 = true; + } + + isLoaded = true; + break; + } catch (Throwable ignored) { + } + } + } + } - @Keep - class AppIndicatorInstanceStruct extends Structure { - public Gobject.GObjectStruct parent; - public Pointer priv; + } - @Override - protected - List getFieldOrder() { - return Arrays.asList("parent", "priv"); + // If we are GTK2, change the order we check and load libraries + + if (Gtk.isGtk2) { + nameToCheck1 = "appindicator-gtk"; + nameToCheck2 = "appindicator-gtk3"; + } + else { + nameToCheck1 = "appindicator-gtk3"; + nameToCheck2 = "appindicator-gtk"; + } + + // another type. who knows... + if (!isLoaded) { + try { + JnaHelper.register(nameToCheck1, AppIndicator.class); + isLoaded = true; + } catch (Throwable ignored) { + } + } + + // this is HORRID. such a PITA + if (!isLoaded) { + try { + JnaHelper.register(nameToCheck2, AppIndicator.class); + isLoaded = true; + } catch (Throwable ignored) { + } + } + + if (!isLoaded) { + throw new RuntimeException("We apologize for this, but we are unable to determine which the appIndicator library is in use, if " + + "or even if it is in use... Please create an issue for this and include your OS type and configuration."); } } // Note: AppIndicators DO NOT support tooltips, as per mark shuttleworth. Rather stupid IMHO. // See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12 - AppIndicatorInstanceStruct app_indicator_new(String id, String icon_name, int category); + public static final int CATEGORY_APPLICATION_STATUS = 0; + public static final int CATEGORY_COMMUNICATIONS = 1; + public static final int CATEGORY_SYSTEM_SERVICES = 2; + public static final int CATEGORY_HARDWARE = 3; + public static final int CATEGORY_OTHER = 4; - void app_indicator_set_status(AppIndicatorInstanceStruct self, int status); - void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu); - void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name); + public static final int STATUS_PASSIVE = 0; + public static final int STATUS_ACTIVE = 1; + public static final int STATUS_ATTENTION = 2; + + + public static native AppIndicatorInstanceStruct app_indicator_new(String id, String icon_name, int category); + + public static native void app_indicator_set_status(AppIndicatorInstanceStruct self, int status); + public static native void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu); + public static native void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name); } diff --git a/src/dorkbox/systemTray/linux/jna/AppIndicatorInstanceStruct.java b/src/dorkbox/systemTray/linux/jna/AppIndicatorInstanceStruct.java new file mode 100644 index 0000000..6ac662b --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/AppIndicatorInstanceStruct.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import dorkbox.util.Keep; + +import java.util.Arrays; +import java.util.List; + +@Keep +public +class AppIndicatorInstanceStruct extends Structure { + public GObjectStruct parent; + public Pointer priv; + + @Override + protected + List getFieldOrder() { + return Arrays.asList("parent", "priv"); + } +} diff --git a/src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java b/src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java deleted file mode 100644 index 7052c50..0000000 --- a/src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package dorkbox.systemTray.linux.jna; - -import com.sun.jna.Native; -import dorkbox.systemTray.SystemTray; - -/** - * Helper for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that standard - * library naming convention or features set is. We just try until we find one that work, and are able to map the symbols we need. - */ -class AppIndicatorQuery { - - /** - * must call get() before accessing this! Only "AppIndicator" interface should access this! - */ - static volatile boolean isVersion3 = false; - - /** - * Is AppIndicator loaded yet? - */ - static volatile boolean isLoaded = false; - - - public static - AppIndicator get() { - // objdump -T /usr/lib/x86_64-linux-gnu/libappindicator.so.1 | grep foo - // objdump -T /usr/lib/x86_64-linux-gnu/libappindicator3.so.1 | grep foo - - Object library; - - // NOTE: GtkSupport uses this info to figure out WHAT VERSION OF GTK to use: appindiactor1 -> GTk2, appindicator3 -> GTK3. - - if (SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE) { - // try loading appindicator1 first, maybe it's there? - - try { - library = Native.loadLibrary("appindicator1", AppIndicator.class); - if (library != null) { - return (AppIndicator) library; - } - } catch (Throwable ignored) { - } - } - - // start with base version - try { - library = Native.loadLibrary("appindicator", AppIndicator.class); - if (library != null) { - String s = library.toString(); - if (s.indexOf("appindicator3") > 0) { - isVersion3 = true; - } - - isLoaded = true; - return (AppIndicator) library; - } - } catch (Throwable ignored) { - } - - // whoops. Symbolic links are bugged out. Look manually for it... - - try { - library = Native.loadLibrary("appindicator1", AppIndicator.class); - if (library != null) { - return (AppIndicator) library; - } - } catch (Throwable ignored) { - } - - // now check all others. super hacky way to do this. - for (int i = 10; i >= 0; i--) { - try { - library = Native.loadLibrary("appindicator" + i, AppIndicator.class); - } catch (Throwable ignored) { - library = null; - } - - if (library != null) { - String s = library.toString(); - // version 3 WILL NOT work with icons in the menu. This allows us to show a warning (in the System tray initialization) - if (i == 3 || s.indexOf("appindicator3") > 0) { - isVersion3 = true; - } - return (AppIndicator) library; - } - } - - // another type. who knows... - try { - library = Native.loadLibrary("appindicator-gtk", AppIndicator.class); - if (library != null) { - return (AppIndicator) library; - } - } catch (Throwable ignored) { - } - - // this is HORRID. such a PITA - try { - library = Native.loadLibrary("appindicator-gtk3", AppIndicator.class); - if (library != null) { - return (AppIndicator) library; - } - } catch (Throwable ignored) { - } - - throw new RuntimeException("We apologize for this, but we are unable to determine which the appIndicator library is in use, if " + - "or even if it is in use... Please create an issue for this and include your OS type and configuration."); - } -} diff --git a/src/dorkbox/systemTray/linux/jna/FuncCallback.java b/src/dorkbox/systemTray/linux/jna/FuncCallback.java new file mode 100644 index 0000000..7c79012 --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/FuncCallback.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Callback; +import com.sun.jna.Pointer; +import dorkbox.util.Keep; + +@Keep +interface FuncCallback extends Callback { + /** + * @return Gtk.FALSE if it will be automatically removed from the stack once it's handled + */ + int callback(Pointer data); +} diff --git a/src/dorkbox/systemTray/linux/jna/GCallback.java b/src/dorkbox/systemTray/linux/jna/GCallback.java new file mode 100644 index 0000000..a4d2df6 --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/GCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Callback; +import com.sun.jna.Pointer; +import dorkbox.util.Keep; + +@Keep +public +interface GCallback extends Callback { + /** + * @return Gtk.TRUE if we handled this event + */ + int callback(Pointer instance, Pointer data); +} diff --git a/src/dorkbox/systemTray/linux/jna/GEventCallback.java b/src/dorkbox/systemTray/linux/jna/GEventCallback.java new file mode 100644 index 0000000..ceb955a --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/GEventCallback.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Callback; +import com.sun.jna.Pointer; +import dorkbox.util.Keep; + +@Keep +public +interface GEventCallback extends Callback { + void callback(Pointer instance, GdkEventButton event); +} diff --git a/src/dorkbox/systemTray/linux/jna/GObjectStruct.java b/src/dorkbox/systemTray/linux/jna/GObjectStruct.java new file mode 100644 index 0000000..21aa9d3 --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/GObjectStruct.java @@ -0,0 +1,45 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import dorkbox.util.Keep; + +import java.util.Arrays; +import java.util.List; + +@Keep +public +class GObjectStruct extends Structure { + public + class ByValue extends GObjectStruct implements Structure.ByValue {} + + + public + class ByReference extends GObjectStruct implements Structure.ByReference {} + + + public GTypeInstanceStruct g_type_instance; + public int ref_count; + public Pointer qdata; + + @Override + protected + List getFieldOrder() { + return Arrays.asList("g_type_instance", "ref_count", "qdata"); + } +} diff --git a/src/dorkbox/systemTray/linux/jna/GThread.java b/src/dorkbox/systemTray/linux/jna/GThread.java index 3ce9e09..a78a407 100644 --- a/src/dorkbox/systemTray/linux/jna/GThread.java +++ b/src/dorkbox/systemTray/linux/jna/GThread.java @@ -15,13 +15,18 @@ */ package dorkbox.systemTray.linux.jna; -import com.sun.jna.Library; -import com.sun.jna.Native; import com.sun.jna.Pointer; +/** + * bindings for libgthread + * + * Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md + */ public -interface GThread extends Library { - GThread INSTANCE = (GThread) Native.loadLibrary("gthread-2.0", GThread.class); +class GThread { + static { + JnaHelper.register("gthread-2.0", GThread.class); + } - void g_thread_init(Pointer GThreadFunctions); + public static native void g_thread_init(Pointer GThreadFunctions); } diff --git a/src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java b/src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java new file mode 100644 index 0000000..ee9d6c3 --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import dorkbox.util.Keep; + +import java.util.Arrays; +import java.util.List; + +@Keep +public +class GTypeInstanceStruct extends Structure { + public + class ByValue extends GTypeInstanceStruct implements Structure.ByValue {} + + + public + class ByReference extends GTypeInstanceStruct implements Structure.ByReference {} + + + public Pointer g_class; + + @Override + protected + List getFieldOrder() { + return Arrays.asList("g_class"); + } +} diff --git a/src/dorkbox/systemTray/linux/jna/GdkEventButton.java b/src/dorkbox/systemTray/linux/jna/GdkEventButton.java new file mode 100644 index 0000000..332afaf --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/GdkEventButton.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Pointer; +import com.sun.jna.Structure; +import dorkbox.util.Keep; + +import java.util.Arrays; +import java.util.List; + +@Keep +public +class GdkEventButton extends Structure { + public int type; + public Pointer window; + public int send_event; + public int time; + public double x; + public double y; + public Pointer axes; + public int state; + public int button; + public Pointer device; + public double x_root; + public double y_root; + + @Override + protected + List getFieldOrder() { + return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root"); + } +} diff --git a/src/dorkbox/systemTray/linux/jna/Gobject.java b/src/dorkbox/systemTray/linux/jna/Gobject.java index 25671f1..b5f95ae 100644 --- a/src/dorkbox/systemTray/linux/jna/Gobject.java +++ b/src/dorkbox/systemTray/linux/jna/Gobject.java @@ -16,169 +16,28 @@ package dorkbox.systemTray.linux.jna; import com.sun.jna.Callback; -import com.sun.jna.Library; -import com.sun.jna.Native; import com.sun.jna.NativeLong; import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import dorkbox.util.Keep; - -import java.util.Arrays; -import java.util.List; +/** + * bindings for libgobject-2.0 + * + * Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md + */ public -interface Gobject extends Library { - Gobject INSTANCE = (Gobject) Native.loadLibrary("gobject-2.0", Gobject.class); +class Gobject { - @Keep - class GTypeClassStruct extends Structure { - public - class ByValue extends GTypeClassStruct implements Structure.ByValue {} - - - public - class ByReference extends GTypeClassStruct implements Structure.ByReference {} - - - public NativeLong g_type; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("g_type"); - } + static { + JnaHelper.register("gobject-2.0", Gobject.class); } + public static native void g_free(Pointer object); + public static native void g_object_unref(Pointer object); - @Keep - class GTypeInstanceStruct extends Structure { - public - class ByValue extends GTypeInstanceStruct implements Structure.ByValue {} + public static native void g_object_force_floating(Pointer object); + public static native void g_object_ref_sink(Pointer object); + public static native NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags); - public - class ByReference extends GTypeInstanceStruct implements Structure.ByReference {} - - - public Pointer g_class; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("g_class"); - } - } - - - @Keep - class GObjectStruct extends Structure { - public - class ByValue extends GObjectStruct implements Structure.ByValue {} - - - public - class ByReference extends GObjectStruct implements Structure.ByReference {} - - - public GTypeInstanceStruct g_type_instance; - public int ref_count; - public Pointer qdata; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("g_type_instance", "ref_count", "qdata"); - } - } - - - @Keep - class GObjectClassStruct extends Structure { - public - class ByValue extends GObjectClassStruct implements Structure.ByValue {} - - - public - class ByReference extends GObjectClassStruct implements Structure.ByReference {} - - - public GTypeClassStruct g_type_class; - public Pointer construct_properties; - public Pointer constructor; - public Pointer set_property; - public Pointer get_property; - public Pointer dispose; - public Pointer finalize; - public Pointer dispatch_properties_changed; - public Pointer notify; - public Pointer constructed; - public NativeLong flags; - public Pointer dummy1; - public Pointer dummy2; - public Pointer dummy3; - public Pointer dummy4; - public Pointer dummy5; - public Pointer dummy6; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("g_type_class", "construct_properties", "constructor", "set_property", "get_property", "dispose", - "finalize", "dispatch_properties_changed", "notify", "constructed", "flags", "dummy1", "dummy2", "dummy3", - "dummy4", "dummy5", "dummy6"); - } - } - - - @Keep - interface FuncCallback extends Callback { - /** - * @return Gtk.FALSE if it will be automatically removed from the stack once it's handled - */ - int callback(Pointer data); - } - - @Keep - interface GCallback extends Callback { - /** - * @return Gtk.TRUE if we handled this event - */ - int callback(Pointer instance, Pointer data); - } - - - @Keep - interface GEventCallback extends Callback { - void callback(Pointer instance, Gtk.GdkEventButton event); - } - - - @Keep - class xyPointer extends Structure { - public int value; - - @Override - protected - List getFieldOrder() { - return Arrays.asList("value"); - } - } - - - @Keep - interface GPositionCallback extends Callback { - void callback(Pointer menu, xyPointer x, xyPointer y, Pointer push_in_bool, Pointer user_data); - } - - - - void g_free(Pointer object); - void g_object_unref(Pointer object); - - void g_object_force_floating(Pointer object); - void g_object_ref_sink(Pointer object); - - NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags); - - Pointer g_markup_printf_escaped(String pattern, String inputString); + public static native Pointer g_markup_printf_escaped(String pattern, String inputString); } diff --git a/src/dorkbox/systemTray/linux/jna/Gtk.java b/src/dorkbox/systemTray/linux/jna/Gtk.java index 57a03d5..3fea125 100644 --- a/src/dorkbox/systemTray/linux/jna/Gtk.java +++ b/src/dorkbox/systemTray/linux/jna/Gtk.java @@ -16,132 +16,297 @@ package dorkbox.systemTray.linux.jna; import com.sun.jna.Function; -import com.sun.jna.Library; import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import dorkbox.util.Keep; +import dorkbox.systemTray.SystemTray; -import java.util.Arrays; -import java.util.List; +import java.util.LinkedList; +import java.util.concurrent.CountDownLatch; +/** + * bindings for gtk 2 or 3 + * + * note: gtk2/3 loading is SENSITIVE, and which AppIndicator symbols are loaded depends on this being loaded first + * + * Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md + */ +@SuppressWarnings("Duplicates") public -interface Gtk extends Library { +class Gtk { + // For funsies to look at, SyncThing did a LOT of work on compatibility in python (unfortunate for us, but interesting). + // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py + + // NOTE: AppIndicator uses this info to figure out WHAT VERSION OF appindicator to use: GTK2 -> appindiactor1, GTK3 -> appindicator3 + public static volatile boolean isGtk2 = false; + + + public static Function gtk_status_icon_position_menu = null; + + private static boolean alreadyRunningGTK = false; + private static boolean isLoaded = false; + + + /** + * We can have GTK v3 or v2. + * + * Observations: + * JavaFX uses GTK2, and we can't load GTK3 if GTK2 symbols are loaded + * SWT uses GTK2 or GTK3. We do not work with the GTK3 version of SWT. + */ // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk // objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk - Gtk INSTANCE = GtkSupport.get(); - Function gtk_status_icon_position_menu = GtkSupport.gtk_status_icon_position_menu; + static { + boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE; - int FALSE = 0; - int TRUE = 1; + // for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm + // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. + // in some cases, we ALWAYS want to try GTK2 first + if (shouldUseGtk2) { + try { + JnaHelper.register("gtk-x11-2.0", Gtk.class); + gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); + isGtk2 = true; - @Keep - class GdkEventButton extends Structure { - public int type; - public Pointer window; - public int send_event; - public int time; - public double x; - public double y; - public Pointer axes; - public int state; - public int button; - public Pointer device; - public double x_root; - public double y_root; + // when running inside of JavaFX, this will be '1'. All other times this should be '0' + // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. + alreadyRunningGTK = gtk_main_level() != 0; + isLoaded = true; + } catch (Throwable ignored) { + } + } - @Override - protected - List getFieldOrder() { - return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root"); + // now for the defaults... + + // start with version 3 + if (!isLoaded) { + try { + JnaHelper.register("libgtk-3.so.0", Gtk.class); + gtk_status_icon_position_menu = Function.getFunction("libgtk-3.so.0", "gtk_status_icon_position_menu"); + // when running inside of JavaFX, this will be '1'. All other times this should be '0' + // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. + alreadyRunningGTK = gtk_main_level() != 0; + isLoaded = true; + } catch (Throwable ignored) { + } + } + + // now version 2 + if (!isLoaded) { + try { + JnaHelper.register("gtk-x11-2.0", Gtk.class); + gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); + isGtk2 = true; + + // when running inside of JavaFX, this will be '1'. All other times this should be '0' + // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. + alreadyRunningGTK = gtk_main_level() != 0; + isLoaded = true; + } catch (Throwable ignored) { + } + } + if (!isLoaded) { + throw new RuntimeException("We apologize for this, but we are unable to determine the GTK library is in use, " + + "or even if it is in use... Please create an issue for this and include your OS type and configuration."); } } - boolean gtk_init_check(int argc, String[] argv); + private static volatile boolean started = false; + + // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) + private static final LinkedList gtkCallbacks = new LinkedList(); + + private static Thread gtkUpdateThread = null; + + public static final int FALSE = 0; + public static final int TRUE = 1; + + + 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 JavaFX/SWT is used, this is UNNECESSARY + if (!alreadyRunningGTK) { + // only necessary if we are the only GTK instance running... + final CountDownLatch blockUntilStarted = new CountDownLatch(1); + + gtkUpdateThread = new Thread() { + @Override + public + void run() { + // prep for the event loop. + gdk_threads_init(); + gdk_threads_enter(); + + GThread.g_thread_init(null); + + if (!SystemTray.COMPATIBILITY_MODE) { + gtk_init_check(0); + } + + // notify our main thread to continue + blockUntilStarted.countDown(); + + if (!SystemTray.COMPATIBILITY_MODE) { + // blocks unit quit + gtk_main(); + } + + gdk_threads_leave(); + } + }; + gtkUpdateThread.setName("GTK Native Event Loop"); + gtkUpdateThread.start(); + + try { + // we CANNOT continue until the GTK thread has started! + blockUntilStarted.await(); + } 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 (gtkUpdateThread == Thread.currentThread()) { + // if we are ALREADY inside the native event + 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 + } + runnable.run(); + + 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 + } + gdk_threads_add_idle(callback, null); + } + } + + public static + void shutdownGui() { + // If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown) + if (!(alreadyRunningGTK || SystemTray.COMPATIBILITY_MODE)) { + gtk_main_quit(); + } + + started = false; + } + + + + /** + * This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky + * enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here. + */ + private static native boolean gtk_init_check(int argc); /** * Runs the main loop until gtk_main_quit() is called. You can nest calls to gtk_main(). In that case gtk_main_quit() will make the * innermost invocation of the main loop return. */ - void gtk_main(); + private static native void gtk_main(); /** * using g_idle_add() instead would require thread protection in the callback - * @param callback - * @param data + * * @return TRUE to run this callback again, FALSE to remove from the list of event sources (and not call it again) */ - int gdk_threads_add_idle (Gobject.FuncCallback callback, Pointer data); + private static native int gdk_threads_add_idle(FuncCallback callback, Pointer data); - /** aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running */ - int gtk_main_level(); + /** + * aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running + */ + private static native int gtk_main_level(); /** * Makes the innermost invocation of the main loop return when it regains control. ONLY CALL FROM THE GtkSupport class, UNLESS you know * what you're doing! */ - void gtk_main_quit(); + private static native void gtk_main_quit(); - void gdk_threads_init(); + private static native void gdk_threads_init(); // tricky business. This should only be in the dispatch thread - void gdk_threads_enter(); - void gdk_threads_leave(); + private static native void gdk_threads_enter(); + private static native void gdk_threads_leave(); - Pointer gtk_menu_new(); - Pointer gtk_menu_item_new(); - Pointer gtk_menu_item_new_with_label(String label); + + + public static native Pointer gtk_menu_new(); + + public static native Pointer gtk_menu_item_new(); + + public static native Pointer gtk_menu_item_new_with_label(String label); // to create a menu entry WITH an icon. - Pointer gtk_image_new_from_file(String iconPath); + public static native Pointer gtk_image_new_from_file(String iconPath); - Pointer gtk_image_menu_item_new_with_label(String label); + public static native Pointer gtk_image_menu_item_new_with_label(String label); - void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image); + public static native void gtk_image_menu_item_set_image(Pointer image_menu_item, Pointer image); - void gtk_image_menu_item_set_always_show_image(Pointer menu_item, int forceShow); + public static native void gtk_image_menu_item_set_always_show_image(Pointer menu_item, int forceShow); - Pointer gtk_bin_get_child(Pointer parent); + public static native Pointer gtk_bin_get_child(Pointer parent); - void gtk_label_set_text(Pointer label, String text); + public static native void gtk_label_set_text(Pointer label, String text); - void gtk_label_set_markup(Pointer label, Pointer markup); + public static native void gtk_label_set_markup(Pointer label, Pointer markup); - void gtk_label_set_use_markup(Pointer label, int gboolean); + public static native void gtk_label_set_use_markup(Pointer label, int gboolean); - Pointer gtk_status_icon_new(); + public static native Pointer gtk_status_icon_new(); - void gtk_status_icon_set_from_file(Pointer widget, String lablel); + public static native void gtk_status_icon_set_from_file(Pointer widget, String lablel); - void gtk_status_icon_set_visible(Pointer widget, boolean visible); + public static native void gtk_status_icon_set_visible(Pointer widget, boolean visible); // app indicators don't support this, and we cater to the lowest common denominator -// void gtk_status_icon_set_tooltip(Pointer widget, String tooltipText); +// public static native void gtk_status_icon_set_tooltip(Pointer widget, String tooltipText); - void gtk_status_icon_set_title(Pointer widget, String titleText); + public static native void gtk_status_icon_set_title(Pointer widget, String titleText); - void gtk_status_icon_set_name(Pointer widget, String name); + public static native void gtk_status_icon_set_name(Pointer widget, String name); - void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time); + public static native void gtk_menu_popup(Pointer menu, Pointer widget, Pointer bla, Function func, Pointer data, int button, int time); - void gtk_menu_item_set_label(Pointer menu_item, String label); + public static native void gtk_menu_item_set_label(Pointer menu_item, String label); - void gtk_menu_shell_append(Pointer menu_shell, Pointer child); + public static native void gtk_menu_shell_append(Pointer menu_shell, Pointer child); - void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child); + public static native void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child); - void gtk_widget_set_sensitive(Pointer widget, int sensitive); + public static native void gtk_widget_set_sensitive(Pointer widget, int sensitive); - void gtk_container_remove(Pointer menu, Pointer subItem); + public static native void gtk_container_remove(Pointer menu, Pointer subItem); - void gtk_widget_show(Pointer widget); + public static native void gtk_widget_show(Pointer widget); - void gtk_widget_show_all(Pointer widget); + public static native void gtk_widget_show_all(Pointer widget); - void gtk_widget_destroy(Pointer widget); + public static native void gtk_widget_destroy(Pointer widget); } diff --git a/src/dorkbox/systemTray/linux/jna/GtkSupport.java b/src/dorkbox/systemTray/linux/jna/GtkSupport.java deleted file mode 100644 index 48eb3f0..0000000 --- a/src/dorkbox/systemTray/linux/jna/GtkSupport.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2015 dorkbox, llc - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dorkbox.systemTray.linux.jna; - -import com.sun.jna.Function; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import dorkbox.systemTray.SystemTray; - -import java.util.LinkedList; -import java.util.concurrent.CountDownLatch; - -public -class GtkSupport { - // For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python. - // https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py - - private static volatile boolean started = false; - - // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) - private static final LinkedList gtkCallbacks = new LinkedList(); - - /** - * must call get() before accessing this! Only "Gtk" interface should access this! - */ - static volatile Function gtk_status_icon_position_menu = null; - - public static volatile boolean isGtk2 = false; - - private static volatile boolean alreadyRunningGTK = false; - private static Thread gtkUpdateThread = null; - - /** - * Helper for GTK, because we could have v3 or v2. - * - * Observations: JavaFX uses GTK2, and we can't load GTK3 if GTK2 symbols are loaded - * SWT uses GTK2 or GTK3. We do not work with the GTK3 version of SWT. - */ - @SuppressWarnings("Duplicates") - public static - Gtk get() { - Gtk library; - - boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE; - - // for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm - // from the page: JavaFX 2.2.3 for Linux requires gtk2 2.18+. - - // in some cases, we ALWAYS want to try GTK2 first - if (shouldUseGtk2) { - try { - gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); - library = (Gtk) Native.loadLibrary("gtk-x11-2.0", Gtk.class); - if (library != null) { - isGtk2 = true; - - // when running inside of JavaFX, this will be '1'. All other times this should be '0' - // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. - alreadyRunningGTK = library.gtk_main_level() != 0; - return library; - } - } catch (Throwable ignored) { - } - } - - if (AppIndicatorQuery.isLoaded) { - if (AppIndicatorQuery.isVersion3) { - // appindicator3 requires GTK3 - try { - gtk_status_icon_position_menu = Function.getFunction("libgtk-3.so.0", "gtk_status_icon_position_menu"); - library = (Gtk) Native.loadLibrary("libgtk-3.so.0", Gtk.class); - if (library != null) { - // when running inside of JavaFX, this will be '1'. All other times this should be '0' - // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. - alreadyRunningGTK = library.gtk_main_level() != 0; - return library; - } - } catch (Throwable ignored) { - } - } else { - // appindicator1 requires GTK2 - try { - gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); - library = (Gtk) Native.loadLibrary("gtk-x11-2.0", Gtk.class); - if (library != null) { - isGtk2 = true; - - // when running inside of JavaFX, this will be '1'. All other times this should be '0' - // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. - alreadyRunningGTK = library.gtk_main_level() != 0; - return library; - } - } catch (Throwable ignored) { - } - } - } - - // now for the defaults... - - // start with version 3 - try { - gtk_status_icon_position_menu = Function.getFunction("libgtk-3.so.0", "gtk_status_icon_position_menu"); - library = (Gtk) Native.loadLibrary("libgtk-3.so.0", Gtk.class); - if (library != null) { - // when running inside of JavaFX, this will be '1'. All other times this should be '0' - // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. - alreadyRunningGTK = library.gtk_main_level() != 0; - return library; - } - } catch (Throwable ignored) { - } - - // now version 2 - try { - gtk_status_icon_position_menu = Function.getFunction("gtk-x11-2.0", "gtk_status_icon_position_menu"); - library = (Gtk) Native.loadLibrary("gtk-x11-2.0", Gtk.class); - if (library != null) { - isGtk2 = true; - - // when running inside of JavaFX, this will be '1'. All other times this should be '0' - // when it's '1', it means that someone else has stared GTK -- so we DO NOT NEED TO. - alreadyRunningGTK = library.gtk_main_level() != 0; - return library; - } - } catch (Throwable ignored) { - } - - throw new RuntimeException("We apologize for this, but we are unable to determine the GTK library is in use, " + - "or even if it is in use... Please create an issue for this and include your OS type and configuration."); - } - - 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 JavaFX/SWT is used, this is UNNECESSARY - if (!alreadyRunningGTK) { - // only necessary if we are the only GTK instance running... - final CountDownLatch blockUntilStarted = new CountDownLatch(1); - - gtkUpdateThread = new Thread() { - @Override - public - void run() { - Gtk gtk = Gtk.INSTANCE; - - // prep for the event loop. - gtk.gdk_threads_init(); - gtk.gdk_threads_enter(); - - GThread.INSTANCE.g_thread_init(null); - - if (!SystemTray.COMPATIBILITY_MODE) { - gtk.gtk_init_check(0, null); - } - - // notify our main thread to continue - blockUntilStarted.countDown(); - - if (!SystemTray.COMPATIBILITY_MODE) { - // blocks unit quit - gtk.gtk_main(); - } - - gtk.gdk_threads_leave(); - } - }; - gtkUpdateThread.setName("GTK Native Event Loop"); - gtkUpdateThread.start(); - - try { - // we CANNOT continue until the GTK thread has started! - blockUntilStarted.await(); - } 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 (gtkUpdateThread == Thread.currentThread()) { - // if we are ALREADY inside the native event - runnable.run(); - } else { - final Gobject.FuncCallback callback = new Gobject.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 - } - runnable.run(); - - 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 - } - Gtk.INSTANCE.gdk_threads_add_idle(callback, null); - } - } - - public static - void shutdownGui() { - // If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown) - if (!(alreadyRunningGTK || SystemTray.COMPATIBILITY_MODE)) { - Gtk.INSTANCE.gtk_main_quit(); - } - - started = false; - } -} diff --git a/src/dorkbox/systemTray/linux/jna/JnaHelper.java b/src/dorkbox/systemTray/linux/jna/JnaHelper.java new file mode 100644 index 0000000..baa854f --- /dev/null +++ b/src/dorkbox/systemTray/linux/jna/JnaHelper.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 dorkbox, llc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dorkbox.systemTray.linux.jna; + +import com.sun.jna.Library; +import com.sun.jna.Native; +import com.sun.jna.NativeLibrary; + +import java.util.HashMap; +import java.util.Map; + +/** + * Helper method to get the library info from JNA when registering via direct map + */ +class JnaHelper { + @SuppressWarnings("unchecked") + static + NativeLibrary register(final String libraryName, final Class clazz) throws IllegalArgumentException { + final Map options = new HashMap(); + options.put(Library.OPTION_CLASSLOADER, clazz.getClassLoader()); + + final NativeLibrary library = NativeLibrary.getInstance(libraryName, options); + if (library == null) { + throw new IllegalArgumentException(libraryName + " doesn't exist or cannot be loaded."); + } + + Native.register(clazz, library); + return library; + } +}