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