Skip to content
Open
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions src/java.desktop/share/classes/java/awt/Robot.java
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ public class Robot {

private DirectColorModel screenCapCM = null;

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many new lines still longer than 80 chars.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to follow a similar structure to the rest of the class. Some may be longer due to formatting links but I thought the result when generating the html looked fine. Was there any specific issue with the lines that are still longer than 80 chars?

* Default step-delay in milliseconds for mouse
* {@link #glide(int, int, int, int) glide}.
*/
public static final int DEFAULT_STEP_DELAY = 20;

/**
* Default pixel step-length for mouse
* {@link #glide(int, int, int, int) glide}.
*/
public static final int DEFAULT_STEP_LENGTH = 2;

/**
* Constructs a Robot object in the coordinate system of the primary screen.
*
Expand Down Expand Up @@ -774,3 +786,215 @@ public synchronized String toString() {
return getClass().getName() + "[ " + params + " ]";
}
}

/**
* A convenience method that simulates clicking a mouse button by calling {@code mousePress}, {@code mouseRelease},
* and {@code waitForIdle}. Invokes {@code waitForIdle} with a default delay of 20 milliseconds after
* {@code mousePress} and {@code mouseRelease} calls. For specifics on valid inputs see
* {@link java.awt.Robot#mousePress(int)}.
*
* @param buttons The button mask; a combination of one or more mouse button masks.
* @throws IllegalArgumentException if the {@code buttons} mask contains the mask for
* extra mouse button and support for extended mouse buttons is
* {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
* {@linkplain Toolkit#areExtraMouseButtonsEnabled() disabled} by Java

{@link} renders in {@code} font.

* @throws IllegalArgumentException if the {@code buttons} mask contains the mask for
* extra mouse button that does not exist on the mouse and support for extended
* mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
* mouse buttons is {@linkplain Toolkit#areExtraMouseButtonsEnabled() enabled}

* by Java
* @throws IllegalThreadStateException if called on the AWT event dispatching thread
* @see #mousePress(int)
* @see #mouseRelease(int)
* @see InputEvent#getMaskForButton(int)
* @see Toolkit#areExtraMouseButtonsEnabled()
* @see java.awt.event.MouseEvent
* @since 26
*/
public void click(int buttons) {
try {
mousePress(buttons);
waitForIdle(DEFAULT_STEP_DELAY);
} finally {
mouseRelease(buttons);
waitForIdle(DEFAULT_STEP_DELAY);
}
}

/**
* A convenience method that clicks mouse button 1.
*
* @throws IllegalThreadStateException if called on the AWT event dispatching thread
* @see #click(int)
* @since 26
*/
public void click() {
click(InputEvent.BUTTON1_DOWN_MASK);
}

/**
* A convenience method that calls {@code waitForIdle} then waits an additional specified
* {@code delayValue} time in milliseconds.
*
* @param delayValue Additional delay length in milliseconds to wait until thread
* sync been completed
* @throws IllegalThreadStateException if called on the AWT event
* dispatching thread
* @throws IllegalArgumentException if {@code delayValue} is not between {@code 0}
* and {@code 60,000} milliseconds inclusive
* @since 26
*/
public synchronized void waitForIdle(int delayValue) {
waitForIdle();
delay(delayValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we should recheck whether new methods like this one need to be synchronized. Some time ago, the synchronized keyword was removed from the delay method because synchronization could cause the delay to last longer than intended and unnecessarily block other methods.

In this case, waitForIdle() might be in a synchronized block (but the method itself is already synchronized), but do we really want to call delay() while holding the lock?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think we should recheck whether new methods like this one need to be synchronized. Some time ago, the synchronized keyword was removed from the delay method because synchronization could cause the delay to last longer than intended and unnecessarily block other methods.

In this case, waitForIdle() might be in a synchronized block (but the method itself is already synchronized), but do we really want to call delay() while holding the lock?

I expect you are referring to this fix you did about 6 years ago https://bugs.openjdk.org/browse/JDK-8210231 ?

That bug was more about side-effects of delay but ended up removing synchronized.

  1. "synchronized" keyword was removed, because of this bug: "if two threads call delay(1000) at around the same time then one of them will be a delay for 2000ms".

I'm not sure that ever affected any actual tests since Robot usage should be single threaded in all usual cases.

I can just about see a case for removing synchronized from the waitForIdle(delay) method - because waitForIdle() is already synchronized
and delay() doesn't change anything
But for cases like type() it is important for its operation that only one thread be allowed.
Just imagine the havoc if there are N robots all concurrently typing away - that's what is allowed by removing synchronized.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the click operation is a different operation and it would be nice to mark it as synchronized, but nothing is stopping the user from creating two robots and using them in parallel, which would lead to the same chaos.

Copy link
Contributor Author

@DamonGuy DamonGuy Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to make waitForIdle(int delay) not synchronized. Thanks! Will update the CSR accordingly.

}

/**
* A convenience method that moves the mouse in multiple
* steps from its current location to the destination coordinates.
*
* @implSpec Invokes {@link #mouseMove(int, int) mouseMove} with a default
* {@link #DEFAULT_STEP_LENGTH step-length} and {@link #DEFAULT_STEP_DELAY step-delay}.
*
* @param x Destination point x coordinate
* @param y Destination point y coordinate
*
* @throws IllegalThreadStateException if called on the AWT event dispatching
* thread and {@code isAutoWaitForIdle} would return true
* @see #DEFAULT_STEP_LENGTH
* @see #DEFAULT_STEP_DELAY
* @see #glide(int, int, int, int, int, int)
* @since 26
*/
public void glide(int x, int y) {
Point p = MouseInfo.getPointerInfo().getLocation();
glide(p.x, p.y, x, y);
}

/**
* A convenience method that moves the mouse in multiple steps
* from source coordinates to the destination coordinates.
*
* @implSpec Invokes {@link #mouseMove(int, int) mouseMove} with a default
* {@link #DEFAULT_STEP_LENGTH step-length} and {@link #DEFAULT_STEP_DELAY step-delay}.
*
* @param srcX Source point x coordinate
* @param srcY Source point y coordinate
* @param dstX Destination point x coordinate
* @param dstY Destination point y coordinate
*
* @throws IllegalThreadStateException if called on the AWT event dispatching
* thread and {@code isAutoWaitForIdle} would return true
* @see #DEFAULT_STEP_LENGTH
* @see #DEFAULT_STEP_DELAY
* @see #glide(int, int, int, int, int, int)
* @since 26
*/
public void glide(int srcX, int srcY, int dstX, int dstY) {
glide(srcX, srcY, dstX, dstY, DEFAULT_STEP_LENGTH, DEFAULT_STEP_DELAY);
}

/**
* A convenience method that moves the mouse in multiple
* steps from source point to the destination point with a
* given {@code stepLength} and {@code stepDelay}.
*
* @param srcX Source point x coordinate
* @param srcY Source point y coordinate
* @param destX Destination point x coordinate
* @param destY Destination point y coordinate
* @param stepLength Preferred length of one step in pixels
* @param stepDelay Delay between steps in milliseconds
*
* @throws IllegalArgumentException if {@code stepLength} is not greater than zero
* @throws IllegalArgumentException if {@code stepDelay} is not between {@code 0}
* and {@code 60,000} milliseconds inclusive
* @throws IllegalThreadStateException if called on the AWT event dispatching
* thread and {@code isAutoWaitForIdle} would return true
* @see #mouseMove(int, int)
* @see #delay(int)
* @since 26
*/
public void glide(int srcX, int srcY, int destX, int destY, int stepLength, int stepDelay) {
if (stepLength <= 0) {
throw new IllegalArgumentException("Step length must be greater than zero");
}
if (stepDelay <= 0 || stepDelay > 60000) {
throw new IllegalArgumentException("Step delay must be between 0 and 60,000 milliseconds");
}

int stepNum;
double tDx, tDy;
double dx, dy, ds;
double x, y;

dx = (destX - srcX);
dy = (destY - srcY);
ds = Math.sqrt(dx*dx + dy*dy);

tDx = dx / ds * stepLength;
tDy = dy / ds * stepLength;

int stepsCount = (int) ds / stepLength;

// Walk the mouse to the destination one step at a time
mouseMove(srcX, srcY);

for (x = srcX, y = srcY, stepNum = 0;
stepNum < stepsCount;
stepNum++) {
x += tDx;
y += tDy;
mouseMove((int)x, (int)y);
delay(stepDelay);
}

// Ensure the mouse moves to the right destination.
// The steps may have led the mouse to a slightly wrong place.
if (x != destX || y != destY) {
mouseMove(destX, destY);
}
}

/**
* A convenience method that simulates typing a key by calling {@code keyPress}
* and {@code keyRelease}. Invokes {@code waitForIdle} with a delay of 20 milliseconds
* after {@code keyPress} and {@code keyRelease} calls.
* <p>
* Key codes that have more than one physical key associated with them
* (e.g. {@code KeyEvent.VK_SHIFT} could mean either the
* left or right shift key) will map to the left key.
*
* @param keycode Key to type (e.g. {@code KeyEvent.VK_A})
* @throws IllegalArgumentException if {@code keycode} is not
* a valid key
* @throws IllegalThreadStateException if called on the AWT event dispatching thread
* @see #keyPress(int)
* @see #keyRelease(int)
* @see java.awt.event.KeyEvent
* @since 26
*/
public synchronized void type(int keycode) {
keyPress(keycode);
waitForIdle(20);
keyRelease(keycode);
waitForIdle(20);
}

/**
* A convenience method that simulates typing a char by calling {@code keyPress}
* and {@code keyRelease}. Gets the ExtendedKeyCode for the char and calls
* type(int keycode).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* type(int keycode).
* {@link #type(int) type(int keycode)}.

Or

Suggested change
* type(int keycode).
* {@code type(int keycode)}.

*
* @param c Character to be typed (e.g. {@code 'a'})
* @throws IllegalArgumentException if {@code keycode} is not
* a valid key
* @throws IllegalThreadStateException if called on the AWT event dispatching thread
* @see #type(int)
* @see #keyPress(int)
* @see #keyRelease(int)
* @see java.awt.event.KeyEvent
* @since 26
*/
public synchronized void type(char c) {
type(KeyEvent.getExtendedKeyCodeForChar(c));
}
}