7
votes

Windows 8/10 has started to include a slider for how much GUI elements should scale, right click on desktop -> display. For a colleague with a laptop 4k-screen it's 250% while another colleague using the same resolution on a 4k 28" screen it's 150%.

How do I read that value programmatically? I need to adjust some graphics so it looks the same on all screens.

I'm working in Java on a Eclipse RCP application, but a way that uses C or C++ through JNI works too. I've been looking around but can't find anything.

6
thanks :) didn't find that when searching. - dutt

6 Answers

5
votes

java.awt.Toolkit.getDefaultToolkit().getScreenResolution() see API

Returns the screen resolution in dots-per-inch.

Assumen your 100% is 96pixel you're able to calculate your scaling factor.

1
votes

Maybe this answer from here might help you:

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
public enum DeviceCap
{
    VERTRES = 10,
    DESKTOPVERTRES = 117,

    // http://pinvoke.net/default.aspx/gdi32/GetDeviceCaps.html
}  


private float getScalingFactor()
{
    Graphics g = Graphics.FromHwnd(IntPtr.Zero);
    IntPtr desktop = g.GetHdc();
    int LogicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.VERTRES);
    int PhysicalScreenHeight = GetDeviceCaps(desktop, (int)DeviceCap.DESKTOPVERTRES); 

    float ScreenScalingFactor = (float)PhysicalScreenHeight / (float)LogicalScreenHeight;

    return ScreenScalingFactor; // 1.25 = 125%
}
1
votes

Adopting @seth-kitchen's example using JNA, this is possible, even on older JDKs like Java 8.

Note: The JNA portion of this technique doesn't work well on JDK11. A comment in the code explains how it will fallback to the Toolkit technique.

public static int getScaleFactor() {
    WinDef.HDC hdc = GDI32.INSTANCE.CreateCompatibleDC(null);
    if (hdc != null) {
        int actual = GDI32.INSTANCE.GetDeviceCaps(hdc, 10 /* VERTRES */);
        int logical = GDI32.INSTANCE.GetDeviceCaps(hdc, 117 /* DESKTOPVERTRES */);
        GDI32.INSTANCE.DeleteDC(hdc);
        // JDK11 seems to always return 1, use fallback below
        if (logical != 0 && logical/actual > 1) {
            return logical/actual;
        }
    }
    return (int)(Toolkit.getDefaultToolkit().getScreenResolution() / 96.0);
}

This above solution grabs the default display for simplicity purposes. You can enhance it to get the display of the current Window by finding the current Window handle (through Native.getComponentPointer(Component) or by title using User32.INSTANCE.FindWindow(...)) and then using CreateaCompatibleDC(GetDC(window)).

1
votes

Although the question asks about Windows scaling, the accepted solution(s), do not work properly with Linux running JDK11.

The below technique uses JNA to detect the screen scale factor on Linux, which is compatible with Linux desktops exposing the Gtk (Gimp Toolkit) libraries.

The snippet contains several Gtk version-dependent workarounds as the Gtk API exposing scaling information has changed several times. Furthermore, information may be inaccurate for specific use-cases (mixed standard and HiDPI monitors on the same desktop) as scaling is monitor-specific.

Warning: If Java fixes this behavior -- or equally if Gtk changes their API -- in a future version, this solution will need to be updated. Furthermore, this solution requires Linux-only components: reflection is required (included) for cross-compilation to succeed.

Usage: GtkUtilities.getScaleFactor()

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Pointer;

import java.lang.reflect.Method;

public class GtkUtilities {
    /**
    * Initializes Gtk2/3 and returns the desktop scaling factor, usually 1.0 or 2.0
    */
    public static double getScaleFactor() {
        GTK gtkHandle = getGtkInstance();
        if (gtkHandle != null && gtkHandle.gtk_init_check(0, null)) {
            System.out.println("Initialized Gtk");

            if (gtkHandle instanceof GTK2) {
                return getGtk2ScaleFactor((GTK2)gtkHandle);
            } else {
                return getGtk3ScaleFactor((GTK3)gtkHandle);
            }
        } else {
            System.err.println("An error occurred initializing the Gtk library");
        }
        return 0;
    }

    private static GTK getGtkInstance() {
        System.out.println("Finding preferred Gtk version...");
        switch(getGtkMajorVersion()) {
            case 2:
                return GTK2.INSTANCE;
            case 3:
                return GTK3.INSTANCE;
            default:
                System.err.println("Not a compatible Gtk version");
        }
        return null;
    }

