Moved JNA libraries into SystemTray project (it made no sense to have them inside the utils project)

This commit is contained in:
nathan 2016-02-14 20:22:55 +01:00
parent db19374bfc
commit eb8092677d
11 changed files with 981 additions and 176 deletions

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

View 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();
}
});
}
}