0
public class MoveCursor extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(Stage stage) throws Exception {
        Button b = new Button("X");
        b.setOnAction((event) -> {
            try {
                new Robot().mouseMove(1000, 1000);
            } catch (AWTException ex) {
                Logger.getLogger(MoveCursor.class.getName()).log(Level.SEVERE, null, ex);
            }
        });
        stage.setScene(new Scene(b));
        stage.show();
    }
}
  1. this should move the mouse cursor over to position 1000x1000 on my/your screen (i.e. 1000px away from the absolute 0x0 coordinate of my screen... and should wind up always in the same position). ... it doesn't... it depends on where the button is. why?

  2. what's causing this?

... this used to work on an old laptop. i.e. windows 10, 1 intel graphics card, 1 nvidia graphics card, 1920x1080 display.

I'm currently using Windows 10, 2 graphics cards in SLI on a 3840x2160 resolution scaled at 175%.

adjusting the scaling factor doesn't seem to do anything.

... i'm also using jdk8.

using the -Dsun.java2d.dpiaware=true or -Dsun.java2d.dpiaware=false vm options doesn't seem to do anything.

[edit] ... for quesiton duplicate issue... it's not a duplicate of that. the fix in that question is useless.

THIS DOES NOT WORK!

 for(int count = 0;(MouseInfo.getPointerInfo().getLocation().getX() != x || 
        MouseInfo.getPointerInfo().getLocation().getY() != y) &&
        count < 100; count++) {
    new Robot().mouseMove(x, y);
  }
