170 lines
5.8 KiB
Java
170 lines
5.8 KiB
Java
package wtf.beatrice.autosqueal.controls;
|
|
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
|
|
import java.awt.*;
|
|
import java.awt.event.InputEvent;
|
|
|
|
/**
|
|
* Moves the mouse cursor towards a destination one small step at a time.
|
|
*
|
|
* Runs on its own virtual thread: it mostly sleeps between steps, so blocking
|
|
* it is free. When it gets to its destination, it can optionally perform a
|
|
* double click, with the pacing the app has always used.
|
|
*
|
|
* All the movements it performs are reported to the {@link RobotMouseTracker},
|
|
* so that the away detection can tell them apart from the user's.
|
|
*/
|
|
public class SingleStepMovementTask implements Runnable {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger(SingleStepMovementTask.class);
|
|
|
|
/** Delay between two consecutive cursor steps, in milliseconds. */
|
|
private static final long STEP_DELAY_MILLISECONDS = 2L;
|
|
|
|
private final RobotMouseTracker robotTracker;
|
|
private final Robot robot;
|
|
private final int destX;
|
|
private final int destY;
|
|
private final boolean click;
|
|
|
|
private float currentX;
|
|
private float currentY;
|
|
private float stepX;
|
|
private float stepY;
|
|
|
|
public SingleStepMovementTask(RobotMouseTracker robotTracker, int destinationX, int destinationY, boolean click) throws AWTException {
|
|
|
|
this.robotTracker = robotTracker;
|
|
this.destX = destinationX;
|
|
this.destY = destinationY;
|
|
this.click = click;
|
|
|
|
Point location = MouseInfo.getPointerInfo().getLocation();
|
|
this.currentX = location.x;
|
|
this.currentY = location.y;
|
|
|
|
int lengthX = Math.round(Math.abs(currentX - destX));
|
|
int lengthY = Math.round(Math.abs(currentY - destY));
|
|
|
|
this.stepX = computeStepX(lengthX, lengthY);
|
|
this.stepY = computeStepY(lengthX, lengthY);
|
|
|
|
LOGGER.info("Dest: [{}, {}], Curr: [{}, {}]", destX, destY, currentX, currentY);
|
|
LOGGER.info("Step: [{}, {}]", stepX, stepY);
|
|
|
|
this.robot = new Robot();
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
|
|
// any position change from here on is the app's doing
|
|
robotTracker.moveStarted();
|
|
|
|
try {
|
|
while (!hasReachedDestination()) {
|
|
// when less than a full step is left, move exactly what is
|
|
// left, so that both axes always land on their destination
|
|
stepX = adjustedStep(currentX, destX, stepX);
|
|
stepY = adjustedStep(currentY, destY, stepY);
|
|
|
|
currentX = advance(currentX, destX, stepX);
|
|
currentY = advance(currentY, destY, stepY);
|
|
|
|
robot.mouseMove(Math.round(currentX), Math.round(currentY));
|
|
|
|
Thread.sleep(STEP_DELAY_MILLISECONDS);
|
|
}
|
|
|
|
onDestinationReached();
|
|
} catch (InterruptedException ex) {
|
|
// the movement was interrupted: the automation was stopped, so
|
|
// just end the movement here
|
|
Thread.currentThread().interrupt();
|
|
LOGGER.debug("Movement interrupted");
|
|
robotTracker.moveEnded(Math.round(currentX), Math.round(currentY));
|
|
} catch (RuntimeException ex) {
|
|
LOGGER.error("Unexpected error while moving the cursor", ex);
|
|
robotTracker.moveEnded(Math.round(currentX), Math.round(currentY));
|
|
}
|
|
}
|
|
|
|
private boolean hasReachedDestination() {
|
|
return Math.round(currentX) == destX && Math.round(currentY) == destY;
|
|
}
|
|
|
|
private void onDestinationReached() throws InterruptedException {
|
|
LOGGER.info("Reached destination [{}, {}], stopping mover", destX, destY);
|
|
|
|
robotTracker.moveEnded(destX, destY);
|
|
|
|
if (click) {
|
|
performClickSequence();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs a double click with the same pacing the app has always used:
|
|
* press at +500ms, release at +700ms, press at +1200ms, release at +1400ms.
|
|
*/
|
|
private void performClickSequence() throws InterruptedException {
|
|
Thread.sleep(500L);
|
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
|
Thread.sleep(200L);
|
|
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
|
Thread.sleep(500L);
|
|
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
|
|
Thread.sleep(200L);
|
|
robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
|
|
}
|
|
|
|
/**
|
|
* Step to take on the X axis so that both axes reach their destination
|
|
* after the same number of steps. Returns 0 when there is no movement to make.
|
|
*/
|
|
static float computeStepX(int lengthX, int lengthY) {
|
|
if (lengthX == 0) {
|
|
return 0.0f;
|
|
}
|
|
if (lengthX >= lengthY) {
|
|
return 1.0f;
|
|
}
|
|
return lengthX / (float) lengthY;
|
|
}
|
|
|
|
/**
|
|
* Step to take on the Y axis so that both axes reach their destination
|
|
* after the same number of steps. Returns 0 when there is no movement to make.
|
|
*/
|
|
static float computeStepY(int lengthX, int lengthY) {
|
|
if (lengthY == 0) {
|
|
return 0.0f;
|
|
}
|
|
if (lengthY > lengthX) {
|
|
return 1.0f;
|
|
}
|
|
return lengthY / (float) lengthX;
|
|
}
|
|
|
|
/**
|
|
* When less than a whole step is left to travel, move exactly what is left
|
|
* instead of a whole step, so that the cursor doesn't overshoot the destination.
|
|
*/
|
|
static float adjustedStep(float current, float destination, float step) {
|
|
float remaining = Math.abs(current - destination);
|
|
return remaining < 1.0f ? remaining : step;
|
|
}
|
|
|
|
/** Moves current towards destination by step, in the right direction. */
|
|
static float advance(float current, float destination, float step) {
|
|
if (current > destination) {
|
|
return current - step;
|
|
}
|
|
if (current < destination) {
|
|
return current + step;
|
|
}
|
|
return current;
|
|
}
|
|
} |