I am programming an overlay in Java showing information for multiple windows.
I need it to follow the window that it is tracking, and to do this I regularly take information about the current windows to update the position of my overlay. But I would also need to know if the windows are visible to hide the overlay if not. Ideally I should be able to do all this in real time but I'm afraid it's too performance-intensive.
I do all of this with JNA
public interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.load("user32", User32.class, W32APIOptions.DEFAULT_OPTIONS);
HWND FindWindow(String lpClassName, String lpWindowName);
int GetWindowRect(HWND handle, int[] rect);
boolean IsWindowVisible(HWND handle); // always true if window exist
}
public static int[] getWindowInformation(String windowName) {
int[] rectangle = {0,0,0,0};
HWND hwnd = User32.INSTANCE.FindWindow(null, windowName);
User32.INSTANCE.GetWindowRect(hwnd, rectangle);
boolean res = User32.INSTANCE.IsWindowVisible(hwnd);
System.out.println(windowName + " is visible ? " + res);
return rectangle;
}
Here is my code, you can see that I tried "IsWindowVisible" after read entirely the User32 API of JNA, but it doesn't do what I want.
Your intuition to use IsWindowVisible from User32 is good. JNA has actually implemented this in its WindowUtils class.
List<DesktopWindow> windows = WindowUtils.getAllWindows(true);
The boolean parameter is onlyVisibleWindows.
Note that "visible" here refers to the state of the window itself, not whether it is minimized (may have off-screen or "zero" coordinate) or "behind" vs. "on top" of other windows and thus visible to the user. From the IsWindowVisible documentation (note the second paragraph):
If the specified window, its parent window, its parent's parent window, and so forth, have the WS_VISIBLE style, the return value is nonzero. Otherwise, the return value is zero.
Because the return value specifies whether the window has the WS_VISIBLE style, it may be nonzero even if the window is totally obscured by other windows.
In order to determine whether the window is partially or fully obscured, you would have to evaluate its locAndSize rectangle relative to all windows "in front" of it using Z-order. You could do this on a pixel-by-pixel basis or just evaluate the corner points, depending on how you want to handle partially obscured windows.
The JNA getAllWindows() method returns a list (filtered by visibility) of JNA DesktopWindows encapsulating these fields:
private HWND hwnd;
private String title;
private String filePath;
private Rectangle locAndSize;
Internally you can see the implementation in the inner class W32WindowUtils, which uses a callback function sent to User32's EnumWindows function:
#Override
public List<DesktopWindow> getAllWindows(final boolean onlyVisibleWindows) {
final List<DesktopWindow> result = new LinkedList<DesktopWindow>();
final WNDENUMPROC lpEnumFunc = new WNDENUMPROC() {
#Override
public boolean callback(final HWND hwnd, final Pointer arg1) {
try {
final boolean visible = !onlyVisibleWindows
|| User32.INSTANCE.IsWindowVisible(hwnd);
if (visible) {
final String title = getWindowTitle(hwnd);
final String filePath = getProcessFilePath(hwnd);
final Rectangle locAndSize = getWindowLocationAndSize(hwnd);
result.add(new DesktopWindow(hwnd, title, filePath,
locAndSize));
}
} catch (final Exception e) {
// FIXME properly handle whatever error is raised
e.printStackTrace();
}
return true;
}
};
if (!User32.INSTANCE.EnumWindows(lpEnumFunc, null))
throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
return result;
}
I maintain the JNA-based OSHI project, and have implemented this cross-platform via new SystemInfo().getOperatingSystem().getDesktopWindows(), which uses the above implementation on Windows and adds information from other functions to get the window ordering. OSHI's return list includes these fields and the returned list is already sorted in order to aid you in determining user visibility:
private final long windowId;
private final String title;
private final String command;
private final Rectangle locAndSize;
private final long owningProcessId;
private final int order;
private final boolean visible;
Related
I have a wicket application on a page we have various forms for the same model split into separate tabs. What I need to do is whenever a tab is clicked check to see if a js variable tabDirty is set to true or false. If it is true I would launch a confirm prompt if okay then reset that form and move to the clicked tab. If cancel stay on that tab with keeping current changes.
I have this js for the warning nothing fancy
function warnOnChange(){
if(tabDirty){
decision = confirm('Leave?');
if(decision){
resetTab(); //sets tabDirty back to false
} else {
return false;
}
}
}
I have a super simple wicket behavior
public class WarnChangePromptOnClickBehavior extends Behavior {
#Override
public void bind(Component component) {
component.add(JQBehaviors.mouseClick(EditMerchant.WARN_ON_CHANGE));
}
}
and that behavior is added to the AjaxFallBackLink
AjaxTabbedPanel<CustomAjaxTab> tabbedPanel = new AjaxTabbedPanel<CustomAjaxTab>("tabbedPanel", tabList, new Model<>(0)) {
private static final long serialVersionUID = 1L;
#Override
protected WebMarkupContainer newLink(final String linkId, final int index) {
AjaxFallbackLink<Void> link = new AjaxFallbackLink<Void>(linkId) {
private static final long serialVersionUID = 1L;
#Override
public void onClick(final AjaxRequestTarget target) {
TabbedPanel<CustomAjaxTab> selectedTab = setSelectedTab(index);
CustomAjaxTab tab = tabList.get(index);
if (target != null) {
tab.getPanel(linkId);
target.add(selectedTab);
}
onAjaxUpdate(target);
}
};
link.add(new WarnChangePromptOnClickBehavior());
return link;
}
};
Current behavior with this is that if there is no change the tabs switch no prompt. If there is a change then I get the prompt. If okay tabDirty is reset and go to the next page clearing changes. Issue is that if I click cancel I still navigate to the next tab and lose changes. I know there is something in onClick I need to change but it is just not registering with me.
It is not that easy to intercept the JS event loop, especially when using Ajax requests.
Here is an approach that may do the job:
In warnOnChange() if dirty then call event.preventDefault() and event.stopImmediatePropagation(). This will tell the browser to not follow the link / make an Ajax call. Then show the confirmation dialog as you do now.
If the user presses Cancel then there is nothing more to do
If the use confirms then set dirty to false and do jQuery(event.target).triggerHandler(event.type), i.e. execute the same event (click) on the link. This time it won't be dirty and it will proceed with the Ajax call.
Not sure if this is the appropriate way to do this but I solved my issue like this:
Same old js just slightly modified to return what the user chose:
function warnOnChange(){
decision = true;
if(tabDirty){
decision = confirm('Leave?');
if(decision){
resetTab();
}
}
return decision;
}
Dumped the whole behavior code although I still think it could be used just not sure at the moment...
So to make this all work on the link I override the updateAjaxAttributesof the link with a precondition:
AjaxTabbedPanel<CustomAjaxTab> tabbedPanel = new AjaxTabbedPanel<CustomAjaxTab>("tabbedPanel", tabList, new Model<>(0)) {
private static final long serialVersionUID = 1L;
#Override
protected WebMarkupContainer newLink(final String linkId, final int index) {
AjaxFallbackLink<Void> link = new AjaxFallbackLink<Void>(linkId) {
private static final long serialVersionUID = 1L;
#Override
protected void updateAjaxAttributes( AjaxRequestAttributes attributes ) {
super.updateAjaxAttributes( attributes );
AjaxCallListener ajaxCallListener = new AjaxCallListener();
//very important to use the "return" if not then nothing happens with the response
ajaxCallListener.onPrecondition("return " + WARN_ON_CHANGE);
attributes.getAjaxCallListeners().add( ajaxCallListener );
}
#Override
public void onClick(final AjaxRequestTarget target) {
TabbedPanel<CustomAjaxTab> selectedTab = setSelectedTab(index);
CustomAjaxTab tab = tabList.get(index);
if (target != null) {
tab.getPanel(linkId);
target.add(selectedTab);
}
onAjaxUpdate(target);
}
};
link.add(new WarnChangePromptOnClickBehavior());
return link;
}
};
So I have just recently moved from LWJGL 2 to 3, and I am having a bit of difficulty with getting keyboard and mouse input. I am using this for camera movement and rotation, by the way.
My problem is that if I use the GLFW callbacks, the movement seems very choppy and slow. It isn't a consistent speed, and just doesn't feel right. Furthermore, when I press a key, for example w to move forward, there is about a half-second delay between the transition from GLFW_PRESS to GLFW_REPEAT This causes the camera to not move for the first half a second when the key is pressed.
By the way, I have a InputHandler class that has methods for keyDown, keyPressed, keyReleased, etc. I do not have all of the key checks in the one invoke method.The invoke method adds the key events to a list which stores the key, and an event state enum that can be one of DOWN, TAPPED, RELEASED, NONE. The class is heavily based on the response to this question
I have spent the past 2 hours trying to find a solution for this, and haven't found much. I did find some people using an alternative method to the callbacks though, like so:
if (glfwGetKey(window, key) == GLFW_PRESS)
and
if (glfwGetKey(windowm key) == GLFW_RELEASE)
But I cant find a way to use this to detect a single key tap, i.e. the GLFW_REPEAT state, because the glfwGetKey method can only detect GLFW_PRESS and GLFW_RELEASE.
I would greatly appreciate it if someone could tell me a way of either detecting a single key tap with the glfwGetKey method, or making the GLFW callbacks much less laggy, and more smooth.
Thanks :)
So I solved the problem, thanks to Brett Hale's suggestion of assuming that the key is down until a GLFW_RELEASE event is fired. This is my working implementation of this:
public final class InputHandler
{
private static long window;
private static final int KEYBOARD_SIZE = 512;
private static final int MOUSE_SIZE = 16;
private static int[] keyStates = new int[KEYBOARD_SIZE];
private static boolean[] activeKeys = new boolean[KEYBOARD_SIZE];
private static int[] mouseButtonStates = new int[MOUSE_SIZE];
private static boolean[] activeMouseButtons = new boolean[MOUSE_SIZE];
private static long lastMouseNS = 0;
private static long mouseDoubleClickPeriodNS = 1000000000 / 5; //5th of a second for double click.
private static int NO_STATE = -1;
protected static GLFWKeyCallback keyboard = new GLFWKeyCallback()
{
#Override
public void invoke(long window, int key, int scancode, int action, int mods)
{
activeKeys[key] = action != GLFW_RELEASE;
keyStates[key] = action;
}
};
protected static GLFWMouseButtonCallback mouse = new GLFWMouseButtonCallback()
{
#Override
public void invoke(long window, int button, int action, int mods)
{
activeMouseButtons[button] = action != GLFW_RELEASE;
mouseButtonStates[button] = action;
}
};
protected static void init(long window)
{
InputHandler.window = window;
resetKeyboard();
resetMouse();
}
protected static void update()
{
resetKeyboard();
resetMouse();
glfwPollEvents();
Engine.getInput();
}
private static void resetKeyboard()
{
for (int i = 0; i < keyStates.length; i++)
{
keyStates[i] = NO_STATE;
}
}
private static void resetMouse()
{
for (int i = 0; i < mouseButtonStates.length; i++)
{
mouseButtonStates[i] = NO_STATE;
}
long now = System.nanoTime();
if (now - lastMouseNS > mouseDoubleClickPeriodNS)
lastMouseNS = 0;
}
public static boolean keyDown(int key)
{
return activeKeys[key];
}
public static boolean keyPressed(int key)
{
return keyStates[key] == GLFW_PRESS;
}
public static boolean keyReleased(int key)
{
return keyStates[key] == GLFW_RELEASE;
}
public static boolean mouseButtonDown(int button)
{
return activeMouseButtons[button];
}
public static boolean mouseButtonPressed(int button)
{
return mouseButtonStates[button] == GLFW_RELEASE;
}
public static boolean mouseButtonReleased(int button)
{
boolean flag = mouseButtonStates[button] == GLFW_RELEASE;
if (flag)
lastMouseNS = System.nanoTime();
return flag;
}
public static boolean mouseButtonDoubleClicked(int button)
{
long last = lastMouseNS;
boolean flag = mouseButtonReleased(button);
long now = System.nanoTime();
if (flag && now - last < mouseDoubleClickPeriodNS)
{
lastMouseNS = 0;
return true;
}
return false;
}
}
Feel free to use this code if you want to. Just some notes though: the update method should be called every frame. Also, if you have the glfwPollEvents() somewhere else, which I think is likely, than you need to keep the order of reset keyboard/mouse, then poll, then getinput().
Edit:
My Engine.getInput() methos is just what tells nodes in the scene graph that require input, i.e. the player, to query that input.
While Kelan's answer works perfectly, I thought I would share my solution to this as I ran into the same issue while working on a game. I implemented my own Keyboard class and included the following methods
public static boolean isKeyPressed(int key)
{
return (GLFW.glfwGetKey(Game.window.handle, key) == KEY_PRESS);
}
public static boolean isKeyReleased(int key)
{
return (GLFW.glfwGetKey(Game.window.handle, key) == KEY_RELEASE);
}
Since GLFW saves the last event for each key, you just need to see if the last event was PRESS or RELEASE. GLFW doesn't report the REPEAT event using this method, but you can assume it is repeating until a RELEASE event is fired.
I'm a little late to the party, but I figured I could share what worked for me. I'm writting in C++, but I'll keep it as simple as possible.
I created an array of booleans with arbitrary size to store all keys pressed.
bool *keysArePressed = new bool[512];.
Instead of making a class for (I'm way too lazy for it) what I did was grab if the key was pressed with:
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
keysArePressed[key] = (glfwGetKey(window, key) == GLFW_PRESS);
}
void keyInput()
{
if(keyArePressed['W'])// 'W' has to be uppercase otherwise it won't work
{
/*Do whatever here*/
}
}
Then call keyInput in the main loop before the glfwPollEvents();.
It seems pretty easy to do multiple key presses as well.
The other answers were not the root cause of the problem I was experiencing. Drawing simple quads seemed to be blocking the input handling when swapping buffers. I simply glfwSwapInterval(0) to turn off v-sync which isn't necessarily a good practise, but at the current stage of my program, I would prefer some screen tear to bad input lag, and that seemed to fix it.
I'm attempting to use the Chinese (Traditional, Taiwan), Chinese (Traditional) - New Phonetic keyboard on an English (US) Windows 7. When I type into a Java Swing-based text area, the candidate list is showing up on the bottom-right of my screen, regardless of where the text area is positioned on the screen. When I'm not using a Java program, the candidate list shows up in the correct place, directly under the text I'm typing.
Has anybody else run into this behavior and found a workaround for it? I haven't found other reports of this behavior online.
Thanks in advance for any help!
System Details:
Microsoft New Phonetic IME 10.1 (10.1.7601.0)
Chinese input mode
Either half or full shape (doesn't matter)
Standard keyboard layout
Windows 7, 64-bit (same happens on 32-bit)
Affects Java 6, 7, and 8
Affects Swing and JavaFX
I did eventually find similar problems reported, but most of them were related to Japanese IMEs and have already been fixed in the JDK. I didn't find any reports specific to this Chinese IME, but I did find a workaround in case it's useful for others.
The brief summary is that I listen for the WM_IME_STARTCOMPOSITION Windows message. When I see that, I locate the IME candidate window, move it to the location I want, and override its WindowProc to prevent further moves. During composition I also listen for WM_KEYDOWN events because I no longer received any WM_IME messages while the user was composing, even though the candidate window closes and gets recreated several times throughout composition. When I receive the WM_IME_ENDCOMPOSITON message, I stop listening for WM_KEYDOWN messages.
As an alternative approach, I tried sending a WM_IME_CONTROL message with the IMC_SETCANDIDATEPOS command to move the candidate window, but this particular IME seems to ignore it.
I used JNA (https://github.com/twall/jna) to override the WindowProc on both the window containing my text area as well as the IME candidate window.
The code snippet below is an example of the workaround.
hwndMain = WIN_INSTANCE.FindWindow(null, "Main Window");
// Note the existing WindowProc so we can restore it later.
prevWndProc = new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndMain, WinUser.GWL_WNDPROC));
// Register a new WindowProc that we will use to intercept IME messages.
mainListener = new WindowsCallbackListener() {
#Override
public int callback(int hWnd, int uMsg, int uParam, int lParam) {
if (uMsg == WM_IME_STARTCOMPOSITION || (imeComposing && uMsg == WM_KEYDOWN)) {
imeComposing = true;
final WinDef.HWND hwndIme = WIN_INSTANCE.FindWindow("SYSIME7_READING_UI", null);
if (hwndIme != null && !hwndIme.equals(imeWindow)) {
// We found an IME window that is not the same as the last one. We assume the last one was
// closed. We need to register our callback with the new window.
imeWindow = hwndIme;
final Point imeWindowLocation = getImeWindowLocation();
WIN_INSTANCE.MoveWindow(hwndIme, imeWindowLocation.x, imeWindowLocation.y, 0, 0, true);
final BaseTSD.LONG_PTR prevWndProcIme =
new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndIme, WinUser.GWL_WNDPROC));
imeListener = new WindowsCallbackListener() {
#Override
public int callback(int hWnd, int uMsg, int uParam, int lParam) {
if (uMsg == WM_WINDOWPOSCHANGING) {
final WindowPosition pos = new WindowPosition(new Pointer((long)lParam));
pos.read();
pos.flags |= SWP_NOMOVE;
pos.write();
}
// Call the window's actual WndProc so the events get processed.
return WIN_INSTANCE.CallWindowProc(prevWndProcIme, hWnd, uMsg, uParam, lParam);
}
};
// Set the WndProc function to use our callback listener instead of the window's one.
WIN_INSTANCE.SetWindowLong(hwndIme, WinUser.GWL_WNDPROC, imeListener);
}
}
else if (uMsg == WM_IME_ENDCOMPOSITION) {
// We can discard the IME listener since its window is closed. If another one gets opened, we'll
// create a new listener.
imeListener = null;
imeComposing = false;
}
// Call the window's previous WindowProc so the event continues to get processed.
return WIN_INSTANCE.CallWindowProc(prevWndProc, hWnd, uMsg, uParam, lParam);
}
};
// Set the WindowProc function to use our WindowProc so the event continues to get processed.
WIN_INSTANCE.SetWindowLong(hwndMain, WinUser.GWL_WNDPROC, mainListener);
The code above assumes the following definitions:
private static final MyUser32 WIN_INSTANCE = MyUser32.INSTANCE;
private static final int SWP_NOMOVE = 2;
private static final int WM_KEYDOWN = 256;
private static final int WM_WINDOWPOSCHANGING = 70;
private static final int WM_IME_ENDCOMPOSITION = 270;
private static final int WM_IME_STARTCOMPOSITION = 269;
private WinDef.HWND hwndMain;
private BaseTSD.LONG_PTR prevWndProc;
// Keep references to these listeners so they don't get garbage-collected.
private WindowsCallbackListener mainListener;
private WindowsCallbackListener imeListener;
private boolean imeComposing;
private WinDef.HWND imeWindow;
public static class WindowPosition extends Structure {
public WinDef.HWND hwnd;
public WinDef.HWND hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
public WindowPosition(Pointer p) {
super(p);
}
#Override
protected List getFieldOrder() {
return Arrays.asList("hwnd", "hwndInsertAfter", "x", "y", "cx", "cy", "flags");
}
}
private interface MyUser32 extends User32 {
MyUser32 INSTANCE = (MyUser32) Native.loadLibrary("user32", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
int CallWindowProc(BaseTSD.LONG_PTR prevWndProc, int hWnd, int uMsg, int uParam, int lParam);
int SetWindowLong(HWND hwnd, int nIndex, BaseTSD.LONG_PTR listener) throws LastErrorException;
int SetWindowLong(HWND hwnd, int nIndex, WindowsCallbackListener listener) throws LastErrorException;
}
private interface WindowsCallbackListener extends Callback, StdCall {
int callback(int hWnd, int Msg, int wParam, int lParam);
}
Suppose i run my program in eclipse and it'll switch to mozilla window(it is running simultaneously). Similarly when we click a icon in task bar. I have tried Robot class to stimulate click but that's hard-coding coordinates into the program and i don't want to do that.
Any suggestion how i can do this. Thanks.
As far as I understand things, you cannot switch to another running window by name using just core Java. You can swap windows by sending alt-tab keystrokes via a Robot, but this won't bring up a named window. To do this, I recommend using JNI, JNA or some OS-specific utility programming language, such as AutoIt if this were a Windows issue.
For example, using JNA, you could do something like this:
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary;
public class SetForgroundWindowUtil {
public interface User32 extends StdCallLibrary {
User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
interface WNDENUMPROC extends StdCallCallback {
boolean callback(Pointer hWnd, Pointer arg);
}
boolean EnumWindows(WNDENUMPROC lpEnumFunc, Pointer arg);
int GetWindowTextA(Pointer hWnd, byte[] lpString, int nMaxCount);
int SetForegroundWindow(Pointer hWnd);
Pointer GetForegroundWindow();
}
public static boolean setForegroundWindowByName(final String windowName,
final boolean starting) {
final User32 user32 = User32.INSTANCE;
return user32.EnumWindows(new User32.WNDENUMPROC() {
#Override
public boolean callback(Pointer hWnd, Pointer arg) {
byte[] windowText = new byte[512];
user32.GetWindowTextA(hWnd, windowText, 512);
String wText = Native.toString(windowText);
// if (wText.contains(WINDOW_TEXT_TO_FIND)) {
if (starting) {
if (wText.startsWith(windowName)) {
user32.SetForegroundWindow(hWnd);
return false;
}
} else {
if (wText.contains(windowName)) {
user32.SetForegroundWindow(hWnd);
return false;
}
}
return true;
}
}, null);
}
public static void main(String[] args) {
boolean result = setForegroundWindowByName("Untitled", true);
System.out.println("result: " + result);
}
}
I don't know any OS-agnostic way of solving this problem.
I Believe you're question may have been answered here: Active other process's window in Java.
Other than that, JNA would be your best bet for doing this, core java, or java in general doesn't allow
interaction with the operating system directly but JNA does.
The only other way i can think of is call the application, for example chrome with command like arguments(if it takes any) with
try{
Desktop.getDesktop().open(new File("Location\\to\\the\\program.exe"));
} catch(IOException ex){
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
edit:
use this method to call it with parameters
Process process = new ProcessBuilder("Location\\to\\the\\program.exe",
"param1","param2").start();
On linux, try this (this is kotlin code):
val window = "The window name you want to show"
Runtime.getRuntime().exec(arrayOf("/bin/bash", "-c", "wmctrl -a \"$window\""))
Works on Elementary (Loki)
How can I detect if a user has a retina display in Java? I am already aware of detecting the scale factor using Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor"), but java won't let me convert the returned value into an int. I'm wondering how I can convert that into an int, or another way to detect retina displays.
I would get the value this way -
public static boolean hasRetinaDisplay() {
Object obj = Toolkit.getDefaultToolkit()
.getDesktopProperty(
"apple.awt.contentScaleFactor");
if (obj instanceof Float) {
Float f = (Float) obj;
int scale = f.intValue();
return (scale == 2); // 1 indicates a regular mac display.
}
return false;
}
Beware that users may have multiple displays! What does “detect a Retina display” mean in this scenario?
For most purposes, you are interested in rendering an image onto a GUI component. You therefore need to detect what display the component is on.
Luckily java.awt.Component has a getGraphicsConfiguration method that gives us the necessary information.
However, Java 8 (and 7) and Java 9 require different handling: Java 9 exposes the necessary information directly via the graphics device’s default transform. Java 7 and 8 also expose this transformation, but it is always set to an identity transformation (i.e. no transformation), even for a Retina display.
For Java < 9, we need to use reflection to query macOS specific fields in the OpenJDK classes that implement graphics for Mac.
The following class implements the necessary checks for Retina displays and works for Java 8 as well as Java 9. Java 7 might also work with trivial changes but I didn’t test it.
package me.klmr.ui;
import java.awt.*;
import java.lang.reflect.Method;
public final class Device {
private enum JavaVersion {
V8,
V9
}
private static final JavaVersion JAVA_VERSION = getJavaVersion();
private static JavaVersion getJavaVersion() {
final String versionString = System.getProperty("java.version");
if (versionString.startsWith("1.8")) return JavaVersion.V8;
if (versionString.startsWith("9.")) return JavaVersion.V9;
throw new RuntimeException("Unsupported Java version");
}
public static GraphicsConfiguration getCurrentConfiguration(final Component component) {
final GraphicsConfiguration graphicsConfiguration = component.getGraphicsConfiguration();
if (graphicsConfiguration == null) {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
} else {
return graphicsConfiguration;
}
}
public static GraphicsDevice getCurrentDevice(final Component component) {
final GraphicsConfiguration graphicsConfiguration = component.getGraphicsConfiguration();
if (graphicsConfiguration == null) {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
} else {
return graphicsConfiguration.getDevice();
}
}
public static boolean isOnRetinaDisplay(final Component component) {
switch (JAVA_VERSION) {
case V8: return isOnRetinaDisplayJava8(component);
case V9: return isOnRetinaDisplayJava9(component);
default: throw new AssertionError("Unreachable");
}
}
public static double getDisplayScalingFactor(final Component component) {
switch (JAVA_VERSION) {
case V8: return getDisplayScalingFactorJava8(component);
case V9: return getDisplayScalingFactorJava9(component);
default: throw new AssertionError("Unreachable");
}
}
private static boolean isOnRetinaDisplayJava8(final Component component) {
final GraphicsDevice device = getCurrentDevice(component);
try {
final Method getScaleFactorMethod = device.getClass().getMethod("getScaleFactor");
final Object scale = getScaleFactorMethod.invoke(device);
return scale instanceof Integer && ((Integer) scale).intValue() == 2;
} catch (ReflectiveOperationException e) {
return false;
}
}
private static boolean isOnRetinaDisplayJava9(final Component component) {
return ! getCurrentConfiguration(component).getDefaultTransform().isIdentity();
}
private static double getDisplayScalingFactorJava8(final Component component) {
return isOnRetinaDisplayJava8(component) ? 2.0 : 1.0;
}
private static double getDisplayScalingFactorJava9(final Component component) {
return getCurrentConfiguration(component).getDefaultTransform().getScaleX();
}
}
In practice, moving a dialog from one screen to another will cause components to re-render. If the component’s rendering code uses the above class to find out the correct resolution, they will render correctly regardless of which display they are currently on.
For Java 9, this also works:
public static boolean isMacRetinaDisplay() {
final GraphicsConfiguration gfxConfig = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
final AffineTransform transform = gfxConfig.getDefaultTransform();
return !transform.isIdentity();
}
You could alternatively inspect the scale factor of the transform and check if it is equal to 2 and fall back to non-retina otherwise.