Switched JNA mode from Proxy -> Direct-Mapping. Direct-Mapping is

significantly faster than Proxy, approaching that of JNI performance.
This commit is contained in:
nathan 2016-05-08 15:51:07 +02:00
parent 33cc61c920
commit 495973d3a7
20 changed files with 902 additions and 783 deletions

View File

@ -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.

View File

@ -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'");
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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);
}
}
});

View File

@ -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("<b>%s</b>", 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("<b>%s</b>", 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("<b>%s</b>", 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("<b>%s</b>", 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() {

View File

@ -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<String> 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);
}

View File

@ -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<String> getFieldOrder() {
return Arrays.asList("parent", "priv");
}
}

View File

@ -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.");
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<String> getFieldOrder() {
return Arrays.asList("g_type_instance", "ref_count", "qdata");
}
}

View File

@ -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);
}

View File

@ -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<String> getFieldOrder() {
return Arrays.asList("g_class");
}
}

View File

@ -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<String> getFieldOrder() {
return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root");
}
}

View File

@ -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<String> 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<String> 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<String> 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<String> 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<String> 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);
}

View File

@ -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<String> 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<Object> gtkCallbacks = new LinkedList<Object>();
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);
}

View File

@ -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<Object> gtkCallbacks = new LinkedList<Object>();
/**
* 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;
}
}

View File

@ -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<String, Object> options = new HashMap<String, Object>();
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;
}
}