forked from dorkbox/SystemTray
Switched JNA mode from Proxy -> Direct-Mapping. Direct-Mapping is
significantly faster than Proxy, approaching that of JNI performance.
This commit is contained in:
parent
33cc61c920
commit
495973d3a7
@ -65,6 +65,10 @@ 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')
|
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.
|
- Forces the system to enter into JavaFX/SWT compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop.
|
||||||
This is only necessary if autodetection fails.
|
This is only necessary if autodetection fails.
|
||||||
|
@ -19,7 +19,7 @@ import dorkbox.systemTray.linux.AppIndicatorTray;
|
|||||||
import dorkbox.systemTray.linux.GnomeShellExtension;
|
import dorkbox.systemTray.linux.GnomeShellExtension;
|
||||||
import dorkbox.systemTray.linux.GtkSystemTray;
|
import dorkbox.systemTray.linux.GtkSystemTray;
|
||||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
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.systemTray.swing.SwingSystemTray;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.Property;
|
import dorkbox.util.Property;
|
||||||
@ -53,6 +53,9 @@ public abstract
|
|||||||
class SystemTray {
|
class SystemTray {
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(SystemTray.class);
|
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
|
@Property
|
||||||
/** How long to wait when updating menu entries before the request times-out */
|
/** How long to wait when updating menu entries before the request times-out */
|
||||||
public static final int TIMEOUT = 2;
|
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). */
|
/** Forces the system tray to always choose GTK2 (even when GTK3 might be available). */
|
||||||
public static boolean FORCE_GTK2 = false;
|
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
|
@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.
|
* 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,7 +159,38 @@ class SystemTray {
|
|||||||
// For funsies, SyncThing did a LOT of work on compatibility (unfortunate for us) in python.
|
// 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
|
// https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_GTK) {
|
||||||
|
try {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
} catch (Throwable e1) {
|
||||||
|
if (DEBUG) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (SystemTray.FORCE_LINUX_TYPE == SystemTray.LINUX_APP_INDICATOR) {
|
||||||
|
try {
|
||||||
|
trayType = AppIndicatorTray.class;
|
||||||
|
} catch (Throwable e1) {
|
||||||
|
if (DEBUG) {
|
||||||
|
e1.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
// 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");
|
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
|
||||||
if ("Unity".equalsIgnoreCase(XDG)) {
|
if ("Unity".equalsIgnoreCase(XDG)) {
|
||||||
try {
|
try {
|
||||||
@ -232,7 +270,7 @@ class SystemTray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// is likely 'gnome', but it can also be unknown (or something completely different), install extension and go from there
|
// is likely 'gnome', but it can also be unknown (or something completely different), install extension and go from there
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
@ -292,8 +330,6 @@ class SystemTray {
|
|||||||
if (readLine != null && readLine.contains("indicator-app")) {
|
if (readLine != null && readLine.contains("indicator-app")) {
|
||||||
// make sure we can also load the library (it might be the wrong version)
|
// make sure we can also load the library (it might be the wrong version)
|
||||||
try {
|
try {
|
||||||
//noinspection unused
|
|
||||||
final AppIndicator instance = AppIndicator.INSTANCE;
|
|
||||||
trayType = AppIndicatorTray.class;
|
trayType = AppIndicatorTray.class;
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
|
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
|
||||||
@ -364,15 +400,11 @@ class SystemTray {
|
|||||||
try {
|
try {
|
||||||
ImageUtil.init();
|
ImageUtil.init();
|
||||||
|
|
||||||
// the order of checking here is critical -- AppIndicator.IS_VERSION_3 initializes `appindicator` and `gtk`
|
|
||||||
if (OS.isLinux() &&
|
if (OS.isLinux() &&
|
||||||
trayType == AppIndicatorTray.class &&
|
trayType == AppIndicatorTray.class &&
|
||||||
AppIndicator.IS_VERSION_3 && // this initializes the appindicator (since we specified that via the trayType)
|
Gtk.isGtk2 &&
|
||||||
GtkSupport.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 {
|
try {
|
||||||
trayType = GtkSystemTray.class;
|
trayType = GtkSystemTray.class;
|
||||||
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
|
logger.warn("AppIndicator3 detected with GTK2, falling back to GTK2 system tray type. " +
|
||||||
@ -382,8 +414,8 @@ class SystemTray {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
logger.error("AppIndicator3 detected with GTK2 and unable to fallback to using GTK2 system tray type." +
|
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 " +
|
"AppIndicator3 requires GTK3 to be fully functional, and while this will work -- " +
|
||||||
"NOT be visible." +
|
"the menu icons WILL NOT be visible." +
|
||||||
" Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
|
" Please install libappindicator1 OR GTK3, for example: 'sudo apt-get install libappindicator1'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.linux.jna.AppIndicator;
|
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;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@ -31,9 +34,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
*/
|
*/
|
||||||
public
|
public
|
||||||
class AppIndicatorTray extends GtkTypeSystemTray {
|
class AppIndicatorTray extends GtkTypeSystemTray {
|
||||||
private static final AppIndicator appindicator = AppIndicator.INSTANCE;
|
private AppIndicatorInstanceStruct appIndicator;
|
||||||
|
|
||||||
private AppIndicator.AppIndicatorInstanceStruct appIndicator;
|
|
||||||
private boolean isActive = false;
|
private boolean isActive = false;
|
||||||
|
|
||||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||||
@ -41,14 +42,18 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||||||
|
|
||||||
public
|
public
|
||||||
AppIndicatorTray() {
|
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() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
appIndicator = appindicator.app_indicator_new(System.nanoTime() + "DBST", "",
|
appIndicator = AppIndicator.app_indicator_new(System.nanoTime() + "DBST", "", AppIndicator.CATEGORY_APPLICATION_STATUS);
|
||||||
AppIndicator.CATEGORY_APPLICATION_STATUS);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -62,9 +67,9 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
// STATUS_PASSIVE hides the indicator
|
// 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();
|
Pointer p = appIndicator.getPointer();
|
||||||
gobject.g_object_unref(p);
|
Gobject.g_object_unref(p);
|
||||||
|
|
||||||
appIndicator = null;
|
appIndicator = null;
|
||||||
}
|
}
|
||||||
@ -81,12 +86,12 @@ class AppIndicatorTray extends GtkTypeSystemTray {
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
appindicator.app_indicator_set_icon(appIndicator, iconPath);
|
AppIndicator.app_indicator_set_icon(appIndicator, iconPath);
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
isActive = true;
|
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
|
protected
|
||||||
void onMenuAdded(final Pointer menu) {
|
void onMenuAdded(final Pointer menu) {
|
||||||
// see: https://code.launchpad.net/~mterry/libappindicator/fix-menu-leak/+merge/53247
|
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,9 @@ import com.sun.jna.Pointer;
|
|||||||
import dorkbox.systemTray.ImageUtil;
|
import dorkbox.systemTray.ImageUtil;
|
||||||
import dorkbox.systemTray.MenuEntry;
|
import dorkbox.systemTray.MenuEntry;
|
||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
|
import dorkbox.systemTray.linux.jna.GCallback;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject.GCallback;
|
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.systemTray.linux.jna.GtkSupport;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -33,9 +32,6 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||||||
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
private static final AtomicInteger ID_COUNTER = new AtomicInteger();
|
||||||
private final int id = ID_COUNTER.getAndIncrement();
|
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 Pointer menuItem;
|
||||||
final GtkTypeSystemTray parent;
|
final GtkTypeSystemTray parent;
|
||||||
|
|
||||||
@ -56,20 +52,20 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||||||
this.text = label;
|
this.text = label;
|
||||||
this.callback = callback;
|
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()) {
|
if (imagePath != null && !imagePath.isEmpty()) {
|
||||||
// NOTE: XFCE uses appindicator3, which DOES NOT support images in the menu. This change was reverted.
|
// 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://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
|
// 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
|
// 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
|
@Override
|
||||||
public
|
public
|
||||||
void setText(final String newText) {
|
void setText(final String newText) {
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
text = newText;
|
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
|
private
|
||||||
void setImage_(final String imagePath) {
|
void setImage_(final String imagePath) {
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
gtk.gtk_widget_destroy(image);
|
Gtk.gtk_widget_destroy(image);
|
||||||
image = null;
|
image = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk.gtk_widget_show_all(menuItem);
|
Gtk.gtk_widget_show_all(menuItem);
|
||||||
|
|
||||||
if (imagePath != null && !imagePath.isEmpty()) {
|
if (imagePath != null && !imagePath.isEmpty()) {
|
||||||
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);
|
||||||
gobject.g_object_ref_sink(image);
|
Gobject.g_object_ref_sink(image);
|
||||||
|
|
||||||
// must always re-set always-show after setting the 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
|
public
|
||||||
void remove() {
|
void remove() {
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
@ -208,13 +204,13 @@ class GtkMenuEntry implements MenuEntry, GCallback {
|
|||||||
|
|
||||||
void removePrivate() {
|
void removePrivate() {
|
||||||
callback = null;
|
callback = null;
|
||||||
gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem);
|
Gtk.gtk_menu_shell_deactivate(parent.getMenu(), menuItem);
|
||||||
|
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
gtk.gtk_widget_destroy(image);
|
Gtk.gtk_widget_destroy(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk.gtk_widget_destroy(menuItem);
|
Gtk.gtk_widget_destroy(menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,9 +17,10 @@ package dorkbox.systemTray.linux;
|
|||||||
|
|
||||||
import com.sun.jna.NativeLong;
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
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.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.systemTray.linux.jna.GtkSupport;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -45,29 +46,28 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||||||
public
|
public
|
||||||
GtkSystemTray() {
|
GtkSystemTray() {
|
||||||
super();
|
super();
|
||||||
GtkSupport.startGui();
|
Gtk.startGui();
|
||||||
|
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
final Pointer trayIcon_ = gtk.gtk_status_icon_new();
|
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");
|
||||||
gtk.gtk_status_icon_set_name(trayIcon_, "SystemTray");
|
|
||||||
|
|
||||||
trayIcon = trayIcon_;
|
trayIcon = trayIcon_;
|
||||||
|
|
||||||
final Gobject.GEventCallback gtkCallback = new Gobject.GEventCallback() {
|
final GEventCallback gtkCallback = new GEventCallback() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void callback(Pointer notUsed, final Gtk.GdkEventButton event) {
|
void callback(Pointer notUsed, final GdkEventButton event) {
|
||||||
// BUTTON_PRESS only (any mouse click)
|
// BUTTON_PRESS only (any mouse click)
|
||||||
if (event.type == 4) {
|
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
|
// have to do this to prevent GC on these objects
|
||||||
gtkCallbacks.add(gtkCallback);
|
gtkCallbacks.add(gtkCallback);
|
||||||
@ -86,8 +86,8 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
// this hides the indicator
|
// this hides the indicator
|
||||||
gtk.gtk_status_icon_set_visible(trayIcon, false);
|
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
||||||
gobject.g_object_unref(trayIcon);
|
Gobject.g_object_unref(trayIcon);
|
||||||
|
|
||||||
// mark for GC
|
// mark for GC
|
||||||
trayIcon = null;
|
trayIcon = null;
|
||||||
@ -106,11 +106,11 @@ class GtkSystemTray extends GtkTypeSystemTray {
|
|||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
|
Gtk.gtk_status_icon_set_from_file(trayIcon, iconPath);
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
isActive = true;
|
isActive = true;
|
||||||
gtk.gtk_status_icon_set_visible(trayIcon, true);
|
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,6 @@ import dorkbox.systemTray.SystemTray;
|
|||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
import dorkbox.systemTray.linux.jna.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.systemTray.linux.jna.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.systemTray.linux.jna.GtkSupport;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -33,9 +32,6 @@ import java.net.URL;
|
|||||||
*/
|
*/
|
||||||
public abstract
|
public abstract
|
||||||
class GtkTypeSystemTray extends SystemTray {
|
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 menu;
|
||||||
|
|
||||||
private volatile Pointer connectionStatusItem;
|
private volatile Pointer connectionStatusItem;
|
||||||
@ -44,19 +40,19 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
void dispatch(final Runnable runnable) {
|
void dispatch(final Runnable runnable) {
|
||||||
GtkSupport.dispatch(runnable);
|
Gtk.dispatch(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
obliterateMenu();
|
obliterateMenu();
|
||||||
|
|
||||||
GtkSupport.shutdownGui();
|
Gtk.shutdownGui();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -72,7 +68,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
void setStatus(final String statusText) {
|
void setStatus(final String statusText) {
|
||||||
this.statusText = statusText;
|
this.statusText = statusText;
|
||||||
|
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
@ -81,16 +77,16 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
if (connectionStatusItem == null && statusText != null && !statusText.isEmpty()) {
|
if (connectionStatusItem == null && statusText != null && !statusText.isEmpty()) {
|
||||||
deleteMenu();
|
deleteMenu();
|
||||||
|
|
||||||
connectionStatusItem = gtk.gtk_menu_item_new_with_label("");
|
connectionStatusItem = Gtk.gtk_menu_item_new_with_label("");
|
||||||
|
|
||||||
// evil hacks abound...
|
// evil hacks abound...
|
||||||
Pointer label = gtk.gtk_bin_get_child(connectionStatusItem);
|
Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem);
|
||||||
gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
|
Gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
|
||||||
Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
|
Pointer markup = Gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
|
||||||
gtk.gtk_label_set_markup(label, markup);
|
Gtk.gtk_label_set_markup(label, markup);
|
||||||
gobject.g_free(markup);
|
Gobject.g_free(markup);
|
||||||
|
|
||||||
gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE);
|
Gtk.gtk_widget_set_sensitive(connectionStatusItem, Gtk.FALSE);
|
||||||
|
|
||||||
createMenu();
|
createMenu();
|
||||||
}
|
}
|
||||||
@ -98,10 +94,10 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
if (statusText == null || statusText.isEmpty()) {
|
if (statusText == null || statusText.isEmpty()) {
|
||||||
// this means the status text already exists, and we are removing it
|
// 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
|
connectionStatusItem = null; // because we manually delete it
|
||||||
|
|
||||||
gtk.gtk_widget_show_all(menu);
|
Gtk.gtk_widget_show_all(menu);
|
||||||
|
|
||||||
deleteMenu();
|
deleteMenu();
|
||||||
createMenu();
|
createMenu();
|
||||||
@ -113,13 +109,13 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText);
|
// libgtk.gtk_menu_item_set_label(this.connectionStatusItem, statusText);
|
||||||
|
|
||||||
// evil hacks abound...
|
// evil hacks abound...
|
||||||
Pointer label = gtk.gtk_bin_get_child(connectionStatusItem);
|
Pointer label = Gtk.gtk_bin_get_child(connectionStatusItem);
|
||||||
gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
|
Gtk.gtk_label_set_use_markup(label, Gtk.TRUE);
|
||||||
Pointer markup = gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
|
Pointer markup = Gobject.g_markup_printf_escaped("<b>%s</b>", statusText);
|
||||||
gtk.gtk_label_set_markup(label, markup);
|
Gtk.gtk_label_set_markup(label, markup);
|
||||||
gobject.g_free(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) {
|
if (menu != null) {
|
||||||
// have to remove status from menu (but not destroy the object)
|
// have to remove status from menu (but not destroy the object)
|
||||||
if (connectionStatusItem != null) {
|
if (connectionStatusItem != null) {
|
||||||
gobject.g_object_force_floating(connectionStatusItem);
|
Gobject.g_object_force_floating(connectionStatusItem);
|
||||||
gtk.gtk_container_remove(menu, connectionStatusItem);
|
Gtk.gtk_container_remove(menu, connectionStatusItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// have to remove all other menu entries
|
// have to remove all other menu entries
|
||||||
@ -144,16 +140,16 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
for (int i = 0; i < menuEntries.size(); i++) {
|
for (int i = 0; i < menuEntries.size(); i++) {
|
||||||
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
||||||
|
|
||||||
gobject.g_object_force_floating(menuEntry__.menuItem);
|
Gobject.g_object_force_floating(menuEntry__.menuItem);
|
||||||
gtk.gtk_container_remove(menu, menuEntry__.menuItem);
|
Gtk.gtk_container_remove(menu, menuEntry__.menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk.gtk_widget_destroy(menu);
|
Gtk.gtk_widget_destroy(menu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes a new one
|
// 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.
|
// 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() {
|
void createMenu() {
|
||||||
// now add status
|
// now add status
|
||||||
if (connectionStatusItem != null) {
|
if (connectionStatusItem != null) {
|
||||||
gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
|
Gtk.gtk_menu_shell_append(this.menu, this.connectionStatusItem);
|
||||||
gobject.g_object_ref_sink(connectionStatusItem);
|
Gobject.g_object_ref_sink(connectionStatusItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now add back other menu entries
|
// now add back other menu entries
|
||||||
@ -171,12 +167,12 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
GtkMenuEntry menuEntry__ = (GtkMenuEntry) menuEntries.get(i);
|
||||||
|
|
||||||
// will also get: gsignal.c:2516: signal 'child-added' is invalid for instance '0x7f1df8244080' of type 'GtkMenu'
|
// 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);
|
Gtk.gtk_menu_shell_append(this.menu, menuEntry__.menuItem);
|
||||||
gobject.g_object_ref_sink(menuEntry__.menuItem);
|
Gobject.g_object_ref_sink(menuEntry__.menuItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMenuAdded(menu);
|
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) {
|
if (menu != null) {
|
||||||
// have to remove status from menu
|
// have to remove status from menu
|
||||||
if (connectionStatusItem != null) {
|
if (connectionStatusItem != null) {
|
||||||
gtk.gtk_widget_destroy(connectionStatusItem);
|
Gtk.gtk_widget_destroy(connectionStatusItem);
|
||||||
connectionStatusItem = null;
|
connectionStatusItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +197,7 @@ class GtkTypeSystemTray extends SystemTray {
|
|||||||
}
|
}
|
||||||
menuEntries.clear();
|
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");
|
throw new NullPointerException("Menu text cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkSupport.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -15,55 +15,176 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.linux.jna;
|
package dorkbox.systemTray.linux.jna;
|
||||||
|
|
||||||
import com.sun.jna.Library;
|
import com.sun.jna.NativeLibrary;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.Structure;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.util.Keep;
|
|
||||||
|
|
||||||
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
|
public
|
||||||
interface AppIndicator extends Library {
|
class AppIndicator {
|
||||||
// effing retarded. There are DIFFERENT versions, of which they all share the same basic compatibility (of the methods that
|
public static boolean isVersion3 = false;
|
||||||
// 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();
|
|
||||||
|
|
||||||
/** Necessary to provide warnings, because libappindicator3 won't properly work with GTK2 */
|
private static boolean isLoaded = false;
|
||||||
boolean IS_VERSION_3 = AppIndicatorQuery.isVersion3;
|
|
||||||
|
|
||||||
int CATEGORY_APPLICATION_STATUS = 0;
|
/**
|
||||||
int CATEGORY_COMMUNICATIONS = 1;
|
* Loader for AppIndicator, because it is absolutely mindboggling how those whom maintain the standard, can't agree to what that
|
||||||
int CATEGORY_SYSTEM_SERVICES = 2;
|
* standard library naming convention or features/API set is. We just try until we find one that work, and are able to map the
|
||||||
int CATEGORY_HARDWARE = 3;
|
* symbols we need. There are bash commands that will tell us the linked library name, however - I'd rather not run bash commands
|
||||||
int CATEGORY_OTHER = 4;
|
* 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;
|
// NOTE:
|
||||||
int STATUS_ACTIVE = 1;
|
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
||||||
int STATUS_ATTENTION = 2;
|
// 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
|
// If we are GTK2, change the order we check and load libraries
|
||||||
protected
|
|
||||||
List<String> getFieldOrder() {
|
if (Gtk.isGtk2) {
|
||||||
return Arrays.asList("parent", "priv");
|
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.
|
// Note: AppIndicators DO NOT support tooltips, as per mark shuttleworth. Rather stupid IMHO.
|
||||||
// See: https://bugs.launchpad.net/indicator-application/+bug/527458/comments/12
|
// 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);
|
public static final int STATUS_PASSIVE = 0;
|
||||||
void app_indicator_set_menu(AppIndicatorInstanceStruct self, Pointer menu);
|
public static final int STATUS_ACTIVE = 1;
|
||||||
void app_indicator_set_icon(AppIndicatorInstanceStruct self, String icon_name);
|
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);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
@ -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.");
|
|
||||||
}
|
|
||||||
}
|
|
28
src/dorkbox/systemTray/linux/jna/FuncCallback.java
Normal file
28
src/dorkbox/systemTray/linux/jna/FuncCallback.java
Normal 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);
|
||||||
|
}
|
29
src/dorkbox/systemTray/linux/jna/GCallback.java
Normal file
29
src/dorkbox/systemTray/linux/jna/GCallback.java
Normal 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);
|
||||||
|
}
|
26
src/dorkbox/systemTray/linux/jna/GEventCallback.java
Normal file
26
src/dorkbox/systemTray/linux/jna/GEventCallback.java
Normal 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);
|
||||||
|
}
|
45
src/dorkbox/systemTray/linux/jna/GObjectStruct.java
Normal file
45
src/dorkbox/systemTray/linux/jna/GObjectStruct.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,18 @@
|
|||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.linux.jna;
|
package dorkbox.systemTray.linux.jna;
|
||||||
|
|
||||||
import com.sun.jna.Library;
|
|
||||||
import com.sun.jna.Native;
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bindings for libgthread
|
||||||
|
*
|
||||||
|
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
||||||
|
*/
|
||||||
public
|
public
|
||||||
interface GThread extends Library {
|
class GThread {
|
||||||
GThread INSTANCE = (GThread) Native.loadLibrary("gthread-2.0", GThread.class);
|
static {
|
||||||
|
JnaHelper.register("gthread-2.0", GThread.class);
|
||||||
void g_thread_init(Pointer GThreadFunctions);
|
}
|
||||||
|
|
||||||
|
public static native void g_thread_init(Pointer GThreadFunctions);
|
||||||
}
|
}
|
||||||
|
43
src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java
Normal file
43
src/dorkbox/systemTray/linux/jna/GTypeInstanceStruct.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
46
src/dorkbox/systemTray/linux/jna/GdkEventButton.java
Normal file
46
src/dorkbox/systemTray/linux/jna/GdkEventButton.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
@ -16,169 +16,28 @@
|
|||||||
package dorkbox.systemTray.linux.jna;
|
package dorkbox.systemTray.linux.jna;
|
||||||
|
|
||||||
import com.sun.jna.Callback;
|
import com.sun.jna.Callback;
|
||||||
import com.sun.jna.Library;
|
|
||||||
import com.sun.jna.Native;
|
|
||||||
import com.sun.jna.NativeLong;
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.Structure;
|
|
||||||
import dorkbox.util.Keep;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public
|
|
||||||
interface Gobject extends Library {
|
|
||||||
Gobject INSTANCE = (Gobject) Native.loadLibrary("gobject-2.0", Gobject.class);
|
|
||||||
|
|
||||||
@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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@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
|
* bindings for libgobject-2.0
|
||||||
|
*
|
||||||
|
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
||||||
*/
|
*/
|
||||||
int callback(Pointer data);
|
public
|
||||||
|
class Gobject {
|
||||||
|
|
||||||
|
static {
|
||||||
|
JnaHelper.register("gobject-2.0", Gobject.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Keep
|
public static native void g_free(Pointer object);
|
||||||
interface GCallback extends Callback {
|
public static native void g_object_unref(Pointer object);
|
||||||
/**
|
|
||||||
* @return Gtk.TRUE if we handled this event
|
public static native void g_object_force_floating(Pointer object);
|
||||||
*/
|
public static native void g_object_ref_sink(Pointer object);
|
||||||
int callback(Pointer instance, Pointer data);
|
|
||||||
}
|
public static native NativeLong g_signal_connect_object(Pointer instance, String detailed_signal, Callback c_handler, Pointer object, int connect_flags);
|
||||||
|
|
||||||
|
public static native Pointer g_markup_printf_escaped(String pattern, String inputString);
|
||||||
@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);
|
|
||||||
}
|
}
|
||||||
|
@ -16,132 +16,297 @@
|
|||||||
package dorkbox.systemTray.linux.jna;
|
package dorkbox.systemTray.linux.jna;
|
||||||
|
|
||||||
import com.sun.jna.Function;
|
import com.sun.jna.Function;
|
||||||
import com.sun.jna.Library;
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.Structure;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.util.Keep;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
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
|
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-x11-2.0.so.0 | grep gtk
|
||||||
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
|
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
|
||||||
Gtk INSTANCE = GtkSupport.get();
|
static {
|
||||||
Function gtk_status_icon_position_menu = GtkSupport.gtk_status_icon_position_menu;
|
boolean shouldUseGtk2 = SystemTray.FORCE_GTK2 || SystemTray.COMPATIBILITY_MODE;
|
||||||
|
|
||||||
int FALSE = 0;
|
// for more info on JavaFX: https://docs.oracle.com/javafx/2/system_requirements_2-2-3/jfxpub-system_requirements_2-2-3.htm
|
||||||
int TRUE = 1;
|
// 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;
|
||||||
|
|
||||||
|
// 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 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
|
||||||
@Keep
|
public static
|
||||||
class GdkEventButton extends Structure {
|
void startGui() {
|
||||||
public int type;
|
// only permit one startup per JVM instance
|
||||||
public Pointer window;
|
if (!started) {
|
||||||
public int send_event;
|
started = true;
|
||||||
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;
|
|
||||||
|
|
||||||
|
// 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
|
@Override
|
||||||
protected
|
public
|
||||||
List<String> getFieldOrder() {
|
void run() {
|
||||||
return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root");
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean gtk_init_check(int argc, String[] argv);
|
/**
|
||||||
|
* 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
|
* 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.
|
* 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
|
* 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)
|
* @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
|
* 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!
|
* 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
|
// tricky business. This should only be in the dispatch thread
|
||||||
void gdk_threads_enter();
|
private static native void gdk_threads_enter();
|
||||||
void gdk_threads_leave();
|
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.
|
// 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
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
43
src/dorkbox/systemTray/linux/jna/JnaHelper.java
Normal file
43
src/dorkbox/systemTray/linux/jna/JnaHelper.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user