    /**
    * Get the major version of Gtk (e.g. 2, 3)
    * UNIXToolkit is unavailable on Windows or Mac; reflection is required.
    * @return Major version if found, zero if not.
    */
    private static int getGtkMajorVersion() {
        try {
            Class toolkitClass = Class.forName("sun.awt.UNIXToolkit");
            Method versionMethod = toolkitClass.getDeclaredMethod("getGtkVersion");
            Enum versionInfo = (Enum)versionMethod.invoke(toolkitClass);
            Method numberMethod = versionInfo.getClass().getDeclaredMethod("getNumber");
            int version = ((Integer)numberMethod.invoke(versionInfo)).intValue();
            System.out.println("Found Gtk " + version);
            return version;
        } catch(Throwable t) {
            System.err.println("Could not obtain GtkVersion information from UNIXToolkit: {}", t.getMessage());
        }
        return 0;
    }

    private static double getGtk2ScaleFactor(GTK2 gtk2) {
        Pointer display = gtk2.gdk_display_get_default();
        System.out.println("Gtk 2.10+ detected, calling \"gdk_screen_get_resolution\"");
        Pointer screen = gtk2.gdk_display_get_default_screen(display);
        return gtk2.gdk_screen_get_resolution(screen) / 96.0d;
    }

    private static double getGtk3ScaleFactor(GTK3 gtk3) {
        Pointer display = gtk3.gdk_display_get_default();
        int gtkMinorVersion = gtk3.gtk_get_minor_version();
        if (gtkMinorVersion < 10) {
            System.err.println("Gtk 3.10+ is required to detect scaling factor, skipping.");
        } else if (gtkMinorVersion >= 22) {
            System.out.println("Gtk 3.22+ detected, calling \"gdk_monitor_get_scale_factor\"");
            Pointer monitor = gtk3.gdk_display_get_primary_monitor(display);
            return gtk3.gdk_monitor_get_scale_factor(monitor);
        } else if (gtkMinorVersion >= 10) {
            System.out.println("Gtk 3.10+ detected, calling \"gdk_screen_get_monitor_scale_factor\"");
            Pointer screen = gtk3.gdk_display_get_default_screen(display);
            return gtk3.gdk_screen_get_monitor_scale_factor(screen, 0);
        }
        return 0;
    }

    /**
    * Gtk2/Gtk3 wrapper
    */
    private interface GTK extends Library {
        // Gtk2.0+
        boolean gtk_init_check(int argc, String[] argv);
        Pointer gdk_display_get_default();
        Pointer gdk_display_get_default_screen (Pointer display);
    }

    private interface GTK3 extends GTK {
        GTK3 INSTANCE = Native.loadLibrary("gtk-3", GTK3.class);

        // Gtk 3.0+
        int gtk_get_minor_version ();

        // Gtk 3.10-3.21
        int gdk_screen_get_monitor_scale_factor (Pointer screen, int monitor_num);

        // Gtk 3.22+
        Pointer gdk_display_get_primary_monitor (Pointer display);
        int gdk_monitor_get_scale_factor (Pointer monitor);
    }

    private interface GTK2 extends GTK {
        GTK2 INSTANCE = Native.loadLibrary("gtk-x11-2.0", GTK2.class);

        // Gtk 2.1-3.0
        double gdk_screen_get_resolution(Pointer screen);
    }
}
1
votes

The following works for me. You need to get window actual device(for multi-monitor enviroment) and calc it's bound dimension agains display mode dimension.

public static double getWindowScale(Window window) {
    GraphicsDevice device = getWindowDevice(window);
    return device.getDisplayMode().getWidth() / (double) device.getDefaultConfiguration().getBounds().width;
}

public static GraphicsDevice getWindowDevice(Window window) {
    Rectangle bounds = window.getBounds();
    return asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()).stream()

            // pick devices where window located
            .filter(d -> d.getDefaultConfiguration().getBounds().intersects(bounds))

            // sort by biggest intersection square
            .sorted((f, s) -> Long.compare(//
                    square(f.getDefaultConfiguration().getBounds().intersection(bounds)),
                    square(s.getDefaultConfiguration().getBounds().intersection(bounds))))

            // use one with the biggest part of the window
            .reduce((f, s) -> s) //

            // fallback to default device
            .orElse(window.getGraphicsConfiguration().getDevice());
}

public static long square(Rectangle rec) {
    return Math.abs(rec.width * rec.height);
}
0
votes

This works on macOS and Windows with Java 17

public static Window getWindow(Component component) {
    return component instanceof Window ? (Window) component : getWindow(component.getParent());
}

@Override
public synchronized void paintComponent(Graphics g) {
    super.paintComponent(g);

    float displayScaling = (float) getWindow(this).getGraphicsConfiguration().getDefaultTransform().getScaleX();
}