Moved GTK Theme methods to it's own class (it was a lot). Suppressed GTK
warnings during startup.
This commit is contained in:
parent
9cfbd7689b
commit
15029d0256
|
@ -1,5 +1,6 @@
|
||||||
package dorkbox.systemTray.jna.linux;
|
package dorkbox.systemTray.jna.linux;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -13,11 +14,48 @@ import com.sun.jna.Structure;
|
||||||
public
|
public
|
||||||
class GdkColor extends Structure {
|
class GdkColor extends Structure {
|
||||||
|
|
||||||
|
/* The color type.
|
||||||
|
* A color consists of red, green and blue values in the
|
||||||
|
* range 0-65535 and a pixel value. The pixel value is highly
|
||||||
|
* dependent on the depth and colormap which this color will
|
||||||
|
* be used to draw into. Therefore, sharing colors between
|
||||||
|
* colormaps is a bad idea.
|
||||||
|
*/
|
||||||
public int pixel;
|
public int pixel;
|
||||||
public short red;
|
public short red;
|
||||||
public short green;
|
public short green;
|
||||||
public short blue;
|
public short blue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255
|
||||||
|
*/
|
||||||
|
private static int convert(int inputColor) {
|
||||||
|
return (inputColor & 0x0000FFFF >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int red() {
|
||||||
|
return convert(red);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int green() {
|
||||||
|
return convert(green);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int blue() {
|
||||||
|
return convert(blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
Color getColor() {
|
||||||
|
return new Color(red(), green(), blue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
String toString() {
|
||||||
|
return "[r=" + red() + ",g=" + green() + ",b=" + blue() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected
|
protected
|
||||||
List<String> getFieldOrder() {
|
List<String> getFieldOrder() {
|
||||||
|
|
|
@ -17,25 +17,17 @@ package dorkbox.systemTray.jna.linux;
|
||||||
|
|
||||||
import static dorkbox.systemTray.SystemTray.logger;
|
import static dorkbox.systemTray.SystemTray.logger;
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import com.sun.jna.Function;
|
import com.sun.jna.Function;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.ptr.PointerByReference;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Entry;
|
import dorkbox.systemTray.Entry;
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.Tray;
|
|
||||||
import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray;
|
|
||||||
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
|
||||||
import dorkbox.systemTray.util.JavaFX;
|
import dorkbox.systemTray.util.JavaFX;
|
||||||
import dorkbox.systemTray.util.Swt;
|
import dorkbox.systemTray.util.Swt;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
|
@ -43,11 +35,7 @@ import dorkbox.util.jna.JnaHelper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bindings for GTK+ 2. Bindings that are exclusively for GTK+ 3 are in that respective class
|
* bindings for GTK+ 2. Bindings that are exclusively for GTK+ 3 are in that respective class
|
||||||
*
|
* <p>
|
||||||
* note: gtk2/3 loading is SENSITIVE, and which AppIndicator symbols are loaded depends on this being loaded first
|
|
||||||
* Additionally, gtk3 has deprecated some methods -- which, fortunately for us, means it will be another 25 years before they are
|
|
||||||
* removed; forcing us to have completely separate gtk2/3 bindings.
|
|
||||||
*
|
|
||||||
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"Duplicates", "SameParameterValue", "DanglingJavadoc", "DeprecatedIsStillUsed"})
|
@SuppressWarnings({"Duplicates", "SameParameterValue", "DanglingJavadoc", "DeprecatedIsStillUsed"})
|
||||||
|
@ -56,18 +44,50 @@ class Gtk {
|
||||||
// For funsies to look at, SyncThing did a LOT of work on compatibility in python (unfortunate for us, but interesting).
|
// 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
|
// https://github.com/syncthing/syncthing-gtk/blob/b7a3bc00e3bb6d62365ae62b5395370f3dcc7f55/syncthing_gtk/statusicon.py
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static class State {
|
||||||
|
public static final int NORMAL = 0x0; // normal state.
|
||||||
|
public static final int ACTIVE = 0x1; // pressed-in or activated; e.g. buttons while the mouse button is held down.
|
||||||
|
public static final int PRELIGHT = 0x2; // color when the mouse is over an activatable widget.
|
||||||
|
public static final int SELECTED = 0x3; // color when something is selected, e.g. when selecting some text to cut/copy.
|
||||||
|
public static final int INSENSITIVE = 0x4; // color when the mouse is over an activatable widget.
|
||||||
|
|
||||||
|
public static final int FLAG_NORMAL = 0;
|
||||||
|
public static final int FLAG_ACTIVE = 1 << 0;
|
||||||
|
public static final int FLAG_PRELIGHT = 1 << 1;
|
||||||
|
public static final int FLAG_SELECTED = 1 << 2;
|
||||||
|
public static final int FLAG_INSENSITIVE = 1 << 3;
|
||||||
|
public static final int FLAG_INCONSISTENT = 1 << 4;
|
||||||
|
public static final int FLAG_FOCUSED = 1 << 5;
|
||||||
|
public static final int FLAG_BACKDROP = 1 << 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// NOTE: AppIndicator uses this info to figure out WHAT VERSION OF appindicator to use: GTK2 -> appindicator1, GTK3 -> appindicator3
|
// NOTE: AppIndicator uses this info to figure out WHAT VERSION OF appindicator to use: GTK2 -> appindicator1, GTK3 -> appindicator3
|
||||||
public static final boolean isGtk2;
|
public static final boolean isGtk2;
|
||||||
public static final boolean isGtk3;
|
public static final boolean isGtk3;
|
||||||
public static final boolean isLoaded;
|
public static final boolean isLoaded;
|
||||||
|
|
||||||
public static Function gtk_status_icon_position_menu = null;
|
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public static final int FALSE = 0;
|
||||||
|
public static final int TRUE = 1;
|
||||||
private static final boolean alreadyRunningGTK;
|
private static final boolean alreadyRunningGTK;
|
||||||
|
|
||||||
// when debugging the EDT, we need a longer timeout.
|
// when debugging the EDT, we need a longer timeout.
|
||||||
private static final boolean debugEDT = true;
|
private static final boolean debugEDT = true;
|
||||||
|
// timeout is in seconds
|
||||||
|
private static final int TIMEOUT = debugEDT ? 10000000 : 2;
|
||||||
|
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
||||||
|
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||||
|
private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>();
|
||||||
|
public static Function gtk_status_icon_position_menu = null;
|
||||||
// This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread
|
// This is required because the EDT needs to have it's own value for this boolean, that is a different value than the main thread
|
||||||
private static ThreadLocal<Boolean> isDispatch = new ThreadLocal<Boolean>() {
|
private static ThreadLocal<Boolean> isDispatch = new ThreadLocal<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -76,14 +96,9 @@ class Gtk {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
private static volatile boolean started = false;
|
||||||
// timeout is in seconds
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private static final int TIMEOUT = debugEDT ? 10000000 : 2;
|
private static Thread gtkUpdateThread = null;
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We can have GTK v3 or v2.
|
* We can have GTK v3 or v2.
|
||||||
|
@ -217,20 +232,6 @@ class Gtk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static volatile boolean started = false;
|
|
||||||
|
|
||||||
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
|
||||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
|
||||||
private static final LinkedList<Object> gtkCallbacks = new LinkedList<Object>();
|
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
|
||||||
private static Thread gtkUpdateThread = null;
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public static final int FALSE = 0;
|
|
||||||
public static final int TRUE = 1;
|
|
||||||
|
|
||||||
|
|
||||||
public static
|
public static
|
||||||
void startGui() {
|
void startGui() {
|
||||||
// only permit one startup per JVM instance
|
// only permit one startup per JVM instance
|
||||||
|
@ -247,10 +248,15 @@ class Gtk {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
Glib.GLogFunc orig = null;
|
||||||
if (SystemTray.DEBUG) {
|
if (SystemTray.DEBUG) {
|
||||||
logger.debug("Running GTK Native Event Loop");
|
logger.debug("Running GTK Native Event Loop");
|
||||||
|
} else {
|
||||||
|
// NOTE: This can output warnings, so we suppress them
|
||||||
|
orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// prep for the event loop.
|
// prep for the event loop.
|
||||||
// GThread.g_thread_init(null); would be needed for g_idle_add()
|
// GThread.g_thread_init(null); would be needed for g_idle_add()
|
||||||
|
|
||||||
|
@ -263,6 +269,10 @@ class Gtk {
|
||||||
|
|
||||||
// gdk_threads_enter(); would be needed for g_idle_add()
|
// gdk_threads_enter(); would be needed for g_idle_add()
|
||||||
|
|
||||||
|
if (orig != null) {
|
||||||
|
Glib.g_log_set_default_handler(orig, null);
|
||||||
|
}
|
||||||
|
|
||||||
// blocks unit quit
|
// blocks unit quit
|
||||||
gtk_main();
|
gtk_main();
|
||||||
|
|
||||||
|
@ -277,6 +287,20 @@ class Gtk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky
|
||||||
|
* enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
|
||||||
|
*/
|
||||||
|
private static native
|
||||||
|
boolean gtk_init_check(int argc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the main loop until gtk_main_quit() is called. You can nest calls to gtk_main(). In that case gtk_main_quit() will make the
|
||||||
|
* innermost invocation of the main loop return.
|
||||||
|
*/
|
||||||
|
private static native
|
||||||
|
void gtk_main();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Waits for the GUI to finish loading
|
* Waits for the GUI to finish loading
|
||||||
*/
|
*/
|
||||||
|
@ -432,6 +456,33 @@ class Gtk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a function to be called whenever there are no higher priority events pending. If the function returns FALSE it is automatically
|
||||||
|
* removed from the list of event sources and will not be called again.
|
||||||
|
* <p>
|
||||||
|
* This variant of g_idle_add_full() calls function with the GDK lock held. It can be thought of a MT-safe version for GTK+ widgets
|
||||||
|
* for the following use case, where you have to worry about idle_callback() running in thread A and accessing self after it has
|
||||||
|
* been finalized in thread B.
|
||||||
|
*/
|
||||||
|
public static native
|
||||||
|
int gdk_threads_add_idle_full(int priority, FuncCallback function, Pointer data, Pointer notify);
|
||||||
|
|
||||||
|
public static
|
||||||
|
void shutdownGui() {
|
||||||
|
dispatchAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown)
|
||||||
|
if (!alreadyRunningGTK) {
|
||||||
|
gtk_main_quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
started = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static
|
public static
|
||||||
void dispatchAndWait(final Runnable runnable) {
|
void dispatchAndWait(final Runnable runnable) {
|
||||||
if (isDispatch.get()) {
|
if (isDispatch.get()) {
|
||||||
|
@ -475,21 +526,12 @@ class Gtk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static
|
/**
|
||||||
void shutdownGui() {
|
* Makes the innermost invocation of the main loop return when it regains control. ONLY CALL FROM THE GtkSupport class, UNLESS you know
|
||||||
dispatchAndWait(new Runnable() {
|
* what you're doing!
|
||||||
@Override
|
*/
|
||||||
public
|
private static native
|
||||||
void run() {
|
void gtk_main_quit();
|
||||||
// If JavaFX/SWT is used, this is UNNECESSARY (and will break SWT/JavaFX shutdown)
|
|
||||||
if (!alreadyRunningGTK) {
|
|
||||||
gtk_main_quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
started = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* required to properly setup the dispatch flag when using native menus
|
* required to properly setup the dispatch flag when using native menus
|
||||||
|
@ -514,44 +556,12 @@ class Gtk {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This would NORMALLY have a 2nd argument that is a String[] -- however JNA direct-mapping DOES NOT support this. We are lucky
|
|
||||||
* enough that we just pass 'null' as the second argument, therefore, we don't have to define that parameter here.
|
|
||||||
*/
|
|
||||||
private static native
|
|
||||||
boolean gtk_init_check(int argc);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs the main loop until gtk_main_quit() is called. You can nest calls to gtk_main(). In that case gtk_main_quit() will make the
|
|
||||||
* innermost invocation of the main loop return.
|
|
||||||
*/
|
|
||||||
private static native
|
|
||||||
void gtk_main();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running
|
* aks for the current nesting level of the main loop. Useful to determine (at startup) if GTK is already running
|
||||||
*/
|
*/
|
||||||
private static native
|
private static native
|
||||||
int gtk_main_level();
|
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!
|
|
||||||
*/
|
|
||||||
private static native
|
|
||||||
void gtk_main_quit();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a function to be called whenever there are no higher priority events pending. If the function returns FALSE it is automatically
|
|
||||||
* removed from the list of event sources and will not be called again.
|
|
||||||
* <p>
|
|
||||||
* This variant of g_idle_add_full() calls function with the GDK lock held. It can be thought of a MT-safe version for GTK+ widgets
|
|
||||||
* for the following use case, where you have to worry about idle_callback() running in thread A and accessing self after it has
|
|
||||||
* been finalized in thread B.
|
|
||||||
*/
|
|
||||||
public static native
|
|
||||||
int gdk_threads_add_idle_full(int priority, FuncCallback function, Pointer data, Pointer notify);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new GtkMenu
|
* Creates a new GtkMenu
|
||||||
*/
|
*/
|
||||||
|
@ -592,9 +602,9 @@ class Gtk {
|
||||||
/**
|
/**
|
||||||
* Creates a new GtkImageMenuItem containing a label. The label will be created using gtk_label_new_with_mnemonic(), so underscores
|
* Creates a new GtkImageMenuItem containing a label. The label will be created using gtk_label_new_with_mnemonic(), so underscores
|
||||||
* in label indicate the mnemonic for the menu item.
|
* in label indicate the mnemonic for the menu item.
|
||||||
*
|
* <p>
|
||||||
* uses '_' to define which key is the mnemonic
|
* uses '_' to define which key is the mnemonic
|
||||||
*
|
* <p>
|
||||||
* gtk_image_menu_item_new_with_mnemonic has been deprecated since version 3.10 and should not be used in newly-written code.
|
* gtk_image_menu_item_new_with_mnemonic has been deprecated since version 3.10 and should not be used in newly-written code.
|
||||||
* NOTE: Use gtk_menu_item_new_with_mnemonic() instead.
|
* NOTE: Use gtk_menu_item_new_with_mnemonic() instead.
|
||||||
*/
|
*/
|
||||||
|
@ -608,7 +618,7 @@ class Gtk {
|
||||||
/**
|
/**
|
||||||
* Sets the image of image_menu_item to the given widget. Note that it depends on the show-menu-images setting whether the image
|
* Sets the image of image_menu_item to the given widget. Note that it depends on the show-menu-images setting whether the image
|
||||||
* will be displayed or not.
|
* will be displayed or not.
|
||||||
*
|
* <p>
|
||||||
* gtk_image_menu_item_set_image has been deprecated since version 3.10 and should not be used in newly-written code.
|
* gtk_image_menu_item_set_image has been deprecated since version 3.10 and should not be used in newly-written code.
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -627,7 +637,7 @@ class Gtk {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an empty status icon object.
|
* Creates an empty status icon object.
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_new has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_new has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
|
@ -639,16 +649,17 @@ class Gtk {
|
||||||
* Gets the size in pixels that is available for the image. Stock icons and named icons adapt their size automatically if the size of
|
* Gets the size in pixels that is available for the image. Stock icons and named icons adapt their size automatically if the size of
|
||||||
* the notification area changes. For other storage types, the size-changed signal can be used to react to size changes.
|
* the notification area changes. For other storage types, the size-changed signal can be used to react to size changes.
|
||||||
* Note that the returned size is only meaningful while the status icon is embedded (see gtk_status_icon_is_embedded()).
|
* Note that the returned size is only meaningful while the status icon is embedded (see gtk_status_icon_is_embedded()).
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_get_size has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_get_size has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static native int gtk_status_icon_get_size(Pointer status_icon);
|
public static native
|
||||||
|
int gtk_status_icon_get_size(Pointer status_icon);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes status_icon display the file filename . See gtk_status_icon_new_from_file() for details.
|
* Makes status_icon display the file filename . See gtk_status_icon_new_from_file() for details.
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_set_from_file has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_set_from_file has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
|
@ -658,7 +669,7 @@ class Gtk {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows or hides a status icon.
|
* Shows or hides a status icon.
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_set_visible has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_set_visible has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
|
@ -676,14 +687,13 @@ class Gtk {
|
||||||
*
|
*
|
||||||
* gtk_status_icon_set_tooltip_text has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_set_tooltip_text has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/ public static native
|
||||||
public static native
|
|
||||||
void gtk_status_icon_set_tooltip_text(Pointer widget, String tooltipText);
|
void gtk_status_icon_set_tooltip_text(Pointer widget, String tooltipText);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the title of this tray icon. This should be a short, human-readable, localized string describing the tray icon. It may be used
|
* Sets the title of this tray icon. This should be a short, human-readable, localized string describing the tray icon. It may be used
|
||||||
* by tools like screen readers to render the tray icon.
|
* by tools like screen readers to render the tray icon.
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_set_title has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_set_title has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
|
@ -694,7 +704,7 @@ class Gtk {
|
||||||
/**
|
/**
|
||||||
* Sets the name of this tray icon. This should be a string identifying this icon. It is may be used for sorting the icons in the
|
* Sets the name of this tray icon. This should be a string identifying this icon. It is may be used for sorting the icons in the
|
||||||
* tray and will not be shown to the user.
|
* tray and will not be shown to the user.
|
||||||
*
|
* <p>
|
||||||
* gtk_status_icon_set_name has been deprecated since version 3.14 and should not be used in newly-written code.
|
* gtk_status_icon_set_name has been deprecated since version 3.14 and should not be used in newly-written code.
|
||||||
* Use notifications
|
* Use notifications
|
||||||
*/
|
*/
|
||||||
|
@ -704,7 +714,7 @@ class Gtk {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a menu and makes it available for selection.
|
* Displays a menu and makes it available for selection.
|
||||||
*
|
* <p>
|
||||||
* gtk_menu_popup has been deprecated since version 3.22 and should not be used in newly-written code.
|
* gtk_menu_popup has been deprecated since version 3.22 and should not be used in newly-written code.
|
||||||
* NOTE: Please use gtk_menu_popup_at_widget(), gtk_menu_popup_at_pointer(). or gtk_menu_popup_at_rect() instead
|
* NOTE: Please use gtk_menu_popup_at_widget(), gtk_menu_popup_at_pointer(). or gtk_menu_popup_at_rect() instead
|
||||||
*/
|
*/
|
||||||
|
@ -777,18 +787,27 @@ class Gtk {
|
||||||
public static native
|
public static native
|
||||||
Pointer gtk_settings_get_default();
|
Pointer gtk_settings_get_default();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply an accessor function that returns @widget->style.
|
||||||
|
*/
|
||||||
|
public static native
|
||||||
|
GtkStyle.ByReference gtk_widget_get_style(Pointer widget);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all matching RC styles for a given widget, composites them together, and then creates a GtkStyle representing the composite
|
* Finds all matching RC styles for a given widget, composites them together, and then creates a GtkStyle representing the composite
|
||||||
* appearance. (GTK+ actually keeps a cache of previously created styles, so a new style may not be created.)
|
* appearance. (GTK+ actually keeps a cache of previously created styles, so a new style may not be created.)
|
||||||
*/
|
*/
|
||||||
public static native Pointer gtk_rc_get_style(Pointer widget);
|
public static native
|
||||||
|
Pointer gtk_rc_get_style(Pointer widget);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a toplevel container widget that is used to retrieve snapshots of widgets without showing them on the screen.
|
* Creates a toplevel container widget that is used to retrieve snapshots of widgets without showing them on the screen.
|
||||||
*
|
*
|
||||||
* @since 2.20
|
* @since 2.20
|
||||||
*/
|
*/
|
||||||
public static native Pointer gtk_offscreen_window_new();
|
public static native
|
||||||
|
Pointer gtk_offscreen_window_new();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Looks up color_name in the style’s logical color mappings, filling in color and returning TRUE if found, otherwise returning
|
* Looks up color_name in the style’s logical color mappings, filling in color and returning TRUE if found, otherwise returning
|
||||||
|
@ -796,8 +815,8 @@ class Gtk {
|
||||||
*
|
*
|
||||||
* @since 2.10
|
* @since 2.10
|
||||||
*/
|
*/
|
||||||
public static native boolean gtk_style_lookup_color(Pointer widgetStyle, String color_name, Pointer color);
|
public static native
|
||||||
|
boolean gtk_style_lookup_color(Pointer widgetStyle, String color_name, Pointer color);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds widget to container . Typically used for simple containers such as GtkWindow, GtkFrame, or GtkButton; for more complicated
|
* Adds widget to container . Typically used for simple containers such as GtkWindow, GtkFrame, or GtkButton; for more complicated
|
||||||
|
@ -805,576 +824,7 @@ class Gtk {
|
||||||
* consider functions such as gtk_box_pack_start() and gtk_table_attach() as an alternative to gtk_container_add() in those cases.
|
* consider functions such as gtk_box_pack_start() and gtk_table_attach() as an alternative to gtk_container_add() in those cases.
|
||||||
* A widget may be added to only one container at a time; you can't place the same widget inside two different containers.
|
* A widget may be added to only one container at a time; you can't place the same widget inside two different containers.
|
||||||
*/
|
*/
|
||||||
public static native void gtk_container_add(Pointer offscreen, Pointer widget);
|
public static native
|
||||||
|
void gtk_container_add(Pointer offscreen, Pointer widget);
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the widget color of text for the current theme, or black. It is important that this is called AFTER GTK has been initialized.
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
Color getCurrentThemeTextColor() {
|
|
||||||
/*
|
|
||||||
* There are several 'directives' to change the attributes of a widget.
|
|
||||||
* fg - Sets the foreground color of a widget.
|
|
||||||
* bg - Sets the background color of a widget.
|
|
||||||
* text - Sets the foreground color for widgets that have editable text.
|
|
||||||
* base - Sets the background color for widgets that have editable text.
|
|
||||||
* bg_pixmap - Sets the background of a widget to a tiled pixmap.
|
|
||||||
* font_name - Sets the font to be used with the given widget.
|
|
||||||
* xthickness - Sets the left and right border width. This is not what you might think; it sets the borders of children(?)
|
|
||||||
* ythickness - similar to above but for the top and bottom.
|
|
||||||
*
|
|
||||||
* There are several states a widget can be in, and you can set different colors, pixmaps and fonts for each state. These states are:
|
|
||||||
* NORMAL - The normal state of a widget. Ie the mouse is not over it, and it is not being pressed, etc.
|
|
||||||
* PRELIGHT - When the mouse is over top of the widget, colors defined using this state will be in effect.
|
|
||||||
* ACTIVE - When the widget is pressed or clicked it will be active, and the attributes assigned by this tag will be in effect.
|
|
||||||
* INSENSITIVE - This is the state when a widget is 'greyed out'. It is not active, and cannot be clicked on.
|
|
||||||
* SELECTED - When an object is selected, it takes these attributes.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: when getting CSS, we redirect STDERR to null, so that we won't spam the console if there are parse errors.
|
|
||||||
// this is a horrid hack, but the only way to work around the errors we have no control over. The parse errors, if bad enough
|
|
||||||
// just mean that we are unable to get the CSS as we want.
|
|
||||||
|
|
||||||
// these methods are from most accurate (but limited in support) to compatible across Linux OSes.. Strangely enough, GTK makes
|
|
||||||
// this information insanely difficult to get.
|
|
||||||
final AtomicReference<Color> color = new AtomicReference<Color>(null);
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// see if we can get the info via CSS properties (> GTK+ 3.2)
|
|
||||||
if (Gtk.isGtk3) {
|
|
||||||
Color c = getFromCss();
|
|
||||||
if (c != null) {
|
|
||||||
System.err.println("Got from CSS");
|
|
||||||
color.set(c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we got here because it's not possible to get the info via raw-CSS...
|
|
||||||
|
|
||||||
|
|
||||||
// try to get via the color scheme
|
|
||||||
Color c = getFromColorScheme();
|
|
||||||
if (c != null) {
|
|
||||||
System.err.println("Got from color scheme");
|
|
||||||
color.set(c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// the following methods all require an offscreen widget to get the style information from
|
|
||||||
|
|
||||||
|
|
||||||
// create an off-screen widget (don't forget to destroy everything!)
|
|
||||||
Pointer offscreen = Gtk.gtk_offscreen_window_new();
|
|
||||||
final Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("asd");
|
|
||||||
Gtk.gtk_container_add (offscreen, item);
|
|
||||||
Gtk.gtk_widget_show_all(item);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Try to get via RC style... Sometimes this works (sometimes it does not...)
|
|
||||||
{
|
|
||||||
Pointer style = Gtk.gtk_rc_get_style(item);
|
|
||||||
|
|
||||||
GdkColor gdkColor = new GdkColor();
|
|
||||||
boolean success = Gtk.gtk_style_lookup_color(style, "menu_fg_color", gdkColor.getPointer());
|
|
||||||
if (!success) {
|
|
||||||
success = Gtk.gtk_style_lookup_color(style, "text_color", gdkColor.getPointer());
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
success = Gtk.gtk_style_lookup_color(style, "theme_text_color", gdkColor.getPointer());
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
// Have to convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255
|
|
||||||
int red = gdkColor.red & 0x0000FFFF;
|
|
||||||
int green = gdkColor.green & 0x0000FFFF;
|
|
||||||
int blue = gdkColor.blue & 0x0000FFFF;
|
|
||||||
|
|
||||||
red = (red >> 8) & 0xFF;
|
|
||||||
green = (green >> 8) & 0xFF;
|
|
||||||
blue = (blue >> 8) & 0xFF;
|
|
||||||
|
|
||||||
color.set(new Color(red, green, blue));
|
|
||||||
|
|
||||||
Gtk.gtk_widget_destroy(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Gtk.isGtk3) {
|
|
||||||
Pointer context = Gtk3.gtk_widget_get_style_context(item);
|
|
||||||
int state = Gtk3.gtk_style_context_get_state(context);
|
|
||||||
|
|
||||||
GdkRGBAColor gdkColor = new GdkRGBAColor();
|
|
||||||
boolean success = Gtk3.gtk_style_context_lookup_color(context, "fg_color", gdkColor.getPointer());
|
|
||||||
if (!success) {
|
|
||||||
success = Gtk3.gtk_style_context_lookup_color(context, "text_color", gdkColor.getPointer());
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
success = Gtk3.gtk_style_context_lookup_color(context, "menu_fg_color", gdkColor.getPointer());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
success = Gtk3.gtk_style_context_lookup_color(context, "color", gdkColor.getPointer());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
color.set(new Color((float) gdkColor.red, (float) gdkColor.green, (float) gdkColor.blue, (float) gdkColor.alpha));
|
|
||||||
} else {
|
|
||||||
// fall back in case nothing else works
|
|
||||||
Gtk3.gtk_style_context_get_color(context, state, gdkColor.getPointer());
|
|
||||||
if ((gdkColor.red == 0.0 && gdkColor.green == 0.0 && gdkColor.blue == 0.0) || gdkColor.alpha == 0.0) {
|
|
||||||
// have nothing here, check something else?
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// if we have something that is not all 0's
|
|
||||||
color.set(new Color((float) gdkColor.red, (float) gdkColor.green, (float) gdkColor.blue, (float) gdkColor.alpha));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Gtk.gtk_widget_destroy(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
Color color1 = color.get();
|
|
||||||
if (color1 != null) {
|
|
||||||
System.err.println("COLOR: " + color1);
|
|
||||||
return color1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemTray.logger.error("Unable to determine the text color in use by your system. Please create an issue and include your " +
|
|
||||||
"full OS configuration and desktop environment (including theme and color variant) details.");
|
|
||||||
|
|
||||||
// who knows WHAT the color is supposed to be. This is just a "best guess" default value.
|
|
||||||
return Color.BLACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the color we are interested in via raw CSS parsing. This is specifically to get the color of the text of the
|
|
||||||
* appindicator/gtk-status-icon menu.
|
|
||||||
*
|
|
||||||
* @return the color string, parsed from CSS/
|
|
||||||
*/
|
|
||||||
private static Color getFromCss() {
|
|
||||||
String css = getGtkThemeCss();
|
|
||||||
if (css != null) {
|
|
||||||
// System.err.println("css: " + css);
|
|
||||||
|
|
||||||
String[] nodes;
|
|
||||||
Tray tray = (Tray) SystemTray.get()
|
|
||||||
.getMenu();
|
|
||||||
|
|
||||||
|
|
||||||
// we care about the following CSS head nodes, and account for multiple versions, in order of preference.
|
|
||||||
if (tray instanceof _GtkStatusIconNativeTray) {
|
|
||||||
nodes = new String[] {"GtkPopover", "gnome-panel-menu-bar", "unity-panel", "PanelMenuBar", ".check"};
|
|
||||||
}
|
|
||||||
else if (tray instanceof _AppIndicatorNativeTray) {
|
|
||||||
nodes = new String[] {"GtkPopover", "unity-panel", "gnome-panel-menu-bar", "PanelMenuBar", ".check"};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// not supported for other types
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// collect a list of all of the sections that have what we are interested in
|
|
||||||
List<String> sections = new ArrayList<String>();
|
|
||||||
|
|
||||||
String colorString = null;
|
|
||||||
|
|
||||||
// now check the css nodes to see if they contain a combination of what we are looking for.
|
|
||||||
colorCheck:
|
|
||||||
for (String node : nodes) {
|
|
||||||
int i = 0;
|
|
||||||
while (i != -1) {
|
|
||||||
i = css.indexOf(node, i);
|
|
||||||
if (i > -1) {
|
|
||||||
int endOfNodeLabels = css.indexOf("{", i);
|
|
||||||
int endOfSection = css.indexOf("}", endOfNodeLabels + 1) + 1;
|
|
||||||
int endOfSectionTest = css.indexOf("}", i) + 1;
|
|
||||||
|
|
||||||
// this makes sure that weird parsing errors don't happen as a result of node keywords appearing in node sections
|
|
||||||
if (endOfSection != endOfSectionTest) {
|
|
||||||
// advance the index
|
|
||||||
i = endOfSection;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
String nodeLabel = css.substring(i, endOfNodeLabels);
|
|
||||||
String nodeSection = css.substring(endOfNodeLabels, endOfSection);
|
|
||||||
|
|
||||||
// if (nodeSection.contains("menu_fg_color")) {
|
|
||||||
// System.err.println(nodeSection);
|
|
||||||
// }
|
|
||||||
|
|
||||||
int j = nodeSection.indexOf(" color");
|
|
||||||
if (j > -1) {
|
|
||||||
sections.add(nodeLabel + " " + nodeSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance the index
|
|
||||||
i = endOfSection;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// for (String section : sections) {
|
|
||||||
// System.err.println("--------------");
|
|
||||||
// System.err.println(section);
|
|
||||||
// System.err.println("--------------");
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!sections.isEmpty()) {
|
|
||||||
String section = sections.get(0);
|
|
||||||
int start = section.indexOf("{");
|
|
||||||
int colorIndex = section.indexOf(" color", start);
|
|
||||||
|
|
||||||
int startOfColorDef = section.indexOf(":", colorIndex) + 1;
|
|
||||||
int endOfColorDef = section.indexOf(";", startOfColorDef);
|
|
||||||
colorString = section.substring(startOfColorDef, endOfColorDef)
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// hopefully we found it.
|
|
||||||
if (colorString != null) {
|
|
||||||
if (colorString.startsWith("@")) {
|
|
||||||
// it's a color definition
|
|
||||||
colorString = colorString.substring(1);
|
|
||||||
|
|
||||||
// have to setup the "define color" section
|
|
||||||
String colorDefine = "@define-color";
|
|
||||||
int start = css.indexOf(colorDefine);
|
|
||||||
int end = css.lastIndexOf(colorDefine);
|
|
||||||
end = css.lastIndexOf(";", end) + 1; // include the ;
|
|
||||||
String colorDefines = css.substring(start, end);
|
|
||||||
|
|
||||||
// System.err.println("+++++++++++++++++++++++");
|
|
||||||
// System.err.println(colorDefines);
|
|
||||||
// System.err.println("+++++++++++++++++++++++");
|
|
||||||
|
|
||||||
// since it's a color definition, it will start a very specific way.
|
|
||||||
String newColorString = colorDefine + " " + colorString;
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
while (i != -1) {
|
|
||||||
i = colorDefines.indexOf(newColorString);
|
|
||||||
|
|
||||||
if (i >= 0) {
|
|
||||||
try {
|
|
||||||
int startIndex = i + newColorString.length();
|
|
||||||
int endIndex = colorDefines.indexOf(";", i);
|
|
||||||
|
|
||||||
String colorSubString = colorDefines.substring(startIndex, endIndex)
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
if (colorSubString.startsWith("@")) {
|
|
||||||
// have to recursively get the defined color
|
|
||||||
newColorString = colorDefine + " " + colorSubString.substring(1);
|
|
||||||
i = 0;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parseColor(colorSubString);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return parseColor(colorString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this works for GtkStatusIcon menus.
|
|
||||||
private static Color getFromColorScheme() {
|
|
||||||
Pointer settings = Gtk.gtk_settings_get_default();
|
|
||||||
if (settings != null) {
|
|
||||||
// see if we can get the info we want the EASY way (likely only when GTK+ 2 is used, but can be < GTK+ 3.2)...
|
|
||||||
|
|
||||||
// been deprecated since version 3.8
|
|
||||||
PointerByReference pointer = new PointerByReference();
|
|
||||||
Gobject.g_object_get(settings, "gtk-color-scheme", pointer, null);
|
|
||||||
|
|
||||||
// A palette of named colors for use in themes. The format of the string is
|
|
||||||
// name1: color1
|
|
||||||
// name2: color2
|
|
||||||
//
|
|
||||||
// Color names must be acceptable as identifiers in the gtkrc syntax, and color specifications must be in the format
|
|
||||||
// accepted by gdk_color_parse().
|
|
||||||
//
|
|
||||||
// Note that due to the way the color tables from different sources are merged, color specifications will be converted
|
|
||||||
// to hexadecimal form when getting this property.
|
|
||||||
//
|
|
||||||
// Starting with GTK+ 2.12, the entries can alternatively be separated by ';' instead of newlines:
|
|
||||||
// name1: color1; name2: color2; ...
|
|
||||||
//
|
|
||||||
// GtkSettings:gtk-color-scheme has been deprecated since version 3.8 and should not be used in newly-written code.
|
|
||||||
// Color scheme support was dropped and is no longer supported. You can still set this property, but it will be ignored.
|
|
||||||
|
|
||||||
|
|
||||||
Pointer value = pointer.getValue();
|
|
||||||
if (value != null) {
|
|
||||||
String s = value.getString(0);
|
|
||||||
if (!s.isEmpty()) {
|
|
||||||
// System.out.println("\t string: " + s);
|
|
||||||
|
|
||||||
// Note: these are the values on my system when forcing GTK+ 2 (XUbuntu 16.04) with GtkStatusIcon
|
|
||||||
// bg_color_dark: #686868686868
|
|
||||||
// fg_color: #3c3c3c3c3c3c
|
|
||||||
// fm_color: #f7f7f7f7f7f7
|
|
||||||
// selected_fg_color: #ffffffffffff
|
|
||||||
// panel_bg: #686868686868
|
|
||||||
// text_color: #212121212121
|
|
||||||
// text_color_dark: #ffffffffffff
|
|
||||||
// tooltip_bg_color: #000000000000
|
|
||||||
// link_color: #2d2d7171b8b8
|
|
||||||
// tooltip_fg_color: #e1e1e1e1e1e1
|
|
||||||
// base_color: #fcfcfcfcfcfc
|
|
||||||
// bg_color: #cececececece
|
|
||||||
// selected_bg_color: #39398e8ee7e7
|
|
||||||
|
|
||||||
|
|
||||||
String textColor = "text_color"; // also theme_text_color
|
|
||||||
int i = s.indexOf(textColor);
|
|
||||||
if (i >= 0) {
|
|
||||||
try {
|
|
||||||
// the color will ALWAYS be in hex notation
|
|
||||||
|
|
||||||
// it is also possible to be separated by ; instead of newline
|
|
||||||
int endIndex = s.indexOf(';', i);
|
|
||||||
if (endIndex == -1) {
|
|
||||||
endIndex = s.indexOf('\n', i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int startIndex = s.indexOf('#', i);
|
|
||||||
String colorString = s.substring(startIndex, endIndex).trim();
|
|
||||||
|
|
||||||
return parseColor(colorString);
|
|
||||||
} catch (Exception ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses out the color from a color:
|
|
||||||
*
|
|
||||||
* - the word "transparent"
|
|
||||||
* - hex 12 digit #ffffaaaaffff
|
|
||||||
* - hex 6 digit #ffaaff
|
|
||||||
* - hex 3 digit #faf
|
|
||||||
* - rgb(r, g, b) rgb(33, 33, 33)
|
|
||||||
* - rgb(r, g, b) rgb(.6, .3, .3)
|
|
||||||
* - rgb(r%, g%, b%) rgb(10%, 20%, 30%)
|
|
||||||
* - rgba(r, g, b, a) rgb(33, 33, 33, 0.53)
|
|
||||||
* - rgba(r, g, b, a) rgb(.33, .33, .33, 0.53)
|
|
||||||
* - rgba(r, g, b, a) rgb(10%, 20%, 30%, 0.53)
|
|
||||||
*
|
|
||||||
* Notes:
|
|
||||||
* - rgb(), when an int, is between 0-255
|
|
||||||
* - rgb(), when a float, is between 0.0-1.0
|
|
||||||
* - rgb(), when a percent, is between 0-100
|
|
||||||
* - alpha is always a float
|
|
||||||
*
|
|
||||||
* @return the parsed color
|
|
||||||
*/
|
|
||||||
private static
|
|
||||||
Color parseColor(String colorString) {
|
|
||||||
int red = 0;
|
|
||||||
int green = 0;
|
|
||||||
int blue = 0;
|
|
||||||
int alpha = 255;
|
|
||||||
|
|
||||||
if (colorString.startsWith("#")) {
|
|
||||||
colorString = colorString.substring(1);
|
|
||||||
|
|
||||||
if (colorString.length() > 11) {
|
|
||||||
red = Integer.parseInt(colorString.substring(0, 4), 16);
|
|
||||||
green = Integer.parseInt(colorString.substring(4, 8), 16);
|
|
||||||
blue = Integer.parseInt(colorString.substring(8), 16);
|
|
||||||
|
|
||||||
// Have to convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255
|
|
||||||
red = red & 0x0000FFFF;
|
|
||||||
green = green & 0x0000FFFF;
|
|
||||||
blue = blue & 0x0000FFFF;
|
|
||||||
|
|
||||||
red = (red >> 8) & 0xFF;
|
|
||||||
green = (green >> 8) & 0xFF;
|
|
||||||
blue = (blue >> 8) & 0xFF;
|
|
||||||
}
|
|
||||||
else if (colorString.length() > 5) {
|
|
||||||
red = Integer.parseInt(colorString.substring(0, 2), 16);
|
|
||||||
green = Integer.parseInt(colorString.substring(2, 4), 16);
|
|
||||||
blue = Integer.parseInt(colorString.substring(4), 16);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
red = Integer.parseInt(colorString.substring(0, 1), 16);
|
|
||||||
green = Integer.parseInt(colorString.substring(1, 2), 16);
|
|
||||||
blue = Integer.parseInt(colorString.substring(2), 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (colorString.startsWith("rgba")) {
|
|
||||||
colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')'));
|
|
||||||
String[] split = colorString.split(",");
|
|
||||||
|
|
||||||
String trim1 = split[0].trim();
|
|
||||||
String trim2 = split[1].trim();
|
|
||||||
String trim3 = split[2].trim();
|
|
||||||
String trim4 = split[3].trim();
|
|
||||||
|
|
||||||
if (colorString.contains("%")) {
|
|
||||||
trim1 = trim1.replace("%", "");
|
|
||||||
trim2 = trim2.replace("%", "");
|
|
||||||
trim3 = trim3.replace("%", "");
|
|
||||||
|
|
||||||
red = Integer.parseInt(trim1) * 255;
|
|
||||||
green = Integer.parseInt(trim2) * 255;
|
|
||||||
blue = Integer.parseInt(trim3) * 255;
|
|
||||||
} else if (colorString.contains(".")) {
|
|
||||||
red = (int) (Float.parseFloat(trim1) * 255);
|
|
||||||
green = (int) (Float.parseFloat(trim2) * 255);
|
|
||||||
blue = (int) (Float.parseFloat(trim3) * 255);
|
|
||||||
} else {
|
|
||||||
red = Integer.parseInt(trim1);
|
|
||||||
green = Integer.parseInt(trim2);
|
|
||||||
blue = Integer.parseInt(trim3);
|
|
||||||
}
|
|
||||||
|
|
||||||
float alphaF = Float.parseFloat(trim4);
|
|
||||||
alpha = (int) (alphaF * 255);
|
|
||||||
}
|
|
||||||
else if (colorString.startsWith("rgb")) {
|
|
||||||
colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')'));
|
|
||||||
String[] split = colorString.split(",");
|
|
||||||
|
|
||||||
String trim1 = split[0].trim();
|
|
||||||
String trim2 = split[1].trim();
|
|
||||||
String trim3 = split[2].trim();
|
|
||||||
|
|
||||||
if (colorString.contains("%")) {
|
|
||||||
trim1 = trim1.replace("%", "");
|
|
||||||
trim2 = trim2.replace("%", "");
|
|
||||||
trim3 = trim3.replace("%", "");
|
|
||||||
|
|
||||||
red = Integer.parseInt(trim1) * 255;
|
|
||||||
green = Integer.parseInt(trim2) * 255;
|
|
||||||
blue = Integer.parseInt(trim3) * 255;
|
|
||||||
} else if (colorString.contains(".")) {
|
|
||||||
red = (int) (Float.parseFloat(trim1) * 255);
|
|
||||||
green = (int) (Float.parseFloat(trim2) * 255);
|
|
||||||
blue = (int) (Float.parseFloat(trim3) * 255);
|
|
||||||
} else {
|
|
||||||
red = Integer.parseInt(trim1);
|
|
||||||
green = Integer.parseInt(trim2);
|
|
||||||
blue = Integer.parseInt(trim3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (colorString.contains("transparent")) {
|
|
||||||
alpha = 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int index = colorString.indexOf(";");
|
|
||||||
if (index > 0) {
|
|
||||||
colorString = colorString.substring(0, index);
|
|
||||||
}
|
|
||||||
colorString = colorString.replaceAll("\"", "");
|
|
||||||
colorString = colorString.replaceAll("'", "");
|
|
||||||
|
|
||||||
// maybe it's just a "color" description, such as "red"?
|
|
||||||
try {
|
|
||||||
return Color.decode(colorString);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Color(red, green, blue, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized.
|
|
||||||
*/
|
|
||||||
public static
|
|
||||||
String getGtkThemeCss() {
|
|
||||||
final AtomicReference<String> css = new AtomicReference<String>(null);
|
|
||||||
|
|
||||||
Gtk.dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Pointer settings = Gtk.gtk_settings_get_default();
|
|
||||||
if (settings != null) {
|
|
||||||
PointerByReference pointer = new PointerByReference();
|
|
||||||
Gobject.g_object_get(settings, "gtk-theme-name", pointer, null);
|
|
||||||
|
|
||||||
// https://wiki.archlinux.org/index.php/GTK%2B
|
|
||||||
//
|
|
||||||
// gets the name of the currently loaded theme (can be used to get colors?)
|
|
||||||
// GTK+ 2:
|
|
||||||
// ~/.gtkrc-2.0
|
|
||||||
// gtk-icon-theme-name = "Adwaita"
|
|
||||||
// gtk-theme-name = "Adwaita"
|
|
||||||
// gtk-font-name = "DejaVu Sans 11"
|
|
||||||
//
|
|
||||||
// GTK+ 3:
|
|
||||||
// $XDG_CONFIG_HOME/gtk-3.0/settings.ini
|
|
||||||
// [Settings]
|
|
||||||
// gtk-icon-theme-name = Adwaita
|
|
||||||
// gtk-theme-name = Adwaita
|
|
||||||
// gtk-font-name = DejaVu Sans 11
|
|
||||||
// Note: The icon theme name is the name defined in the theme's index file, not the name of its directory.
|
|
||||||
|
|
||||||
String themeName = null;
|
|
||||||
Pointer value = pointer.getValue();
|
|
||||||
if (value != null) {
|
|
||||||
themeName = value.getString(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (themeName != null) {
|
|
||||||
value = Gtk3.gtk_css_provider_get_named(themeName, null);
|
|
||||||
if (value != null) {
|
|
||||||
// we have the css provider!
|
|
||||||
// NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them
|
|
||||||
Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
|
|
||||||
|
|
||||||
css.set(Gtk3.gtk_css_provider_to_string(value));
|
|
||||||
|
|
||||||
Glib.g_log_set_default_handler(orig, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Pointer value = Gtk3.gtk_css_provider_get_default();
|
|
||||||
if (value != null) {
|
|
||||||
// we have the css provider!
|
|
||||||
// NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them
|
|
||||||
Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
|
|
||||||
|
|
||||||
css.set(Gtk3.gtk_css_provider_to_string(value));
|
|
||||||
|
|
||||||
Glib.g_log_set_default_handler(orig, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// will be either the string, or null.
|
|
||||||
return css.get();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
83
src/dorkbox/systemTray/jna/linux/GtkStyle.java
Normal file
83
src/dorkbox/systemTray/jna/linux/GtkStyle.java
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package dorkbox.systemTray.jna.linux;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.Structure;
|
||||||
|
|
||||||
|
import dorkbox.util.Keep;
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
public
|
||||||
|
class GtkStyle extends Structure {
|
||||||
|
/*
|
||||||
|
* There are several 'directives' to change the attributes of a widget.
|
||||||
|
* fg - Sets the foreground color of a widget.
|
||||||
|
* bg - Sets the background color of a widget.
|
||||||
|
* text - Sets the foreground color for widgets that have editable text.
|
||||||
|
* base - Sets the background color for widgets that have editable text.
|
||||||
|
* bg_pixmap - Sets the background of a widget to a tiled pixmap.
|
||||||
|
* font_name - Sets the font to be used with the given widget.
|
||||||
|
* xthickness - Sets the left and right border width. This is not what you might think; it sets the borders of children(?)
|
||||||
|
* ythickness - similar to above but for the top and bottom.
|
||||||
|
*
|
||||||
|
* There are several states a widget can be in, and you can set different colors, pixmaps and fonts for each state. These states are:
|
||||||
|
* NORMAL - The normal state of a widget. Ie the mouse is not over it, and it is not being pressed, etc.
|
||||||
|
* PRELIGHT - When the mouse is over top of the widget, colors defined using this state will be in effect.
|
||||||
|
* ACTIVE - When the widget is pressed or clicked it will be active, and the attributes assigned by this tag will be in effect.
|
||||||
|
* INSENSITIVE - This is the state when a widget is 'greyed out'. It is not active, and cannot be clicked on.
|
||||||
|
* SELECTED - When an object is selected, it takes these attributes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static
|
||||||
|
class ByReference extends GtkStyle implements Structure.ByReference {}
|
||||||
|
|
||||||
|
public
|
||||||
|
class ByValue extends GtkStyle implements Structure.ByValue {}
|
||||||
|
|
||||||
|
// only list public fields
|
||||||
|
|
||||||
|
/** fg: foreground for drawing GtkLabel */
|
||||||
|
public GdkColor fg[] = new GdkColor[5];
|
||||||
|
|
||||||
|
/** bg: the usual background color, gray by default */
|
||||||
|
public GdkColor bg[] = new GdkColor[5];
|
||||||
|
public GdkColor light[] = new GdkColor[5];
|
||||||
|
public GdkColor dark[] = new GdkColor[5];
|
||||||
|
public GdkColor mid[] = new GdkColor[5];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* text: text for entries and text widgets (although in GTK 1.2 sometimes fg gets used, this is more or less a bug and fixed in GTK 2.0).
|
||||||
|
*/
|
||||||
|
public GdkColor text[] = new GdkColor[5];
|
||||||
|
|
||||||
|
/** base: background when using text, colored white in the default theme. */
|
||||||
|
public GdkColor base[] = new GdkColor[5];
|
||||||
|
public GdkColor text_aa[] = new GdkColor[5]; /* Halfway between text/base */
|
||||||
|
public GdkColor black;
|
||||||
|
public GdkColor white;
|
||||||
|
public Pointer /*PangoFontDescription*/ font_desc;
|
||||||
|
public int xthickness;
|
||||||
|
public int ythickness;
|
||||||
|
public Pointer /*cairo_pattern_t*/ background[] = new Pointer[5];
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected
|
||||||
|
List<String> getFieldOrder() {
|
||||||
|
return Arrays.asList("fg",
|
||||||
|
"bg",
|
||||||
|
"light",
|
||||||
|
"dark",
|
||||||
|
"mid",
|
||||||
|
"text",
|
||||||
|
"base",
|
||||||
|
"text_aa",
|
||||||
|
"black",
|
||||||
|
"white",
|
||||||
|
"font_desc",
|
||||||
|
"xthickness",
|
||||||
|
"ythickness",
|
||||||
|
"background");
|
||||||
|
}
|
||||||
|
}
|
974
src/dorkbox/systemTray/jna/linux/GtkTheme.java
Normal file
974
src/dorkbox/systemTray/jna/linux/GtkTheme.java
Normal file
|
@ -0,0 +1,974 @@
|
||||||
|
package dorkbox.systemTray.jna.linux;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.Tray;
|
||||||
|
import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray;
|
||||||
|
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
||||||
|
import dorkbox.util.FileUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to contain all of the methods needed to get the text color from the AppIndicator/GtkStatusIcon menu entry. This is primarily
|
||||||
|
* used to get the color needed for the checkmark icon. In GTK, the checkmark icon can be defined to be it's OWN color and
|
||||||
|
* shape, however getting/parsing that would be even significantly more difficult -- so we decided to make the icon the same color
|
||||||
|
* as the text.
|
||||||
|
* <p>
|
||||||
|
* Additionally, CUSTOM, user theme modifications in ~/.gtkrc-2.0 (for example), will be ignored.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public
|
||||||
|
class GtkTheme {
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
private static final boolean DEBUG_SHOW_CSS = false;
|
||||||
|
private static final boolean DEBUG_VERBOSE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the widget color of text for the current theme, or black. It is important that this is called AFTER GTK has been initialized.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
Color getCurrentThemeTextColor() {
|
||||||
|
// NOTE: when getting CSS, we redirect STDERR to null (via GTK), so that we won't spam the console if there are parse errors.
|
||||||
|
// this is a horrid hack, but the only way to work around the errors we have no control over. The parse errors, if bad enough
|
||||||
|
// just mean that we are unable to get the CSS as we want.
|
||||||
|
|
||||||
|
// these methods are from most accurate (but limited in support) to compatible across Linux OSes.. Strangely enough, GTK makes
|
||||||
|
// this information insanely difficult to get.
|
||||||
|
final AtomicReference<Color> color = new AtomicReference<Color>(null);
|
||||||
|
Gtk.dispatchAndWait(new Runnable() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// see if we can get the info via CSS properties (> GTK+ 3.2 uses an API, GTK2 gets it from disk)
|
||||||
|
Color c = getFromCss();
|
||||||
|
if (c != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Got from CSS");
|
||||||
|
}
|
||||||
|
color.set(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we got here because it's not possible to get the info via raw-CSS
|
||||||
|
|
||||||
|
// try to get via the color scheme. A bit more accurate than parsing the raw theme file
|
||||||
|
c = getFromColorScheme();
|
||||||
|
if (c != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Got from color scheme");
|
||||||
|
}
|
||||||
|
color.set(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// if we get here, this means that there was NO "gtk-color-scheme" value in the theme file.
|
||||||
|
// This usually happens when the theme does not have @fg_color (for example), but instead has each color explicitly
|
||||||
|
// defined for every widget instance in the theme file. Old/bizzare themes tend to do it this way...
|
||||||
|
if (Gtk.isGtk2) {
|
||||||
|
c = getFromGtk2ThemeText();
|
||||||
|
if (c != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Got from gtk2 color theme file");
|
||||||
|
}
|
||||||
|
color.set(c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// the following methods all require an offscreen widget to get the style information from. This rarely is correct for
|
||||||
|
// some bizzare reason.
|
||||||
|
|
||||||
|
|
||||||
|
// create an off-screen widget (don't forget to destroy everything!)
|
||||||
|
Pointer menu = Gtk.gtk_menu_new();
|
||||||
|
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic("a");
|
||||||
|
|
||||||
|
Gtk.gtk_container_add(menu, item);
|
||||||
|
Gtk.gtk_widget_show_all(item);
|
||||||
|
|
||||||
|
// Try to get via RC style... Sometimes this works (sometimes it does not...)
|
||||||
|
{
|
||||||
|
Pointer style = Gtk.gtk_rc_get_style(item);
|
||||||
|
|
||||||
|
GdkColor gdkColor = new GdkColor();
|
||||||
|
boolean success;
|
||||||
|
|
||||||
|
success = Gtk.gtk_style_lookup_color(style, "menu_fg_color", gdkColor.getPointer());
|
||||||
|
if (!success) {
|
||||||
|
success = Gtk.gtk_style_lookup_color(style, "text_color", gdkColor.getPointer());
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
success = Gtk.gtk_style_lookup_color(style, "theme_text_color", gdkColor.getPointer());
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
color.set(gdkColor.getColor());
|
||||||
|
|
||||||
|
Gtk.gtk_widget_destroy(item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Gtk.isGtk3) {
|
||||||
|
Pointer context = Gtk3.gtk_widget_get_style_context(item);
|
||||||
|
int state = Gtk3.gtk_style_context_get_state(context);
|
||||||
|
|
||||||
|
GdkRGBAColor gdkColor = new GdkRGBAColor();
|
||||||
|
boolean success = Gtk3.gtk_style_context_lookup_color(context, "fg_color", gdkColor.getPointer());
|
||||||
|
if (!success) {
|
||||||
|
success = Gtk3.gtk_style_context_lookup_color(context, "text_color", gdkColor.getPointer());
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
success = Gtk3.gtk_style_context_lookup_color(context, "menu_fg_color", gdkColor.getPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
success = Gtk3.gtk_style_context_lookup_color(context, "color", gdkColor.getPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
color.set(new Color((float) gdkColor.red, (float) gdkColor.green, (float) gdkColor.blue, (float) gdkColor.alpha));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// fall back in case nothing else works
|
||||||
|
Gtk3.gtk_style_context_get_color(context, state, gdkColor.getPointer());
|
||||||
|
if (gdkColor.red == 0.0 && gdkColor.green == 0.0 && gdkColor.blue == 0.0 && gdkColor.alpha == 0.0) {
|
||||||
|
// have nothing here, check something else...
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("No valid output from gtk_style_context_get_color");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// if we have something that is not all 0's
|
||||||
|
color.set(new Color((float) gdkColor.red,
|
||||||
|
(float) gdkColor.green,
|
||||||
|
(float) gdkColor.blue,
|
||||||
|
(float) gdkColor.alpha));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this also doesn't always work...
|
||||||
|
GtkStyle.ByReference style = Gtk.gtk_widget_get_style(item);
|
||||||
|
color.set(style.text[Gtk.State.NORMAL].getColor());
|
||||||
|
|
||||||
|
Gtk.gtk_widget_destroy(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Color color1 = color.get();
|
||||||
|
if (color1 != null) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("COLOR FOUND: " + color1);
|
||||||
|
}
|
||||||
|
return color1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SystemTray.logger.error("Unable to determine the text color in use by your system. Please create an issue and include your " +
|
||||||
|
"full OS configuration and desktop environment, including theme details, such as the theme name, color " +
|
||||||
|
"variant, and custom theme options (if any).");
|
||||||
|
|
||||||
|
// who knows WHAT the color is supposed to be. This is just a "best guess" default value.
|
||||||
|
return Color.BLACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the color we are interested in via raw CSS parsing. This is specifically to get the color of the text of the
|
||||||
|
* appindicator/gtk-status-icon menu.
|
||||||
|
* <p>
|
||||||
|
* > GTK+ 3.2 uses an API, GTK2 gets it from disk
|
||||||
|
*
|
||||||
|
* @return the color string, parsed from CSS/
|
||||||
|
*/
|
||||||
|
private static
|
||||||
|
Color getFromCss() {
|
||||||
|
String css = getGtkThemeCss();
|
||||||
|
if (css != null) {
|
||||||
|
if (DEBUG_SHOW_CSS) {
|
||||||
|
System.err.println(css);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] nodes;
|
||||||
|
Tray tray = (Tray) SystemTray.get()
|
||||||
|
.getMenu();
|
||||||
|
|
||||||
|
|
||||||
|
// we care about the following CSS head nodes, and account for multiple versions, in order of preference.
|
||||||
|
if (tray instanceof _GtkStatusIconNativeTray) {
|
||||||
|
nodes = new String[] {"GtkPopover", "gnome-panel-menu-bar", "unity-panel", "PanelMenuBar", ".check"};
|
||||||
|
}
|
||||||
|
else if (tray instanceof _AppIndicatorNativeTray) {
|
||||||
|
nodes = new String[] {"GtkPopover", "unity-panel", "gnome-panel-menu-bar", "PanelMenuBar", ".check"};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// not supported for other types
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collect a list of all of the sections that have what we are interested in
|
||||||
|
List<String> sections = new ArrayList<String>();
|
||||||
|
|
||||||
|
String colorString = null;
|
||||||
|
|
||||||
|
// now check the css nodes to see if they contain a combination of what we are looking for.
|
||||||
|
colorCheck:
|
||||||
|
for (String node : nodes) {
|
||||||
|
int i = 0;
|
||||||
|
while (i != -1) {
|
||||||
|
i = css.indexOf(node, i);
|
||||||
|
if (i > -1) {
|
||||||
|
int endOfNodeLabels = css.indexOf("{", i);
|
||||||
|
int endOfSection = css.indexOf("}", endOfNodeLabels + 1) + 1;
|
||||||
|
int endOfSectionTest = css.indexOf("}", i) + 1;
|
||||||
|
|
||||||
|
// this makes sure that weird parsing errors don't happen as a result of node keywords appearing in node sections
|
||||||
|
if (endOfSection != endOfSectionTest) {
|
||||||
|
// advance the index
|
||||||
|
i = endOfSection;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String nodeLabel = css.substring(i, endOfNodeLabels);
|
||||||
|
String nodeSection = css.substring(endOfNodeLabels, endOfSection);
|
||||||
|
|
||||||
|
int j = nodeSection.indexOf(" color");
|
||||||
|
if (j > -1) {
|
||||||
|
sections.add(nodeLabel + " " + nodeSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// advance the index
|
||||||
|
i = endOfSection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_VERBOSE) {
|
||||||
|
for (String section : sections) {
|
||||||
|
System.err.println("--------------");
|
||||||
|
System.err.println(section);
|
||||||
|
System.err.println("--------------");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sections.isEmpty()) {
|
||||||
|
String section = sections.get(0);
|
||||||
|
int start = section.indexOf("{");
|
||||||
|
int colorIndex = section.indexOf(" color", start);
|
||||||
|
|
||||||
|
int startOfColorDef = section.indexOf(":", colorIndex) + 1;
|
||||||
|
int endOfColorDef = section.indexOf(";", startOfColorDef);
|
||||||
|
colorString = section.substring(startOfColorDef, endOfColorDef)
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// hopefully we found it.
|
||||||
|
if (colorString != null) {
|
||||||
|
if (colorString.startsWith("@")) {
|
||||||
|
// it's a color definition
|
||||||
|
colorString = colorString.substring(1);
|
||||||
|
|
||||||
|
// have to setup the "define color" section
|
||||||
|
String colorDefine = "@define-color";
|
||||||
|
int start = css.indexOf(colorDefine);
|
||||||
|
int end = css.lastIndexOf(colorDefine);
|
||||||
|
end = css.lastIndexOf(";", end) + 1; // include the ;
|
||||||
|
String colorDefines = css.substring(start, end);
|
||||||
|
|
||||||
|
if (DEBUG_VERBOSE) {
|
||||||
|
System.err.println("+++++++++++++++++++++++");
|
||||||
|
System.err.println(colorDefines);
|
||||||
|
System.err.println("+++++++++++++++++++++++");
|
||||||
|
}
|
||||||
|
|
||||||
|
// since it's a color definition, it will start a very specific way.
|
||||||
|
String newColorString = colorDefine + " " + colorString;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (i != -1) {
|
||||||
|
i = colorDefines.indexOf(newColorString);
|
||||||
|
|
||||||
|
if (i >= 0) {
|
||||||
|
try {
|
||||||
|
int startIndex = i + newColorString.length();
|
||||||
|
int endIndex = colorDefines.indexOf(";", i);
|
||||||
|
|
||||||
|
String colorSubString = colorDefines.substring(startIndex, endIndex)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (colorSubString.startsWith("@")) {
|
||||||
|
// have to recursively get the defined color
|
||||||
|
newColorString = colorDefine + " " + colorSubString.substring(1);
|
||||||
|
i = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseColor(colorSubString);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return parseColor(colorString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String getGtkThemeCss() {
|
||||||
|
if (Gtk.isGtk3) {
|
||||||
|
final AtomicReference<String> css = new AtomicReference<String>(null);
|
||||||
|
|
||||||
|
Gtk.dispatchAndWait(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
String themeName = getThemeName();
|
||||||
|
|
||||||
|
if (themeName != null) {
|
||||||
|
Pointer value = Gtk3.gtk_css_provider_get_named(themeName, null);
|
||||||
|
if (value != null) {
|
||||||
|
// we have the css provider!
|
||||||
|
|
||||||
|
// NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them
|
||||||
|
Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
|
||||||
|
|
||||||
|
css.set(Gtk3.gtk_css_provider_to_string(value));
|
||||||
|
|
||||||
|
Glib.g_log_set_default_handler(orig, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Pointer value = Gtk3.gtk_css_provider_get_default();
|
||||||
|
if (value != null) {
|
||||||
|
// we have the css provider!
|
||||||
|
|
||||||
|
// NOTE: This can output warnings if the theme doesn't parse correctly by GTK, so we suppress them
|
||||||
|
Glib.GLogFunc orig = Glib.g_log_set_default_handler(Glib.nullLogFunc, null);
|
||||||
|
|
||||||
|
css.set(Gtk3.gtk_css_provider_to_string(value));
|
||||||
|
|
||||||
|
Glib.g_log_set_default_handler(orig, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// will be either the string, or null.
|
||||||
|
return css.get();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// GTK2 has to get the GTK3 theme text a different way (parsing it from disk). SOMETIMES, the app must be GTK2, even though
|
||||||
|
// the system is GTK3. This works around the API restriction if we are an APP in GTK2 mode.
|
||||||
|
return getGtk3ThemeCssViaFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this works for GtkStatusIcon menus.
|
||||||
|
*
|
||||||
|
* @return the menu_fg/fg/text color from gtk-color-scheme or null
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
Color getFromColorScheme() {
|
||||||
|
Pointer settings = Gtk.gtk_settings_get_default();
|
||||||
|
if (settings != null) {
|
||||||
|
// see if we can get the info we want the EASY way (likely only when GTK+ 2 is used, but can be < GTK+ 3.2)...
|
||||||
|
|
||||||
|
// been deprecated since version 3.8
|
||||||
|
PointerByReference pointer = new PointerByReference();
|
||||||
|
Gobject.g_object_get(settings, "gtk-color-scheme", pointer, null);
|
||||||
|
|
||||||
|
|
||||||
|
// A palette of named colors for use in themes. The format of the string is
|
||||||
|
// name1: color1
|
||||||
|
// name2: color2
|
||||||
|
//
|
||||||
|
// Color names must be acceptable as identifiers in the gtkrc syntax, and color specifications must be in the format
|
||||||
|
// accepted by gdk_color_parse().
|
||||||
|
//
|
||||||
|
// Note that due to the way the color tables from different sources are merged, color specifications will be converted
|
||||||
|
// to hexadecimal form when getting this property.
|
||||||
|
//
|
||||||
|
// Starting with GTK+ 2.12, the entries can alternatively be separated by ';' instead of newlines:
|
||||||
|
// name1: color1; name2: color2; ...
|
||||||
|
//
|
||||||
|
// GtkSettings:gtk-color-scheme has been deprecated since version 3.8 and should not be used in newly-written code.
|
||||||
|
// Color scheme support was dropped and is no longer supported. You can still set this property, but it will be ignored.
|
||||||
|
|
||||||
|
|
||||||
|
Pointer value = pointer.getValue();
|
||||||
|
if (value != null) {
|
||||||
|
String s = value.getString(0);
|
||||||
|
if (!s.isEmpty()) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("\t string: " + s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: these are the values on my system when forcing GTK+ 2 (XUbuntu 16.04) with GtkStatusIcon and Aidwata theme
|
||||||
|
// bg_color_dark: #686868686868
|
||||||
|
// fg_color: #3c3c3c3c3c3c
|
||||||
|
// fm_color: #f7f7f7f7f7f7
|
||||||
|
// selected_fg_color: #ffffffffffff
|
||||||
|
// panel_bg: #686868686868
|
||||||
|
// text_color: #212121212121
|
||||||
|
// text_color_dark: #ffffffffffff
|
||||||
|
// tooltip_bg_color: #000000000000
|
||||||
|
// link_color: #2d2d7171b8b8
|
||||||
|
// tooltip_fg_color: #e1e1e1e1e1e1
|
||||||
|
// base_color: #fcfcfcfcfcfc
|
||||||
|
// bg_color: #cececececece
|
||||||
|
// selected_bg_color: #39398e8ee7e7
|
||||||
|
|
||||||
|
// list of colors, in order of importance, that we want to parse.
|
||||||
|
String colors[] = new String[] {"menu_fg_color", "fg_color", "text_color"};
|
||||||
|
|
||||||
|
for (String colorName : colors) {
|
||||||
|
int i = 0;
|
||||||
|
while (i != -1) {
|
||||||
|
i = s.indexOf(colorName, i);
|
||||||
|
if (i >= 0) {
|
||||||
|
try {
|
||||||
|
// the color will ALWAYS be in hex notation
|
||||||
|
|
||||||
|
// it is also possible to be separated by ; instead of newline
|
||||||
|
int endIndex = s.indexOf(';', i);
|
||||||
|
if (endIndex == -1) {
|
||||||
|
endIndex = s.indexOf('\n', i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.charAt(i - 1) == '_') {
|
||||||
|
i = endIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int startIndex = s.indexOf('#', i);
|
||||||
|
String colorString = s.substring(startIndex, endIndex)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Color string: " + colorString);
|
||||||
|
}
|
||||||
|
return parseColor(colorString);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks in the following locations for the current GTK3 theme.
|
||||||
|
* <p>
|
||||||
|
* /usr/share/themes
|
||||||
|
* /opt/gnome/share/themes
|
||||||
|
*/
|
||||||
|
private static
|
||||||
|
String getGtk3ThemeCssViaFile() {
|
||||||
|
File themeDirectory = getThemeDirectory(true);
|
||||||
|
|
||||||
|
if (themeDirectory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
File gtkFile = new File(themeDirectory, "gtk.css");
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder((int) (gtkFile.length()));
|
||||||
|
FileUtil.read(gtkFile, stringBuilder);
|
||||||
|
|
||||||
|
removeComments(stringBuilder);
|
||||||
|
|
||||||
|
// only comments in the file
|
||||||
|
if (stringBuilder.length() < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
injectAdditionalCss(themeDirectory, stringBuilder);
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// cant read the file or something else.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the discovered fg[NORMAL] or text[NORMAL] color for this theme or null
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
Color getFromGtk2ThemeText() {
|
||||||
|
String gtk2ThemeText = getGtk2ThemeText();
|
||||||
|
|
||||||
|
if (gtk2ThemeText != null) {
|
||||||
|
String[] colorText = new String[] {"fg[NORMAL]", "text[NORMAL]"};
|
||||||
|
for (String text : colorText) {
|
||||||
|
int i = 0;
|
||||||
|
while (i != -1) {
|
||||||
|
i = gtk2ThemeText.indexOf(text, i);
|
||||||
|
if (i != -1) {
|
||||||
|
if (i > 0 && gtk2ThemeText.charAt(i - 1) != '_') {
|
||||||
|
i += text.length();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int j = gtk2ThemeText.indexOf("=", i);
|
||||||
|
if (j != -1) {
|
||||||
|
int lineEnd = gtk2ThemeText.indexOf('\n', j);
|
||||||
|
|
||||||
|
if (lineEnd != -1) {
|
||||||
|
String colorName = gtk2ThemeText.substring(j + 1, lineEnd)
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
colorName = colorName.replaceAll("\"", "");
|
||||||
|
return parseColor(colorName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks in the following locations for the current GTK2 theme.
|
||||||
|
* <p>
|
||||||
|
* /usr/share/themes
|
||||||
|
* /opt/gnome/share/themes
|
||||||
|
*/
|
||||||
|
private static
|
||||||
|
String getGtk2ThemeText() {
|
||||||
|
File themeDirectory = getThemeDirectory(false);
|
||||||
|
|
||||||
|
if (themeDirectory == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ie: /usr/share/themes/Numix/gtk-2.0/gtkrc
|
||||||
|
File gtkFile = new File(themeDirectory, "gtkrc");
|
||||||
|
|
||||||
|
try {
|
||||||
|
StringBuilder stringBuilder = new StringBuilder((int) (gtkFile.length()));
|
||||||
|
FileUtil.read(gtkFile, stringBuilder);
|
||||||
|
|
||||||
|
removeComments(stringBuilder);
|
||||||
|
|
||||||
|
// only comments in the file
|
||||||
|
if (stringBuilder.length() < 2) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
// cant read the file or something else.
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out what the directory is for the specified type of GTK theme files (css/gtkrc/etc)
|
||||||
|
*
|
||||||
|
* @param gtk3 true if you want to look for the GTK3 theme dir, false if you want the GTK2 theme dir
|
||||||
|
*
|
||||||
|
* @return the directory or null if it cannot be found
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
File getThemeDirectory(boolean gtk3) {
|
||||||
|
String themeName = getThemeName();
|
||||||
|
|
||||||
|
if (themeName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String gtkType;
|
||||||
|
if (gtk3) {
|
||||||
|
gtkType = "gtk-3.0";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gtkType = "gtk-2.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String[] dirs = new String[] {"/usr/share/themes", "/opt/gnome/share/themes"};
|
||||||
|
|
||||||
|
// ie: /usr/share/themes
|
||||||
|
for (String dirName : dirs) {
|
||||||
|
File themesDir = new File(dirName);
|
||||||
|
|
||||||
|
File[] themeDirs = themesDir.listFiles();
|
||||||
|
if (themeDirs != null) {
|
||||||
|
// ie: /usr/share/themes/Numix
|
||||||
|
for (File themeDir : themeDirs) {
|
||||||
|
File[] files1 = themeDir.listFiles();
|
||||||
|
if (files1 != null) {
|
||||||
|
boolean isCorrectTheme;
|
||||||
|
|
||||||
|
File themeIndexFile = new File(themeDir, "index.theme");
|
||||||
|
try {
|
||||||
|
List<String> read = FileUtil.read(themeIndexFile, false);
|
||||||
|
for (String s : read) {
|
||||||
|
if (s.startsWith("GtkTheme=")) {
|
||||||
|
String calculatedThemeName = s.substring("GtkTheme=".length());
|
||||||
|
|
||||||
|
isCorrectTheme = calculatedThemeName.equals(themeName);
|
||||||
|
|
||||||
|
if (isCorrectTheme) {
|
||||||
|
// ie: /usr/share/themes/Numix/gtk-3.0/gtk.css
|
||||||
|
// the DARK variant is only used by some apps. The dark variant is NOT SYSTEM-WIDE!
|
||||||
|
return new File(themeDir, gtkType);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses out the color from a color:
|
||||||
|
* <p>
|
||||||
|
* - the word "transparent"
|
||||||
|
* - hex 12 digit #ffffaaaaffff
|
||||||
|
* - hex 6 digit #ffaaff
|
||||||
|
* - hex 3 digit #faf
|
||||||
|
* - rgb(r, g, b) rgb(33, 33, 33)
|
||||||
|
* - rgb(r, g, b) rgb(.6, .3, .3)
|
||||||
|
* - rgb(r%, g%, b%) rgb(10%, 20%, 30%)
|
||||||
|
* - rgba(r, g, b, a) rgb(33, 33, 33, 0.53)
|
||||||
|
* - rgba(r, g, b, a) rgb(.33, .33, .33, 0.53)
|
||||||
|
* - rgba(r, g, b, a) rgb(10%, 20%, 30%, 0.53)
|
||||||
|
* <p>
|
||||||
|
* Notes:
|
||||||
|
* - rgb(), when an int, is between 0-255
|
||||||
|
* - rgb(), when a float, is between 0.0-1.0
|
||||||
|
* - rgb(), when a percent, is between 0-100
|
||||||
|
* - alpha is always a float
|
||||||
|
*
|
||||||
|
* @return the parsed color
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
private static
|
||||||
|
Color parseColor(String colorString) {
|
||||||
|
int red = 0;
|
||||||
|
int green = 0;
|
||||||
|
int blue = 0;
|
||||||
|
int alpha = 255;
|
||||||
|
|
||||||
|
if (colorString.startsWith("#")) {
|
||||||
|
colorString = colorString.substring(1);
|
||||||
|
|
||||||
|
if (colorString.length() > 11) {
|
||||||
|
red = Integer.parseInt(colorString.substring(0, 4), 16);
|
||||||
|
green = Integer.parseInt(colorString.substring(4, 8), 16);
|
||||||
|
blue = Integer.parseInt(colorString.substring(8), 16);
|
||||||
|
|
||||||
|
// Have to convert to positive int (value between 0 and 65535, these are 16 bits per pixel) that is from 0-255
|
||||||
|
red = red & 0x0000FFFF;
|
||||||
|
green = green & 0x0000FFFF;
|
||||||
|
blue = blue & 0x0000FFFF;
|
||||||
|
|
||||||
|
red = (red >> 8) & 0xFF;
|
||||||
|
green = (green >> 8) & 0xFF;
|
||||||
|
blue = (blue >> 8) & 0xFF;
|
||||||
|
}
|
||||||
|
else if (colorString.length() > 5) {
|
||||||
|
red = Integer.parseInt(colorString.substring(0, 2), 16);
|
||||||
|
green = Integer.parseInt(colorString.substring(2, 4), 16);
|
||||||
|
blue = Integer.parseInt(colorString.substring(4), 16);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
red = Integer.parseInt(colorString.substring(0, 1), 16);
|
||||||
|
green = Integer.parseInt(colorString.substring(1, 2), 16);
|
||||||
|
blue = Integer.parseInt(colorString.substring(2), 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (colorString.startsWith("rgba")) {
|
||||||
|
colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')'));
|
||||||
|
String[] split = colorString.split(",");
|
||||||
|
|
||||||
|
String trim1 = split[0].trim();
|
||||||
|
String trim2 = split[1].trim();
|
||||||
|
String trim3 = split[2].trim();
|
||||||
|
String trim4 = split[3].trim();
|
||||||
|
|
||||||
|
if (colorString.contains("%")) {
|
||||||
|
trim1 = trim1.replace("%", "");
|
||||||
|
trim2 = trim2.replace("%", "");
|
||||||
|
trim3 = trim3.replace("%", "");
|
||||||
|
|
||||||
|
red = Integer.parseInt(trim1) * 255;
|
||||||
|
green = Integer.parseInt(trim2) * 255;
|
||||||
|
blue = Integer.parseInt(trim3) * 255;
|
||||||
|
}
|
||||||
|
else if (colorString.contains(".")) {
|
||||||
|
red = (int) (Float.parseFloat(trim1) * 255);
|
||||||
|
green = (int) (Float.parseFloat(trim2) * 255);
|
||||||
|
blue = (int) (Float.parseFloat(trim3) * 255);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
red = Integer.parseInt(trim1);
|
||||||
|
green = Integer.parseInt(trim2);
|
||||||
|
blue = Integer.parseInt(trim3);
|
||||||
|
}
|
||||||
|
|
||||||
|
float alphaF = Float.parseFloat(trim4);
|
||||||
|
alpha = (int) (alphaF * 255);
|
||||||
|
}
|
||||||
|
else if (colorString.startsWith("rgb")) {
|
||||||
|
colorString = colorString.substring(colorString.indexOf('(') + 1, colorString.indexOf(')'));
|
||||||
|
String[] split = colorString.split(",");
|
||||||
|
|
||||||
|
String trim1 = split[0].trim();
|
||||||
|
String trim2 = split[1].trim();
|
||||||
|
String trim3 = split[2].trim();
|
||||||
|
|
||||||
|
if (colorString.contains("%")) {
|
||||||
|
trim1 = trim1.replace("%", "");
|
||||||
|
trim2 = trim2.replace("%", "");
|
||||||
|
trim3 = trim3.replace("%", "");
|
||||||
|
|
||||||
|
red = Integer.parseInt(trim1) * 255;
|
||||||
|
green = Integer.parseInt(trim2) * 255;
|
||||||
|
blue = Integer.parseInt(trim3) * 255;
|
||||||
|
}
|
||||||
|
else if (colorString.contains(".")) {
|
||||||
|
red = (int) (Float.parseFloat(trim1) * 255);
|
||||||
|
green = (int) (Float.parseFloat(trim2) * 255);
|
||||||
|
blue = (int) (Float.parseFloat(trim3) * 255);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
red = Integer.parseInt(trim1);
|
||||||
|
green = Integer.parseInt(trim2);
|
||||||
|
blue = Integer.parseInt(trim3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (colorString.contains("transparent")) {
|
||||||
|
alpha = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int index = colorString.indexOf(";");
|
||||||
|
if (index > 0) {
|
||||||
|
colorString = colorString.substring(0, index);
|
||||||
|
}
|
||||||
|
colorString = colorString.replaceAll("\"", "");
|
||||||
|
colorString = colorString.replaceAll("'", "");
|
||||||
|
|
||||||
|
// maybe it's just a "color" description, such as "red"?
|
||||||
|
try {
|
||||||
|
return Color.decode(colorString);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Color(red, green, blue, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://wiki.archlinux.org/index.php/GTK%2B
|
||||||
|
* <p>
|
||||||
|
* gets the name of the currently loaded theme
|
||||||
|
* GTK+ 2:
|
||||||
|
* ~/.gtkrc-2.0
|
||||||
|
* gtk-icon-theme-name = "Adwaita"
|
||||||
|
* gtk-theme-name = "Adwaita"
|
||||||
|
* gtk-font-name = "DejaVu Sans 11"
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* GTK+ 3:
|
||||||
|
* $XDG_CONFIG_HOME/gtk-3.0/settings.ini
|
||||||
|
* [Settings]
|
||||||
|
* gtk-icon-theme-name = Adwaita
|
||||||
|
* gtk-theme-name = Adwaita
|
||||||
|
* gtk-font-name = DejaVu Sans 11
|
||||||
|
* <p>
|
||||||
|
* <p>
|
||||||
|
* Note: The icon theme name is the name defined in the theme's index file, not the name of its directory.
|
||||||
|
* <p>
|
||||||
|
* directories:
|
||||||
|
* /usr/share/themes
|
||||||
|
* /opt/gnome/share/themes
|
||||||
|
* <p>
|
||||||
|
* GTK+ 2 user specific: ~/.gtkrc-2.0
|
||||||
|
* GTK+ 2 system wide: /etc/gtk-2.0/gtkrc
|
||||||
|
* <p>
|
||||||
|
* GTK+ 3 user specific: $XDG_CONFIG_HOME/gtk-3.0/settings.ini, or $HOME/.config/gtk-3.0/settings.ini if $XDG_CONFIG_HOME is not set
|
||||||
|
* GTK+ 3 system wide: /etc/gtk-3.0/settings.ini
|
||||||
|
*
|
||||||
|
* @return the theme name, or null if it cannot find it.
|
||||||
|
*/
|
||||||
|
public static
|
||||||
|
String getThemeName() {
|
||||||
|
String themeName = null;
|
||||||
|
|
||||||
|
Pointer settings = Gtk.gtk_settings_get_default();
|
||||||
|
if (settings != null) {
|
||||||
|
|
||||||
|
|
||||||
|
PointerByReference pointer = new PointerByReference();
|
||||||
|
Gobject.g_object_get(settings, "gtk-theme-name", pointer, null);
|
||||||
|
|
||||||
|
Pointer value = pointer.getValue();
|
||||||
|
if (value != null) {
|
||||||
|
themeName = value.getString(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.err.println("Theme name: " + themeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return themeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
private static
|
||||||
|
void removeComments(final StringBuilder stringBuilder) {
|
||||||
|
// remove block comments, /* .... */ This can span multiple lines
|
||||||
|
int start = 0;
|
||||||
|
while (start != -1) {
|
||||||
|
// get the start of a comment
|
||||||
|
start = stringBuilder.indexOf("/*", start);
|
||||||
|
|
||||||
|
if (start != -1) {
|
||||||
|
// get the end of a comment
|
||||||
|
int end = stringBuilder.indexOf("*/", start);
|
||||||
|
if (end != -1) {
|
||||||
|
stringBuilder.delete(start, end + 2); // 2 is the size of */
|
||||||
|
|
||||||
|
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
|
||||||
|
if (stringBuilder.charAt(start) == '\n') {
|
||||||
|
stringBuilder.delete(start, start + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now remove comments that start with // (line MUST start with //)
|
||||||
|
start = 0;
|
||||||
|
while (start != -1) {
|
||||||
|
// get the start of a comment
|
||||||
|
start = stringBuilder.indexOf("//", start);
|
||||||
|
|
||||||
|
if (start != -1) {
|
||||||
|
// the comment is at the start of a line
|
||||||
|
if (start == 0 || stringBuilder.charAt(start-1) == '\n') {
|
||||||
|
// get the end of the comment (the end of the line)
|
||||||
|
int end = stringBuilder.indexOf("\n", start);
|
||||||
|
if (end != -1) {
|
||||||
|
stringBuilder.delete(start, end + 1); // 1 is the size of \n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
|
||||||
|
if (stringBuilder.charAt(start) == '\n') {
|
||||||
|
stringBuilder.delete(start, start + 1);
|
||||||
|
}
|
||||||
|
else if (start > 0){
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now remove comments that start with # (line MUST start with #)
|
||||||
|
start = 0;
|
||||||
|
while (start != -1) {
|
||||||
|
// get the start of a comment
|
||||||
|
start = stringBuilder.indexOf("#", start);
|
||||||
|
|
||||||
|
if (start != -1) {
|
||||||
|
// the comment is at the start of a line
|
||||||
|
if (start == 0 || stringBuilder.charAt(start-1) == '\n') {
|
||||||
|
// get the end of the comment (the end of the line)
|
||||||
|
int end = stringBuilder.indexOf("\n", start);
|
||||||
|
if (end != -1) {
|
||||||
|
stringBuilder.delete(start, end + 1); // 1 is the size of \n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sometimes when the comments are removed, there is a trailing newline. remove that too. Works for windows too
|
||||||
|
if (stringBuilder.charAt(start) == '\n') {
|
||||||
|
stringBuilder.delete(start, start + 1);
|
||||||
|
}
|
||||||
|
else if (start > 0){
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static
|
||||||
|
void injectAdditionalCss(final File parent, final StringBuilder stringBuilder) {
|
||||||
|
// not the BEST way to do this because duplicates are not merged at all.
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
while (start != -1) {
|
||||||
|
// now check if it says: @import url("gtk-main.css")
|
||||||
|
start = stringBuilder.indexOf("@import url(", start);
|
||||||
|
|
||||||
|
if (start != -1) {
|
||||||
|
int end = stringBuilder.indexOf("\")", start);
|
||||||
|
if (end != -1) {
|
||||||
|
String url = stringBuilder.substring(start + 13, end);
|
||||||
|
stringBuilder.delete(start, end + 2); // 2 is the size of ")
|
||||||
|
|
||||||
|
if (DEBUG_VERBOSE) {
|
||||||
|
System.err.println("import url: " + url);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// now inject the new file where the import command was.
|
||||||
|
File file = new File(parent, url);
|
||||||
|
StringBuilder stringBuilder2 = new StringBuilder((int) (file.length()));
|
||||||
|
FileUtil.read(file, stringBuilder2);
|
||||||
|
|
||||||
|
removeComments(stringBuilder2);
|
||||||
|
|
||||||
|
stringBuilder.insert(start, stringBuilder2);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user