Utilities/src/dorkbox/jna/linux/GtkLoader.java

266 lines
9.7 KiB
Java

/*
* 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.jna.linux;
import org.slf4j.LoggerFactory;
import com.sun.jna.Function;
import com.sun.jna.NativeLibrary;
import dorkbox.jna.JnaHelper;
import dorkbox.jna.rendering.RenderProvider;
import dorkbox.os.OS;
/**
* Bindings for GTK+ 2. Bindings that are exclusively for GTK+ 3 are in that respective class
* <p>
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
@SuppressWarnings({"Duplicates", "SameParameterValue", "WeakerAccess"})
public
class GtkLoader {
/**
* Gets the version number.
*/
public static
String getVersion() {
return "1.29";
}
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-x11-2.0.so.0 | grep gtk
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
// objdump -T /usr/local/lib/libgtk-3.so.0 | grep 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 -> appindicator1, GTK3 -> appindicator3
static boolean isGtk2;
static boolean isGtk3;
static boolean isLoaded;
static boolean alreadyRunningGTK;
static Function gtk_status_icon_position_menu = null;
// use GtkCheck for a safe accessor of these
static int MAJOR;
static int MINOR;
static int MICRO;
private static final NativeLibrary libraryReference;
/*
* We can have GTK v3 or v2.
*
* Observations:
* JavaFX (java8) 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.
*/
static {
// Add this project to the updates system, which verifies this class + UUID + version information
dorkbox.updates.Updates.INSTANCE.add(GtkLoader.class, "d712ab5998d742a1bc96ca2f7be197ce", getVersion());
boolean forceGtk2 = GtkEventDispatch.FORCE_GTK2;
// prefer GTK3 and force GTK2 are mutually exclusive
boolean preferGtk3 = !forceGtk2 && GtkEventDispatch.PREFER_GTK3;
boolean _isGtk2 = false;
boolean _isLoaded = false;
boolean _alreadyRunningGTK = false;
int major = 0;
int minor = 0;
int micro = 0;
boolean shouldLoadGtk = !(OS.INSTANCE.isWindows() || OS.INSTANCE.isMacOsX());
if (!shouldLoadGtk) {
_isLoaded = true;
}
// we can force the system to use the swing indicator, which WORKS, but doesn't support transparency in the icon. However, there
// are certain GTK functions we might want to use (even if we are Swing or AWT), so we load GTK anyways...
// in some cases, we ALWAYS want to try GTK2 first
String gtk2LibName = "gtk-x11-2.0";
String gtk3LibName = "libgtk-3.so.0";
NativeLibrary library = null;
if (!_isLoaded && (forceGtk2 || !preferGtk3)) {
try {
library = JnaHelper.register(gtk2LibName, Gtk2.class);
_isGtk2 = true;
major = library.getGlobalVariableAddress("gtk_major_version").getInt(0);
minor = library.getGlobalVariableAddress("gtk_minor_version").getInt(0);
micro = library.getGlobalVariableAddress("gtk_micro_version").getInt(0);
gtk_status_icon_position_menu = library.getFunction( "gtk_status_icon_position_menu");
Function gtk_main_level = library.getFunction("gtk_main_level");
// 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 started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = gtk_main_level.invokeInt(null) != 0;
_isLoaded = true;
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
if (library != null) {
library.dispose();
}
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("Error loading library", e);
}
}
if (forceGtk2) {
// don't try anything else if we forced GTK2
_isLoaded = true;
}
}
// now for the defaults...
// start with version 3
if (!_isLoaded) {
try {
// have to get the version information FIRST, because there are some really old GTK3 libraries out there.
library = JnaHelper.register(gtk3LibName, Gtk3VersionInfo.class);
Gtk3VersionInfo version = new Gtk3VersionInfo();
major = version.gtk_get_major_version();
minor = version.gtk_get_minor_version();
micro = version.gtk_get_micro_version();
library.dispose();
library = null;
library = JnaHelper.register(gtk3LibName, Gtk3.class);
gtk_status_icon_position_menu = library.getFunction( "gtk_status_icon_position_menu");
Function gtk_main_level = library.getFunction("gtk_main_level");
// 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 started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = gtk_main_level.invokeInt(null) != 0;
_isLoaded = true;
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("GTK: {}", gtk3LibName);
}
} catch (Throwable e) {
if (library != null) {
library.dispose();
}
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("Error loading library.", e);
}
}
}
// now version 2
if (!_isLoaded) {
try {
library = JnaHelper.register(gtk2LibName, Gtk2.class);
_isGtk2 = true;
major = library.getGlobalVariableAddress("gtk_major_version").getInt(0);
minor = library.getGlobalVariableAddress("gtk_minor_version").getInt(0);
micro = library.getGlobalVariableAddress("gtk_micro_version").getInt(0);
gtk_status_icon_position_menu = Function.getFunction(gtk2LibName, "gtk_status_icon_position_menu");
Function gtk_main_level = library.getFunction("gtk_main_level");
// 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 started GTK -- so we DO NOT NEED TO.
_alreadyRunningGTK = gtk_main_level.invokeInt(null) != 0;
_isLoaded = true;
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("GTK: {}", gtk2LibName);
}
} catch (Throwable e) {
if (library != null) {
library.dispose();
}
if (GtkEventDispatch.DEBUG) {
LoggerFactory.getLogger(GtkLoader.class).debug("Error loading library", e);
}
}
}
if (shouldLoadGtk && _isLoaded) {
isLoaded = true;
// depending on how the system is initialized, SWT may, or may not, have the gtk_main loop running.
// It will ALWAYS EVENTUALLY run, so we do not want to run our own GTK event loop. It is also incompatible with our event loop
_alreadyRunningGTK |= RenderProvider.alreadyRunning();
alreadyRunningGTK = _alreadyRunningGTK;
isGtk2 = _isGtk2;
isGtk3 = !_isGtk2;
MAJOR = major;
MINOR = minor;
MICRO = micro;
}
else {
isLoaded = false;
alreadyRunningGTK = false;
isGtk2 = false;
isGtk3 = false;
MAJOR = 0;
MINOR = 0;
MICRO = 0;
}
// This is so that queries for the GTK version DO NOT try to load GTK
GtkCheck.isGtk2 = isGtk2;
GtkCheck.isGtk3 = isGtk3;
GtkCheck.isGtkLoaded = isLoaded;
GtkCheck.MAJOR = MAJOR;
GtkCheck.MINOR = MINOR;
GtkCheck.MICRO = MICRO;
// load any GTK version specific methods
if (isGtk3) {
Gtk3.loadMethods(library);
}
libraryReference = library;
if (shouldLoadGtk) {
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.");
}
}
}
}