Moved JNA libraries into SystemTray project (it made no sense to have them inside the utils project)
This commit is contained in:
parent
db19374bfc
commit
eb8092677d
|
@ -18,11 +18,11 @@ package dorkbox.systemTray;
|
||||||
import dorkbox.systemTray.linux.AppIndicatorTray;
|
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.GtkSupport;
|
||||||
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;
|
||||||
import dorkbox.util.jna.linux.AppIndicator;
|
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
|
||||||
import dorkbox.util.process.ShellProcessBuilder;
|
import dorkbox.util.process.ShellProcessBuilder;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -60,168 +60,193 @@ class SystemTray {
|
||||||
static {
|
static {
|
||||||
Class<? extends SystemTray> trayType = null;
|
Class<? extends SystemTray> trayType = null;
|
||||||
|
|
||||||
// Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
|
boolean isJavaFxLoaded = false;
|
||||||
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
|
boolean isSwtLoaded = false;
|
||||||
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
|
try {
|
||||||
|
// First check if JavaFX is loaded - if it's NOT LOADED, then we only proceed if JAVAFX_COMPATIBILITY_MODE is enabled.
|
||||||
if (OS.isWindows()) {
|
// this is important, because if JavaFX is not being used, calling getToolkit() will initialize it...
|
||||||
// the tray icon size in windows is DIFFERENT than on Mac (TODO: test on mac with retina stuff).
|
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
||||||
TRAY_SIZE -= 4;
|
m.setAccessible(true);
|
||||||
|
ClassLoader cl = ClassLoader.getSystemClassLoader();
|
||||||
|
isJavaFxLoaded = null != m.invoke(cl, "com.sun.javafx.tk.Toolkit");
|
||||||
|
isSwtLoaded = null != m.invoke(cl, "org.eclipse.swt.widgets.Display");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OS.isLinux()) {
|
// maybe we should load the SWT version? (SWT's use of GTK is incompatible with how we use GTK)
|
||||||
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
|
||||||
|
|
||||||
// quick check, because we know that unity uses app-indicator. Maybe REALLY old versions do not. We support 14.04 LTE at least
|
if (isSwtLoaded) {
|
||||||
String XDG = System.getenv("XDG_CURRENT_DESKTOP");
|
|
||||||
if ("Unity".equalsIgnoreCase(XDG)) {
|
|
||||||
try {
|
|
||||||
trayType = AppIndicatorTray.class;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ("XFCE".equalsIgnoreCase(XDG)) {
|
|
||||||
try {
|
|
||||||
trayType = AppIndicatorTray.class;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
// we can fail on AppIndicator, so this is the fallback
|
|
||||||
//noinspection EmptyCatchBlock
|
|
||||||
try {
|
|
||||||
trayType = GtkSystemTray.class;
|
|
||||||
} catch (Throwable i) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ("LXDE".equalsIgnoreCase(XDG)) {
|
|
||||||
try {
|
|
||||||
trayType = GtkSystemTray.class;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ("KDE".equalsIgnoreCase(XDG)) {
|
|
||||||
isKDE = true;
|
|
||||||
try {
|
|
||||||
trayType = AppIndicatorTray.class;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ("GNOME".equalsIgnoreCase(XDG)) {
|
|
||||||
// check other DE
|
|
||||||
String GDM = System.getenv("GDMSESSION");
|
|
||||||
|
|
||||||
if ("cinnamon".equalsIgnoreCase(GDM)) {
|
}
|
||||||
|
else {
|
||||||
|
// Note: AppIndicators DO NOT support tooltips. We could try to create one, by creating a GTK widget and attaching it on
|
||||||
|
// mouseover or something, but I don't know how to do that. It seems that tooltips for app-indicators are a custom job, as
|
||||||
|
// all examined ones sometimes have it (and it's more than just text), or they don't have it at all.
|
||||||
|
|
||||||
|
if (OS.isWindows()) {
|
||||||
|
// the tray icon size in windows is DIFFERENT than on Mac (TODO: test on mac with retina stuff).
|
||||||
|
TRAY_SIZE -= 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OS.isLinux()) {
|
||||||
|
// see: https://askubuntu.com/questions/72549/how-to-determine-which-window-manager-is-running
|
||||||
|
|
||||||
|
if (isJavaFxLoaded) {
|
||||||
|
// we MUST use GTK2 with javaFX!
|
||||||
|
GtkSupport.FORCE_GTK2 = isJavaFxLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
try {
|
||||||
trayType = GtkSystemTray.class;
|
trayType = AppIndicatorTray.class;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
else if ("XFCE".equalsIgnoreCase(XDG)) {
|
||||||
try {
|
try {
|
||||||
trayType = GtkSystemTray.class;
|
trayType = AppIndicatorTray.class;
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
// we can fail on AppIndicator, so this is the fallback
|
||||||
}
|
//noinspection EmptyCatchBlock
|
||||||
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
|
||||||
try {
|
|
||||||
trayType = GtkSystemTray.class;
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// unknown exactly, install extension and go from there
|
|
||||||
if (trayType == null) {
|
|
||||||
// if the "topicons" extension is installed, don't install us (because it will override what we do, where ours
|
|
||||||
// is more specialized - so it only modified our tray icon (instead of ALL tray icons)
|
|
||||||
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
|
||||||
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
|
||||||
|
|
||||||
// gnome-shell --version
|
|
||||||
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
|
||||||
shellVersion.setExecutable("gnome-shell");
|
|
||||||
shellVersion.addArgument("--version");
|
|
||||||
shellVersion.start();
|
|
||||||
|
|
||||||
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
|
||||||
|
|
||||||
if (!output.isEmpty()) {
|
|
||||||
GnomeShellExtension.install(logger, output);
|
|
||||||
trayType = GtkSystemTray.class;
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
trayType = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
|
|
||||||
if (trayType == null) {
|
|
||||||
BufferedReader bin = null;
|
|
||||||
try {
|
|
||||||
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
|
|
||||||
// is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli
|
|
||||||
File proc = new File("/proc");
|
|
||||||
File[] listFiles = proc.listFiles();
|
|
||||||
if (listFiles != null) {
|
|
||||||
for (File procs : listFiles) {
|
|
||||||
String name = procs.getName();
|
|
||||||
|
|
||||||
if (!Character.isDigit(name.charAt(0))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
File status = new File(procs, "status");
|
|
||||||
if (!status.canRead()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
bin = new BufferedReader(new FileReader(status));
|
|
||||||
String readLine = bin.readLine();
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
if (AppIndicator.IS_VERSION_3) {
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (bin != null) {
|
|
||||||
bin.close();
|
|
||||||
bin = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
} finally {
|
|
||||||
if (bin != null) {
|
|
||||||
try {
|
try {
|
||||||
bin.close();
|
trayType = GtkSystemTray.class;
|
||||||
} catch (IOException ignored) {
|
} catch (Throwable i) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else if ("LXDE".equalsIgnoreCase(XDG)) {
|
||||||
|
try {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("KDE".equalsIgnoreCase(XDG)) {
|
||||||
|
isKDE = true;
|
||||||
|
try {
|
||||||
|
trayType = AppIndicatorTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("GNOME".equalsIgnoreCase(XDG)) {
|
||||||
|
// check other DE
|
||||||
|
String GDM = System.getenv("GDMSESSION");
|
||||||
|
|
||||||
|
if ("cinnamon".equalsIgnoreCase(GDM)) {
|
||||||
|
try {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("gnome-classic".equalsIgnoreCase(GDM)) {
|
||||||
|
try {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("gnome-fallback".equalsIgnoreCase(GDM)) {
|
||||||
|
try {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// fallback...
|
// unknown exactly, install extension and go from there
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
trayType = GtkSystemTray.class;
|
// if the "topicons" extension is installed, don't install us (because it will override what we do, where ours
|
||||||
logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " +
|
// is more specialized - so it only modified our tray icon (instead of ALL tray icons)
|
||||||
"configuration");
|
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8196);
|
||||||
|
PrintStream outputStream = new PrintStream(byteArrayOutputStream);
|
||||||
|
|
||||||
|
// gnome-shell --version
|
||||||
|
final ShellProcessBuilder shellVersion = new ShellProcessBuilder(outputStream);
|
||||||
|
shellVersion.setExecutable("gnome-shell");
|
||||||
|
shellVersion.addArgument("--version");
|
||||||
|
shellVersion.start();
|
||||||
|
|
||||||
|
String output = ShellProcessBuilder.getOutput(byteArrayOutputStream);
|
||||||
|
|
||||||
|
if (!output.isEmpty()) {
|
||||||
|
GnomeShellExtension.install(logger, output);
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
trayType = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to autodetect if we can use app indicators (or if we need to fallback to GTK indicators)
|
||||||
|
if (trayType == null) {
|
||||||
|
BufferedReader bin = null;
|
||||||
|
try {
|
||||||
|
// the ONLY guaranteed way to determine if indicator-application-service is running (and thus, using app-indicator),
|
||||||
|
// is to look through all /proc/<pid>/status, and first line should be Name:\tindicator-appli
|
||||||
|
File proc = new File("/proc");
|
||||||
|
File[] listFiles = proc.listFiles();
|
||||||
|
if (listFiles != null) {
|
||||||
|
for (File procs : listFiles) {
|
||||||
|
String name = procs.getName();
|
||||||
|
|
||||||
|
if (!Character.isDigit(name.charAt(0))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
File status = new File(procs, "status");
|
||||||
|
if (!status.canRead()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
bin = new BufferedReader(new FileReader(status));
|
||||||
|
String readLine = bin.readLine();
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (AppIndicator.IS_VERSION_3) {
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.error("AppIndicator support detected, but unable to load the library. Falling back to GTK");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (bin != null) {
|
||||||
|
bin.close();
|
||||||
|
bin = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
} finally {
|
||||||
|
if (bin != null) {
|
||||||
|
try {
|
||||||
|
bin.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// fallback...
|
||||||
|
if (trayType == null) {
|
||||||
|
trayType = GtkSystemTray.class;
|
||||||
|
logger.error("Unable to load the system tray native library. Please write an issue and include your OS type and " +
|
||||||
|
"configuration");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,11 +262,12 @@ class SystemTray {
|
||||||
|
|
||||||
if (trayType == null) {
|
if (trayType == null) {
|
||||||
// unsupported tray
|
// unsupported tray
|
||||||
logger.error("Unsupported tray type!");
|
logger.error("Unable to discover what tray implementation to use!");
|
||||||
systemTray = null;
|
systemTray = null;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SystemTray systemTray_ = null;
|
SystemTray systemTray_ = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ImageUtil.init();
|
ImageUtil.init();
|
||||||
|
|
||||||
|
@ -251,8 +277,6 @@ class SystemTray {
|
||||||
AppIndicator.IS_VERSION_3 && // this initializes the appindicator (since we specified that via the trayType)
|
AppIndicator.IS_VERSION_3 && // this initializes the appindicator (since we specified that via the trayType)
|
||||||
GtkSupport.isGtk2) {
|
GtkSupport.isGtk2) {
|
||||||
|
|
||||||
final boolean isVersion3 = AppIndicator.IS_VERSION_3;
|
|
||||||
|
|
||||||
// NOTE:
|
// NOTE:
|
||||||
// ALSO WHAT VERSION OF GTK to use? appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
// 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
|
// appindicator3 doesn't support menu icons via GTK2. AT THIS POINT, we DO NOT have GTK3
|
||||||
|
@ -280,22 +304,9 @@ class SystemTray {
|
||||||
systemTray = systemTray_;
|
systemTray = systemTray_;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Necessary because javaFX **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive.
|
// Necessary because javaFX **ALSO** runs a gtk main loop, and when it stops (if we don't stop first), we become unresponsive.
|
||||||
// we ONLY need this on linux for compatibility with JavaFX... (windows/mac don't use gtk)
|
// we ONLY need this on linux for compatibility with JavaFX... (windows/mac don't use gtk)
|
||||||
if (OS.isLinux()) {
|
if (OS.isLinux()) {
|
||||||
boolean isJavaFxLoaded = false;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// First check if JavaFX is loaded - if it's NOT LOADED, then we only proceed if JAVAFX_COMPATIBILITY_MODE is enabled.
|
|
||||||
// this is important, because if JavaFX is not being used, calling getToolkit() will initialize it...
|
|
||||||
java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
|
|
||||||
m.setAccessible(true);
|
|
||||||
ClassLoader cl = ClassLoader.getSystemClassLoader();
|
|
||||||
isJavaFxLoaded = null != m.invoke(cl, "com.sun.javafx.tk.Toolkit");
|
|
||||||
} catch (Throwable ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isJavaFxLoaded || GtkSupport.JAVAFX_COMPATIBILITY_MODE) {
|
if (isJavaFxLoaded || GtkSupport.JAVAFX_COMPATIBILITY_MODE) {
|
||||||
// com.sun.javafx.tk.Toolkit.getToolkit()
|
// com.sun.javafx.tk.Toolkit.getToolkit()
|
||||||
// .addShutdownHook(new Runnable() {
|
// .addShutdownHook(new Runnable() {
|
||||||
|
|
|
@ -16,8 +16,8 @@
|
||||||
package dorkbox.systemTray.linux;
|
package dorkbox.systemTray.linux;
|
||||||
|
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import dorkbox.util.jna.linux.AppIndicator;
|
import dorkbox.systemTray.linux.jna.AppIndicator;
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
import dorkbox.systemTray.linux.jna.GtkSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling all system tray interactions.
|
* Class for handling all system tray interactions.
|
||||||
|
|
|
@ -20,10 +20,10 @@ 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.util.jna.linux.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.util.jna.linux.Gobject.GCallback;
|
import dorkbox.systemTray.linux.jna.Gobject.GCallback;
|
||||||
import dorkbox.util.jna.linux.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
import dorkbox.systemTray.linux.jna.GtkSupport;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
|
@ -17,9 +17,9 @@ 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.util.jna.linux.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.util.jna.linux.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
import dorkbox.systemTray.linux.jna.GtkSupport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling all system tray interactions via GTK.
|
* Class for handling all system tray interactions via GTK.
|
||||||
|
|
|
@ -21,9 +21,9 @@ import dorkbox.systemTray.ImageUtil;
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.SystemTrayMenuAction;
|
import dorkbox.systemTray.SystemTrayMenuAction;
|
||||||
import dorkbox.util.NamedThreadFactory;
|
import dorkbox.util.NamedThreadFactory;
|
||||||
import dorkbox.util.jna.linux.Gobject;
|
import dorkbox.systemTray.linux.jna.Gobject;
|
||||||
import dorkbox.util.jna.linux.Gtk;
|
import dorkbox.systemTray.linux.jna.Gtk;
|
||||||
import dorkbox.util.jna.linux.GtkSupport;
|
import dorkbox.systemTray.linux.jna.GtkSupport;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
69
src/dorkbox/systemTray/linux/jna/AppIndicator.java
Normal file
69
src/dorkbox/systemTray/linux/jna/AppIndicator.java
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* 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.Library;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
import dorkbox.util.Keep;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/* bindings for libappindicator */
|
||||||
|
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();
|
||||||
|
|
||||||
|
/** Necessary to provide warnings, because libappindicator3 won't properly work with GTK2 */
|
||||||
|
boolean IS_VERSION_3 = AppIndicatorQuery.isVersion3;
|
||||||
|
|
||||||
|
int CATEGORY_APPLICATION_STATUS = 0;
|
||||||
|
int CATEGORY_COMMUNICATIONS = 1;
|
||||||
|
int CATEGORY_SYSTEM_SERVICES = 2;
|
||||||
|
int CATEGORY_HARDWARE = 3;
|
||||||
|
int CATEGORY_OTHER = 4;
|
||||||
|
|
||||||
|
int STATUS_PASSIVE = 0;
|
||||||
|
int STATUS_ACTIVE = 1;
|
||||||
|
int STATUS_ATTENTION = 2;
|
||||||
|
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
class AppIndicatorInstanceStruct extends Structure {
|
||||||
|
public Gobject.GObjectStruct parent;
|
||||||
|
public Pointer priv;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("parent", "priv");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
120
src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java
Normal file
120
src/dorkbox/systemTray/linux/jna/AppIndicatorQuery.java
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
Object library;
|
||||||
|
|
||||||
|
// NOTE: GtkSupport uses this info to figure out WHAT VERSION OF GTK to use: appindiactor1 -> GTk2, appindicator3 -> GTK3.
|
||||||
|
|
||||||
|
if (GtkSupport.FORCE_GTK2) {
|
||||||
|
// 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 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.");
|
||||||
|
}
|
||||||
|
}
|
27
src/dorkbox/systemTray/linux/jna/GThread.java
Normal file
27
src/dorkbox/systemTray/linux/jna/GThread.java
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
|
public
|
||||||
|
interface GThread extends Library {
|
||||||
|
GThread INSTANCE = (GThread) Native.loadLibrary("gthread-2.0", GThread.class);
|
||||||
|
|
||||||
|
void g_thread_init(Pointer GThreadFunctions);
|
||||||
|
}
|
174
src/dorkbox/systemTray/linux/jna/Gobject.java
Normal file
174
src/dorkbox/systemTray/linux/jna/Gobject.java
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
* 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.*;
|
||||||
|
import dorkbox.util.Keep;
|
||||||
|
import dorkbox.systemTray.linux.jna.Gtk.GdkEventButton;
|
||||||
|
|
||||||
|
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 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, 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_ref(Pointer object);
|
||||||
|
void g_object_unref(Pointer object);
|
||||||
|
void g_object_ref_sink(Pointer object);
|
||||||
|
|
||||||
|
NativeLong g_signal_connect_data(Pointer instance, String detailed_signal, Callback c_handler, Pointer data, Pointer destroy_data,
|
||||||
|
int connect_flags);
|
||||||
|
|
||||||
|
void g_signal_handler_disconnect(Pointer instance, NativeLong longAddress);
|
||||||
|
|
||||||
|
Pointer g_markup_printf_escaped(String pattern, String inputString);
|
||||||
|
}
|
140
src/dorkbox/systemTray/linux/jna/Gtk.java
Normal file
140
src/dorkbox/systemTray/linux/jna/Gtk.java
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* 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.Library;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
import dorkbox.util.Keep;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public
|
||||||
|
interface Gtk extends Library {
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
int FALSE = 0;
|
||||||
|
int TRUE = 1;
|
||||||
|
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("type", "window", "send_event", "time", "x", "y", "axes", "state", "button", "device", "x_root", "y_root");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean gtk_init_check(int argc, String[] argv);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
|
||||||
|
/** sks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already runnign */
|
||||||
|
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();
|
||||||
|
|
||||||
|
void gdk_threads_init();
|
||||||
|
|
||||||
|
// tricky business. This should only be in the dispatch thread
|
||||||
|
void gdk_threads_enter();
|
||||||
|
void gdk_threads_leave();
|
||||||
|
|
||||||
|
Pointer gtk_menu_new();
|
||||||
|
|
||||||
|
Pointer gtk_menu_item_new();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
Pointer gtk_image_menu_item_new_with_label(String label);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
Pointer gtk_bin_get_child(Pointer parent);
|
||||||
|
|
||||||
|
void gtk_label_set_text(Pointer label, String text);
|
||||||
|
|
||||||
|
void gtk_label_set_markup(Pointer label, Pointer markup);
|
||||||
|
|
||||||
|
void gtk_label_set_use_markup(Pointer label, int gboolean);
|
||||||
|
|
||||||
|
Pointer gtk_status_icon_new();
|
||||||
|
|
||||||
|
void gtk_status_icon_set_from_file(Pointer widget, String lablel);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
void gtk_status_icon_set_title(Pointer widget, String titleText);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
void gtk_menu_item_set_label(Pointer menu_item, String label);
|
||||||
|
|
||||||
|
void gtk_menu_shell_append(Pointer menu_shell, Pointer child);
|
||||||
|
|
||||||
|
void gtk_menu_shell_deactivate(Pointer menu_shell, Pointer child);
|
||||||
|
|
||||||
|
void gtk_widget_set_sensitive(Pointer widget, int sensitive);
|
||||||
|
|
||||||
|
void gtk_container_remove(Pointer menu, Pointer subItem);
|
||||||
|
|
||||||
|
void gtk_widget_show(Pointer widget);
|
||||||
|
|
||||||
|
void gtk_widget_show_all(Pointer widget);
|
||||||
|
|
||||||
|
void gtk_widget_destroy(Pointer widget);
|
||||||
|
}
|
||||||
|
|
264
src/dorkbox/systemTray/linux/jna/GtkSupport.java
Normal file
264
src/dorkbox/systemTray/linux/jna/GtkSupport.java
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
* 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 dorkbox.util.Property;
|
||||||
|
|
||||||
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
|
public
|
||||||
|
class GtkSupport {
|
||||||
|
// RE: SWT
|
||||||
|
// https://developer.gnome.org/glib/stable/glib-Deprecated-Thread-APIs.html#g-thread-init
|
||||||
|
// Since version >= 2.24, threads can only init once. Multiple calls do nothing, and we can nest gtk_main()
|
||||||
|
// in a nested loop.
|
||||||
|
|
||||||
|
private static volatile boolean started = false;
|
||||||
|
private static final ArrayBlockingQueue<Runnable> dispatchEvents = new ArrayBlockingQueue<Runnable>(256);
|
||||||
|
private static volatile Thread gtkDispatchThread;
|
||||||
|
|
||||||
|
@Property
|
||||||
|
/** Forces the system to always choose GTK2 (even when GTK3 might be available). JavaFX uses GTK2! */
|
||||||
|
public static boolean FORCE_GTK2 = false;
|
||||||
|
|
||||||
|
@Property
|
||||||
|
/**
|
||||||
|
* Forces the system to enter into JavaFX compatibility mode, where it will use GTK2 AND will not start/stop the GTK main loop.
|
||||||
|
* This is only necessary if autodetection fails
|
||||||
|
*/
|
||||||
|
public static boolean JAVAFX_COMPATIBILITY_MODE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public static
|
||||||
|
Gtk get() {
|
||||||
|
Gtk library;
|
||||||
|
|
||||||
|
boolean shouldUseGtk2 = GtkSupport.FORCE_GTK2 || JAVAFX_COMPATIBILITY_MODE;
|
||||||
|
alreadyRunningGTK = JAVAFX_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, if " +
|
||||||
|
"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;
|
||||||
|
|
||||||
|
// GTK specifies that we ONLY run from a single thread. This guarantees that.
|
||||||
|
gtkDispatchThread = new Thread() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
final Gtk gtk = Gtk.INSTANCE;
|
||||||
|
while (started) {
|
||||||
|
try {
|
||||||
|
final Runnable take = dispatchEvents.take();
|
||||||
|
|
||||||
|
gtk.gdk_threads_enter();
|
||||||
|
take.run();
|
||||||
|
gtk.gdk_threads_leave();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gtkDispatchThread.setName("GTK Event Loop");
|
||||||
|
gtkDispatchThread.start();
|
||||||
|
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
Thread 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);
|
||||||
|
gtk.gtk_init_check(0, null);
|
||||||
|
|
||||||
|
// notify our main thread to continue
|
||||||
|
blockUntilStarted.countDown();
|
||||||
|
|
||||||
|
// blocks unit quit
|
||||||
|
gtk.gtk_main();
|
||||||
|
|
||||||
|
gtk.gdk_threads_leave();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gtkUpdateThread.setName("GTK Event Loop (Native)");
|
||||||
|
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 a SINGLE THREAD. This accomplishes that.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
void dispatch(Runnable runnable) {
|
||||||
|
try {
|
||||||
|
dispatchEvents.put(runnable);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static
|
||||||
|
void shutdownGui() {
|
||||||
|
// If JavaFX/SWT is used, this is UNNECESSARY (an will break SWT/JavaFX shutdown)
|
||||||
|
if (!alreadyRunningGTK) {
|
||||||
|
Gtk.INSTANCE.gtk_main_quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
|
||||||
|
// put it in a NEW dispatch event (so that we cleanup AFTER this one is finished)
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// this should happen in a new thread
|
||||||
|
gtkDispatchThread.interrupt();
|
||||||
|
}
|
||||||
|
}).run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user