EverNight
  • 964
  • 7
  • 16
  • 2
    Possible duplicate of [Java robot.mouseMove(x, y) not producing correct results](https://stackoverflow.com/questions/48837741/java-robot-mousemovex-y-not-producing-correct-results) – Turamarth Mar 30 '18 at 16:04
  • it's not a duplicate of that. The "fix" in that question simply moves my mouse position to the same location 20000 times. – EverNight Mar 30 '18 at 16:38

4 Answers4

1

A full but slightly swampy-feeling virtual mouse delta solution is at the bottom. Doesn't need libraries, tested on Java 8/9/11 on Windows 10 with desktop scaling 200%.


This solution by Tejas Shah works for me 99%. (I'm still looking for my jaw, it must be here somewhere.)

Here's my commented Java adaptation from what I suspect to be Kotlin. It works on my 4K 200% Desktop in Java 8 and 9. My diagonal mouse move loop still looks a wee bit weird, but the mouse goes 99% where you want it instead of jumping around.

It's not 100%! On Java 8, when I keep putting the mouse at a fixed position every 20 ms on Swing thread, the mouse will sometimes be off by 1 pixel if I have moved it a certain way. This is probably a rounding issue. (Using Math.round below does not solve it.) Similarly, on Java 9 the mouse will either be in that place, or it will keep jumping between that place and one pixel to the left/top of that place.

To get the correct scaling factor (e.g. 2 on a Windows system at 200% scaling), you can use my crappy but very elaborate GUIScaling class, which also supports custom graphics scaling, mouse scaling to and from, etc., and keeps it all consistent on Java 8&9 (or other) between 100% and 200% (or other) scaling.

final private Robot robot;


private void mouseMoveFixed(final int x,
                            final int y) {

    final double scaleFactor = GUIScaling.GUISCALINGFACTOR_SYSTEM; // If your Windows system is at 200% scaling, put a 2 here.

    final double modFactor = 1 - (1 / scaleFactor);

    final Point origin = MouseInfo.getPointerInfo().getLocation();

    final int deltaX = x - origin.x;
    final int deltaY = y - origin.y;

    final int finalX = (int) (x - deltaX * modFactor); // I don't know if this needs to be rounded.
    final int finalY = (int) (y - deltaY * modFactor); // I couldn't spot a difference if Math.round() was used.

    robot.mouseMove(finalX, finalY);
}

In the future, if ever you need a "virtual mouse" delta, just call this static method:

JavaRobotMouseDelta.calcVirtualMouseDelta()

It will return the delta from screen center and set the mouse to that location. If there is no delta to be returned, the method returns null.

If desktop scaling is not 100%, the method will use different approaches depending on Java 8, 9, and 11+. All of which are imprecise and thus will give a slightly swampy mouse feeling. But it's better than nothing, and it's all encapsulated. Enjoy!

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;




/**
 * [v1, 2022-01-15 12!00 UTC] by Dreamspace President
 * <p>
 * Uses 2 values from my GUIScaling class, see https://stackoverflow.com/questions/43057457/jdk-9-high-dpi-disable-for-specific-panel/46630710#46630710
 */
final public class JavaRobotMouseDelta {


    /**
     * @return NULL if there is no delta and thus no action should be taken.
     */
    public static Dimension calcVirtualMouseDelta() {

        final int centerX;
        final int centerY;

        int deltaX;
        int deltaY;

        {
            final PointerInfo pointerInfo = MouseInfo.getPointerInfo();

            final float halfScaled = (float) (2 * GUIScaling.GUISCALINGFACTOR_FONTINCUSTOMGRAPHICSCONTEXT);

            final DisplayMode displayMode = pointerInfo.getDevice().getDisplayMode();
            centerX = Math.round(displayMode.getWidth() / halfScaled);
            centerY = Math.round(displayMode.getHeight() / halfScaled);

            final Point mouseLocation = pointerInfo.getLocation();
            deltaX = mouseLocation.x - centerX;
            deltaY = mouseLocation.y - centerY;
        }


        if (GUIScaling.GUISCALINGFACTOR_SYSTEM == 1) {

            if (deltaX == 0 && deltaY == 0) {
                // Ignoring mouse move event caused by Robot centering call.
                return null;
            }

            ROBOT.mouseMove(centerX, centerY);

            return new Dimension(deltaX, deltaY);

        } else {


            // The following comments and logic are for desktop scaling 200%.
            // I did not yet bother trying to figure this out for other values.


            // Java 11 etc. finally have a FIXED Robot.mouseMove()! HOWEVER: You will be blindfolded,
            // so you get a pixel offset of 1 every once in a while because moving the mouse from center to the LEFT OR TOP
            // will INSTANTLY cause a mouse event with a new position. Moving it right or bottom will NOT cause an event,
            // because e.g. a 3840x2160 (twice 1920x1080) display will ALL THROUGHOUT inside Java have HALVED coordinates,
            // e.g. you set window width 800, reality will be 1600, but you will have getWidth 800, and the mouse coordinates
            // will also be treated like this - EVEN FOR THE ROBOT!

            // Java 8/9 (and below) have a broken Robot.mouseMove() under Windows!=100%, but thanks to Tejas Shah's solution below,
            // we can work around that. Without JNA or looping tricks.

            // HOWEVER: We need to lose 1 pixel of motion. And we need to thoroughly rape the resulting delta DEPENDING ON JAVA VERSION.

            // Rant: I want an additional mouseMove method that uses TRUE pixels. And I want access to the HARDWARE MOUSE DELTA,
            // scaled by system (acceleration) as well as unscaled. And I want access to scanCode (KeyEvent) without Reflection
            // and on ALL systems, not just Windows! ... Rant over.

            if (deltaX < 0) {
                deltaX += 1;
            } else if (deltaX > 0) {
                deltaX -= 1;
            }
            if (deltaY < 0) {
                deltaY += 1;
            } else if (deltaY > 0) {
                deltaY -= 1;
            }


            if (deltaX == 0 && deltaY == 0) {
                // Ignoring mouse move event caused by Robot centering call.
                return null;
            }

            final int deltaXRaped;
            final int deltaYRaped;

            if (JAVAVERSIONMAJOR >= 11) {

                deltaXRaped = deltaX < 0 ? deltaX : Math.round(deltaX * (1 + RAND.nextFloat() * 0.75f));
                deltaYRaped = deltaY < 0 ? deltaY : Math.round(deltaY * (1 + RAND.nextFloat() * 0.75f));

                ROBOT.mouseMove(centerX, centerY);

            } else if (JAVAVERSIONMAJOR >= 9) {

                deltaXRaped = deltaX > 0 ? deltaX : Math.round(deltaX * (1 + RAND.nextFloat() * 0.75f));
                deltaYRaped = deltaY > 0 ? deltaY : Math.round(deltaY * (1 + RAND.nextFloat() * 0.75f));

                mouseMoveFixed(centerX, centerY);

            } else {

                deltaXRaped = deltaX < 0 ? deltaX : Math.round(deltaX * (1 + RAND.nextFloat() * 0.75f));
                deltaYRaped = deltaY < 0 ? deltaY : Math.round(deltaY * (1 + RAND.nextFloat() * 0.75f));

                mouseMoveFixed(centerX, centerY);

            }

            return new Dimension(deltaXRaped, deltaYRaped);

        }

    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // UTILITIES NEEDED ABOVE.
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    final public static int JAVAVERSIONMAJOR = getJavaVersionMajor();

    final public static Robot ROBOT = getRobot();

    final public static Random RAND = new Random();


    private static Robot getRobot() {

        try {
            return new Robot();
        } catch (AWTException e) {
            e.printStackTrace();
            System.exit(-1);
            throw new Error();
        }
    }


    // From https://stackoverflow.com/a/2591122/3500521 (Aaron Digulla)
    private static int getJavaVersionMajor() {

        String version = System.getProperty("java.version");
        if (version.startsWith("1.")) {
            version = version.substring(2, 3);
        } else {
            int dot = version.indexOf(".");
            if (dot != -1) {
                version = version.substring(0, dot);
            }
        }
        return Integer.parseInt(version);
    }


    // Converted from the ?Kotlin? original at https://gist.github.com/tejashah88/201daacada3a785f86b8cf069ead63f5 (Tejas Shah)
    public static void mouseMoveFixed(final int x,
                                      final int y) {

        final double scaleFactor = GUIScaling.GUISCALINGFACTOR_SYSTEM; // If your Windows system is at 200% scaling, put a 2 here.

        final double modFactor = 1 - (1 / scaleFactor);

        final Point origin = MouseInfo.getPointerInfo().getLocation();

        final int deltaX = x - origin.x;
        final int deltaY = y - origin.y;

        int finalX = (int) (x - deltaX * modFactor); // I don't know if this needs to be rounded.
        int finalY = (int) (y - deltaY * modFactor); // I couldn't spot a difference if Math.round() was used.

        ROBOT.mouseMove(finalX, finalY);
    }


    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // BONUS, YOU CAN DELETE THIS.
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    final private static Cursor INVISIBLE_CURSOR;

    static {
        final BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
        INVISIBLE_CURSOR = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg,
                                                                          new Point(0, 0),
                                                                          "invisibleCursor");
    }


    public static void setMouseBusy(final Component windowOrComponent) {

        if (windowOrComponent != null) {
            windowOrComponent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        }
    }


    public static void setMouseIdle(final Component windowOrComponent) {

        if (windowOrComponent != null) {
            windowOrComponent.setCursor(Cursor.getDefaultCursor());
        }
    }


    public static void setMouseInvisible(final Component windowOrComponent) {

        if (windowOrComponent != null) {
            windowOrComponent.setCursor(INVISIBLE_CURSOR);
        }
    }


    public static void setMouseCrossHair(final Component windowOrComponent) {

        if (windowOrComponent != null) {
            windowOrComponent.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
        }
    }

}
Dreamspace President
  • 1,060
  • 13
  • 33
  • 1
    Thanks for the thoroughly researched answer. You're pretty much the only person in roughly 4 years to do so. Will definitely try out your solution if I ever go back down that rabbit hole again. On a related note, after resisting the urge to apply percussive maintenance on my laptop throughout the week I posted this question, I set about working out a solution which prevented "blind" pixel-based robot mouse movements since scrolling was an issue as well. The workaround implied forcing event triggers on whatever objects I had to work with and it was relatively easy going from there. – EverNight Jan 18 '22 at 09:42
  • 1
    The problem I was working on had to deal with javaFx's webView and webEngine components, so pretty much HTML stuff... therefore easy to trigger clicks and whatnot. But your solution can theoretically be applied to desktop software applications for machine learning which is another thing I've got lying around in my backlog ... so yeah... you deserve a beer. – EverNight Jan 18 '22 at 09:46
0

Yes, this is a known bug for high resolution screens. See https://bugs.openjdk.java.net/browse/JDK-8186063 for more details.

The best solution would be to run the command in a loop until its gets to the right coordinates.

The code for such a thing would be as follows:

 for(int count = 0;(MouseInfo.getPointerInfo().getLocation().getX() != x || 
        MouseInfo.getPointerInfo().getLocation().getY() != y) &&
        count < 100; count++) {
    new Robot().mouseMove(x, y);
  }

Note that this code sets the max amount of iterations to 100 to prevent an infinite loop, so it would be worthwile to perform another check right after the loop to determine if it is in the right place.

SteelToe
  • 2,477
  • 1
  • 17
  • 28
  • Tried this before submitting the question. All it does is simply move the mouse pointer to the same location "count" times. – EverNight Apr 02 '18 at 10:00
  • Increase the iterations in the loop – SteelToe Apr 02 '18 at 11:52
  • @EverNight its a known bug, many reported that the above worked, if the above does not work for you then the best solution is to scale your monitor at 100% and not more – SteelToe Apr 04 '18 at 14:26
0

Sub-optimal, pathetically bad solution... but it WORKS... and i've wasted far too much time on this issue than i should have.

import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.DWORD;
import com.sun.jna.platform.win32.WinDef.LONG;
import com.sun.jna.platform.win32.WinUser.INPUT;
import java.awt.MouseInfo;
import java.awt.Point;

public class Mouse {

    public static final int MOUSEEVENTF_MOVE = 1;

    public static void _winEvent_mi_move(int x, int y) {
        mouseAction(x, y, MOUSEEVENTF_MOVE);
    }

    public static void mouseAction(int x, int y, int flags) {
        INPUT input = new INPUT();
        input.type = new DWORD(INPUT.INPUT_MOUSE);
        input.input.setType("mi");
        if (x != -1) {
            input.input.mi.dx = new LONG(x);
        }
        if (y != -1) {
            input.input.mi.dy = new LONG(y);
        }
        input.input.mi.time = new DWORD(0);
        input.input.mi.dwExtraInfo = new ULONG_PTR(0);
        input.input.mi.dwFlags = new DWORD(flags);
        User32.INSTANCE.SendInput(new DWORD(1), new INPUT[]{input}, input.size());
    }

    public static void forceMove(int x, int y) {
        init_abs_move_0_0:
        {
            Point ip = MouseInfo.getPointerInfo().getLocation();
            _winEvent_mi_move(-ip.x, -ip.y);
        }
        moveX:
        {
            while (MouseInfo.getPointerInfo().getLocation().x < x - 1) {
                _winEvent_mi_move(1, 0);
            }
        }
        moveY:
        {
            while (MouseInfo.getPointerInfo().getLocation().y < y - 1) {
                _winEvent_mi_move(0, 1);
            }
        }
        System.out.println(MouseInfo.getPointerInfo().getLocation().toString());
    }

    public static void main(String[] args) {
        forceMove(1000, 1000);
        forceMove(2000, 1500);
    }

}

Theoretially one could achieve the same results by using an awt.Robot instance to move something pixel by pixel... this seems to work better.

yeah... and i did try to calculate the relative target position of the pointer destination based on the current position of the mouse cursor and everything... but it's still based on obscurely difficult to acquire information about whether or not DPI scaling and whatever nonsense MS has decided to implement in the WPI and GDI input event processing routines.

... moving it directly to 0x0 then 1000x1000 makes it result in a cursor position defined by (2000x1400) ... don't have the time nor the patience to figure it out. ... solution works. end of story.

EverNight
  • 964
  • 7
  • 16
  • Few days have passed and I must admit I failed with this aproach so far. I wanted to support multiple screens too. Eg case where main screen is on right (left has negative coordinates), one of them is scalled and they are not fully aligned is a full failure for me so far. I also tried DPI awareness on exe level (I wrap jar to exe) - see eg https://stackoverflow.com/questions/23551112/how-can-i-set-the-dpiaware-property-in-a-windows-application-manifest-to-per-mo. However finally I will go with informing users approach. – parvuselephantus Jun 28 '18 at 09:44
  • 1
    @parvuselephantus Hey man, i don't regularly check this website, so excuse the late comment. While fiddling wiht JNA, i came across this : https://learn.microsoft.com/en-us/windows/desktop/gdi/positioning-objects-on-a-multiple-display-setup Seems to be similar to what you're looking for. I've since temporarly abandoned the jna approach in hope that soon enough the Robot class would take into account the new changes in the GDI, but if you're up for learning a bit of C++ or JNA, reading everyghing under Windows GDI / Multiple Display Monitors might help your case. – EverNight Jul 05 '18 at 14:02
  • Thanks for the JNA solution, but it's (literally) off by one for me in many cases (200% desktop, Java 8). So is *this* solution - but it gets the mouse to target in one step and doesn't need JNA, just uses Java's Robot: https://stackoverflow.com/a/70719686/3500521 – Dreamspace President Jan 15 '22 at 08:49
-1

I wrote an "adjust" function and it do the dirty work. NOTE: If it has to adjust more than 30 px then it's terminate. You can call with:

If you had the same bug as me the code fix like this:

Adjust:638:598>507:537
Adjust:638:598>670:613
Adjust:638:598>630:595
Adjust:638:598>640:598
Move: 638 598 Click:true

moveAndClick(1000,1000,true);

private void moveAndClick(int x, int y, boolean click) throws AWTException {

    robot.mouseMove(x, y);

    adjustMouse(x, y);

    if (click) {
        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    }
    System.out.println("Move: " + x + " " + y + " Click:" + click);

}

private void adjustMouse(int x, int y) {

    int realX = MouseInfo.getPointerInfo().getLocation().x;
    int realY = MouseInfo.getPointerInfo().getLocation().y;

    int aX = x;
    int aY = y;

    int count = 0;

    while (realX != x || realY != y) {
        System.out.println("Adjust:" + x + ":" + y + ">" + realX + ":" + realY + "");

        aX = realX > x ? aX-- : aX++;
        aY = realY > y ? aY-- : aY++;

        robot.mouseMove(x, y);
        try {  // you can remove this part 
            Thread.sleep(30);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;

        realX = MouseInfo.getPointerInfo().getLocation().x;
        realY = MouseInfo.getPointerInfo().getLocation().y;
        if (count > 30) {  // you can remove or increase this part
            System.exit(0);
        }
    }


}

yields:

@ system.start - mouse position : java.awt.Point[x=1540,y=1462]
Adjust:1000:1000>191:307
Adjust:1000:1000>2212:2039
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
Adjust:1000:1000>2500:2159
Adjust:1000:1000>0:0
@ system.exit - mouse position : java.awt.Point[x=2500,y=2159]
SüniÚr
  • 826
  • 1
  • 16
  • 33