
dorkbox.util.jna.linux.GtkTheme Maven / Gradle / Ivy
/*
* Copyright 2017 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.util.jna.linux;
import static dorkbox.util.jna.linux.Gtk.Gtk2;
import static dorkbox.util.jna.linux.Gtk.Gtk3;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.LoggerFactory;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;
import dorkbox.executor.ShellExecutor;
import dorkbox.util.FileUtil;
import dorkbox.util.MathUtil;
import dorkbox.util.OS;
import dorkbox.util.OSUtil;
import dorkbox.util.Swt;
import dorkbox.util.jna.linux.structs.GtkRequisition;
import dorkbox.util.jna.linux.structs.GtkStyle;
import dorkbox.util.jna.linux.structs.PangoRectangle;
/**
* Class to contain all of the various methods needed to get information set by a GTK theme.
*/
@SuppressWarnings({"deprecation", "WeakerAccess"})
public
class GtkTheme {
public static
Rectangle getPixelTextHeight(String text) {
// the following method requires an offscreen widget to get the size of text (for the checkmark size) via pango
// don't forget to destroy everything!
Pointer menu = null;
Pointer item = null;
try {
menu = Gtk2.gtk_menu_new();
item = Gtk2.gtk_image_menu_item_new_with_mnemonic(text);
Gtk2.gtk_container_add(menu, item);
Gtk2.gtk_widget_realize(menu);
Gtk2.gtk_widget_realize(item);
Gtk2.gtk_widget_show_all(menu);
// get the text widget (GtkAccelLabel/GtkLabel) from inside the GtkMenuItem
Pointer textLabel = Gtk2.gtk_bin_get_child(item);
Pointer pangoLayout = Gtk2.gtk_label_get_layout(textLabel);
// ink pixel size is how much exact space it takes on the screen
PangoRectangle ink = new PangoRectangle();
Gtk2.pango_layout_get_pixel_extents(pangoLayout, ink.getPointer(), null);
ink.read();
return new Rectangle(ink.width, ink.height);
} finally {
Gtk2.gtk_widget_destroy(item);
Gtk2.gtk_widget_destroy(menu);
}
}
/**
* @return the size of the GTK menu entry's IMAGE, as best as we can tell, for determining how large of icons to use for the menu entry
*/
public static
int getMenuEntryImageSize() {
final AtomicReference imageHeight = new AtomicReference();
GtkEventDispatch.dispatchAndWait(new Runnable() {
@Override
public
void run() {
Pointer offscreen = Gtk2.gtk_offscreen_window_new();
// get the default icon size for the "paste" icon.
Pointer item = Gtk2.gtk_image_menu_item_new_from_stock("gtk-paste", null);
Gtk2.gtk_container_add(offscreen, item);
PointerByReference r = new PointerByReference();
GObject.g_object_get(item, "image", r.getPointer(), null);
Pointer imageWidget = r.getValue();
GtkRequisition gtkRequisition = new GtkRequisition();
Gtk2.gtk_widget_size_request(imageWidget, gtkRequisition.getPointer());
gtkRequisition.read();
imageHeight.set(gtkRequisition.height);
}
});
int height = imageHeight.get();
if (height > 0) {
return height;
}
else {
LoggerFactory.getLogger(GtkTheme.class).warn("Unable to get tray menu image size. Using default.");
return 16;
}
}
/**
* Gets the system tray indicator size.
* - AppIndicator: will properly scale the image if it's not the correct size
* - GtkStatusIndicator: ??
*/
public static
int getIndicatorSize() {
// Linux is similar enough, that it just uses this method
// https://wiki.archlinux.org/index.php/HiDPI
// 96 DPI is the default
final double defaultDPI = 96.0;
final AtomicReference screenScale = new AtomicReference();
final AtomicInteger screenDPI = new AtomicInteger();
screenScale.set(0D);
screenDPI.set(0);
GtkEventDispatch.dispatchAndWait(new Runnable() {
@Override
public
void run() {
// screen DPI
Pointer screen = Gtk2.gdk_screen_get_default();
if (screen != null) {
// this call makes NO SENSE, but reading the documentation shows it is the CORRECT call.
screenDPI.set((int) Gtk2.gdk_screen_get_resolution(screen));
}
if (Gtk2.isGtk3) {
Pointer window = Gtk2.gdk_get_default_root_window();
if (window != null) {
double scale = Gtk3.gdk_window_get_scale_factor(window);
screenScale.set(scale);
}
}
}
});
// fallback
if (screenDPI.get() <= 0) {
// GET THE DPI IN LINUX
// https://wiki.archlinux.org/index.php/Talk:GNOME
Object detectedValue = Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/DPI");
if (detectedValue instanceof Integer) {
int dpi = ((Integer) detectedValue) / 1024;
if (dpi == -1) {
screenDPI.set((int) defaultDPI);
}
}
}
// 50 dpi is the minimum value gnome allows, and assume something screwed up. We apply this for ALL environments!
if (screenDPI.get() < 50) {
screenDPI.set((int) defaultDPI);
}
// check system ENV variables.
if (screenScale.get() == 0) {
String envVar = System.getenv("QT_AUTO_SCREEN_SCALE_FACTOR");
if (envVar != null) {
try {
screenScale.set(Double.parseDouble(envVar));
} catch (Exception ignored) {
}
}
}
// check system ENV variables.
if (screenScale.get() == 0) {
String envVar = System.getenv("QT_SCALE_FACTOR");
if (envVar != null) {
try {
screenScale.set(Double.parseDouble(envVar));
} catch (Exception ignored) {
}
}
}
// check system ENV variables.
if (screenScale.get() == 0) {
String envVar = System.getenv("GDK_SCALE");
if (envVar != null) {
try {
screenScale.set(Double.parseDouble(envVar));
} catch (Exception ignored) {
}
}
}
// check system ENV variables.
if (screenScale.get() == 0) {
String envVar = System.getenv("ELM_SCALE");
if (envVar != null) {
try {
screenScale.set(Double.parseDouble(envVar));
} catch (Exception ignored) {
}
}
}
OSUtil.DesktopEnv.Env env = OSUtil.DesktopEnv.get();
// sometimes the scaling-factor is set. If we have gsettings, great! otherwise try KDE
try {
// gsettings get org.gnome.desktop.interface scaling-factor
final ShellExecutor shellVersion = new ShellExecutor();
shellVersion.setExecutable("gsettings");
shellVersion.addArgument("get");
shellVersion.addArgument("org.gnome.desktop.interface");
shellVersion.addArgument("scaling-factor");
shellVersion.start();
String output = shellVersion.getOutput();
if (!output.isEmpty()) {
// DEFAULT icon size is 16. HiDpi changes this scale, so we should use it as well.
// should be: uint32 0 or something
if (output.contains("uint32")) {
String value = output.substring(output.indexOf("uint") + 7, output.length());
// 0 is disabled (no scaling)
// 1 is enabled (default scale)
// 2 is 2x scale
// 3 is 3x scale
// etc
double scalingFactor = Double.parseDouble(value);
if (scalingFactor >= 1) {
screenScale.set(scalingFactor);
}
// A setting of 2, 3, etc, which is all you can do with scaling-factor
// To enable HiDPI, use gsettings:
// gsettings set org.gnome.desktop.interface scaling-factor 2
}
}
} catch (Throwable ignore) {
}
if (OSUtil.DesktopEnv.isKDE()) {
// check the custom KDE override file
try {
File customSettings = new File("/usr/bin/startkde-custom");
if (customSettings.canRead()) {
List lines = FileUtil.readLines(customSettings);
for (String line : lines) {
String str = "export GDK_SCALE=";
int i = line.indexOf(str);
if (i > -1) {
String scale = line.substring(i + str.length());
double scalingFactor = Double.parseDouble(scale);
if (scalingFactor >= 1) {
screenScale.set(scalingFactor);
break;
}
}
}
}
} catch (Exception ignored) {
}
}
// System.err.println("screen scale: " + screenScale.get());
// System.err.println("screen DPI: " + screenDPI.get());
if (OSUtil.DesktopEnv.isKDE()) {
/*
*
* Looking in plasma-framework/src/declarativeimports/core/units.cpp:
// Scale the icon sizes up using the devicePixelRatio
// This function returns the next stepping icon size
// and multiplies the global settings with the dpi ratio.
const qreal ratio = devicePixelRatio();
if (ratio < 1.5) {
return size;
} else if (ratio < 2.0) {
return size * 1.5;
} else if (ratio < 2.5) {
return size * 2.0;
} else if (ratio < 3.0) {
return size * 2.5;
} else if (ratio < 3.5) {
return size * 3.0;
} else {
return size * ratio;
}
My ratio is 1.47674, that means I have no scaling at all when there is a 1.5 factor existing. Is it reasonable? Wouldn't it make more sense to use the factor the closest to the ratio rather than what is done here?
*/
File mainFile = new File("/usr/share/plasma/plasmoids/org.kde.plasma.private.systemtray/contents/config/main.xml");
if (mainFile.canRead()) {
List lines = FileUtil.readLines(mainFile);
boolean found = false;
int index;
for (final String line : lines) {
if (line.contains("")) {
found = true;
// have to get the "default" line value
}
String str = "";
if (found && (index = line.indexOf(str)) > -1) {
// this is our line. now get the value.
String substring = line.substring(index + str.length(), line.indexOf(" ", index));
if (MathUtil.isInteger(substring)) {
// Default icon size for the systray icons, it's an enum which values mean,
// Small, SmallMedium, Medium, Large, Huge, Enormous respectively.
// On low DPI systems they correspond to :
// 16, 22, 32, 48, 64, 128 pixels.
// On high DPI systems those values would be scaled up, depending on the DPI.
int imageSize = 0;
int imageSizeEnum = Integer.parseInt(substring);
switch (imageSizeEnum) {
case 0:
imageSize = 16;
break;
case 1:
imageSize = 22;
break;
case 2:
imageSize = 32;
break;
case 3:
imageSize = 48;
break;
case 4:
imageSize = 64;
break;
case 5:
imageSize = 128;
break;
}
if (imageSize > 0) {
double scaleRatio = screenDPI.get() / defaultDPI;
return (int) (scaleRatio * imageSize);
}
}
}
}
}
}
else {
if (OSUtil.Linux.isUbuntu() && OSUtil.DesktopEnv.isUnity(env)) {
// if we measure on ubuntu unity using a screen shot (using swing, so....) , the max size was 24, HOWEVER this goes from
// the top->bottom of the indicator bar -- and since it was swing, it uses a different rendering method and it (honestly)
// looks weird, because there is no padding for the icon. The official AppIndicator size is hardcoded...
// http://bazaar.launchpad.net/~indicator-applet-developers/libindicator/trunk.16.10/view/head:/libindicator/indicator-image-helper.c
return 22;
}
else {
// xfce is easy, because it's not a GTK setting for the size (xfce notification area maximum icon size)
if (env == OSUtil.DesktopEnv.Env.XFCE) {
String properties = OSUtil.DesktopEnv.queryXfce("xfce4-panel", null);
List propertiesAsList = Arrays.asList(properties.split(OS.LINE_SEPARATOR));
for (String prop : propertiesAsList) {
if (prop.startsWith("/plugins/") && prop.endsWith("/size-max")) {
// this is the property we are looking for (we just don't know which panel it's on)
String size = OSUtil.DesktopEnv.queryXfce("xfce4-panel", prop);
try {
return Integer.parseInt(size);
} catch (Exception e) {
LoggerFactory.getLogger(GtkTheme.class)
.error("Unable to get XFCE notification panel size for channel '{}', property '{}'",
"xfce4-panel", prop, e);
}
}
}
// default...
return 22;
}
// try to use GTK to get the tray icon size
final AtomicInteger traySize = new AtomicInteger();
if (Swt.isLoaded) {
} else {
GtkEventDispatch.dispatchAndWait(new Runnable() {
@Override
public
void run() {
Pointer screen = Gtk2.gdk_screen_get_default();
Pointer settings = null;
if (screen != null) {
settings = Gtk2.gtk_settings_get_for_screen(screen);
}
if (settings != null) {
PointerByReference pointer = new PointerByReference();
// https://wiki.archlinux.org/index.php/GTK%2B
// To use smaller icons, use a line like this:
// gtk-icon-sizes = "panel-menu=16,16:panel=16,16:gtk-menu=16,16:gtk-large-toolbar=16,16:gtk-small-toolbar=16,16:gtk-button=16,16"
// this gets icon sizes. On XFCE, ubuntu, it returns "panel-menu-bar=24,24"
// NOTE: gtk-icon-sizes is deprecated and ignored since GTK+ 3.10.
// A list of icon sizes. The list is separated by colons, and item has the form: size-name = width , height
GObject.g_object_get(settings, "gtk-icon-sizes", pointer.getPointer(), null);
Pointer value = pointer.getValue();
if (value != null) {
String iconSizes = value.getString(0);
String[] strings = new String[] {"panel-menu-bar=", "panel=", "gtk-large-toolbar=", "gtk-small-toolbar="};
for (String var : strings) {
int i = iconSizes.indexOf(var);
if (i >= 0) {
String size = iconSizes.substring(i + var.length(), iconSizes.indexOf(",", i));
if (MathUtil.isInteger(size)) {
traySize.set(Integer.parseInt(size));
return;
}
}
}
}
}
}
});
}
int i = traySize.get();
if (i != 0) {
return i;
}
}
}
// sane default
LoggerFactory.getLogger(GtkTheme.class).warn("Unable to get tray image size. Using default.");
return 24;
}
/**
* @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 getTextColor() {
final AtomicReference color = new AtomicReference(null);
GtkEventDispatch.dispatchAndWait(new Runnable() {
@SuppressWarnings("UnusedAssignment")
@Override
public
void run() {
Color c = null;
// the following method requires an offscreen widget to get the style information from.
// don't forget to destroy everything!
Pointer menu = null;
Pointer item = null;
try {
menu = Gtk2.gtk_menu_new();
item = Gtk2.gtk_image_menu_item_new_with_mnemonic("a");
Gtk2.gtk_container_add(menu, item);
Gtk2.gtk_widget_realize(menu);
Gtk2.gtk_widget_realize(item);
Gtk2.gtk_widget_show_all(menu);
GtkStyle style = Gtk2.gtk_rc_get_style(item);
style.read();
// this is the same color chromium uses (fg)
// https://chromium.googlesource.com/chromium/src/+/b3ca230ddd7d1238ee96ed26ea23e369f10dd655/chrome/browser/ui/libgtk2ui/gtk2_ui.cc#873
c = style.fg[GtkState.NORMAL].getColor();
color.set(c);
} finally {
Gtk2.gtk_widget_destroy(item);
Gtk2.gtk_widget_destroy(menu);
}
}
});
return color.get();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy