300 lines
9.2 KiB
Kotlin
300 lines
9.2 KiB
Kotlin
import java.awt.MouseInfo
|
|
import java.awt.Point
|
|
import java.awt.Robot
|
|
import java.awt.event.InputEvent
|
|
import java.util.concurrent.TimeUnit
|
|
import kotlin.random.Random
|
|
|
|
/**
|
|
* Interface for controllers that provide input capabilities.
|
|
*
|
|
* This defines methods for mouse and keyboard input like:
|
|
*
|
|
* - Moving the mouse
|
|
* - Mouse clicks
|
|
* - Key presses
|
|
* - Scrolling
|
|
*
|
|
* Classes that implement this interface can serve as input automation controllers.
|
|
*/
|
|
interface InputController {
|
|
/**
|
|
* Moves the mouse to the given [Point].
|
|
*
|
|
* @param point The destination [Point] to move the mouse to.
|
|
*/
|
|
fun moveMouse(point: Point)
|
|
|
|
/**
|
|
* Performs a mouse click at the current pointer location.
|
|
*
|
|
* @param button The mouse button to click.
|
|
* e.g. [InputEvent.BUTTON1_MASK]
|
|
*/
|
|
fun mouseClick(button: Int)
|
|
|
|
/**
|
|
* Presses and releases the given key.
|
|
*
|
|
* @param keyCode The key code of the key to press.
|
|
*/
|
|
fun keyPress(keyCode: Int)
|
|
|
|
/**
|
|
* Performs a scroll in motion.
|
|
*
|
|
* This will move the scroll wheel forward by the number of ticks
|
|
* over the duration. It will sleep for short intervals between
|
|
* ticks using the provided sleep duration and variance.
|
|
*
|
|
* @param sleepDur The base sleep duration between scroll ticks.
|
|
* @param sleepDurVariance The variance in sleep duration.
|
|
*/
|
|
fun scrollIn(sleepDur: Long, sleepDurVariance: Long)
|
|
|
|
/**
|
|
* Performs a scroll out motion.
|
|
*
|
|
* Same as [scrollIn] but moves the scroll wheel backward.
|
|
*/
|
|
fun scrollOut(sleepDur: Long, sleepDurVariance: Long)
|
|
}
|
|
|
|
interface TemporalController{
|
|
/**
|
|
* Sleeps for the specified duration.
|
|
*
|
|
* This uses [TimeUnit.MILLISECONDS] to sleep for the given duration in milliseconds.
|
|
*
|
|
* @param dur The sleep duration in milliseconds.
|
|
*/
|
|
fun sleep(dur: Long) {
|
|
TimeUnit.MILLISECONDS.sleep(dur)
|
|
}
|
|
|
|
/**
|
|
* Sleeps for the specified duration with some variance.
|
|
*
|
|
* This will sleep for the given duration plus or minus a random variance.
|
|
* The variance is divided in half to generate a random positive and negative
|
|
* value that is added to the duration.
|
|
*
|
|
* If the duration is negative or the variance is less than 1, this method
|
|
* will return immediately without sleeping.
|
|
*
|
|
* @param duration The base sleep duration in ms
|
|
* @param variance The amount of variance to add in ms. Gets divided in half
|
|
* to generate random +/- values.
|
|
*/
|
|
fun sleepWithVariance(duration: Long, variance: Long) {
|
|
if (duration < 0 || variance <= 1) {
|
|
return
|
|
}
|
|
val dSize = (variance) / 2
|
|
val r1 = Random.nextLong(dSize)
|
|
val r2 = Random.nextLong(dSize)
|
|
sleep(duration + r1 + r2)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface for controllers that interact with the desktop.
|
|
*
|
|
* This defines methods for getting desktop state like the mouse pointer
|
|
* location.
|
|
*
|
|
* Classes that implement this can serve as desktop automation controllers.
|
|
*/
|
|
interface DesktopController {
|
|
/**
|
|
* Gets the current pointer/mouse location on the desktop.
|
|
*
|
|
* @return The current [Point] location of the mouse pointer.
|
|
*/
|
|
fun getPointerLocation(): Point {
|
|
return MouseInfo.getPointerInfo().location
|
|
}
|
|
|
|
/**
|
|
* Gets a "wiggly" point near the given point.
|
|
*
|
|
* This takes in a target [Point] and [WiggleParams] and returns a new
|
|
* point that is randomly offset from the target point based on the
|
|
* wiggle parameters.
|
|
*
|
|
* This is useful for adding variance to mouse movements.
|
|
*
|
|
* @param point The target point to wiggle around
|
|
* @param params The wiggle parameters
|
|
* @return A new [Point] randomly offset from the target point.
|
|
*/
|
|
fun getAlmostPoint(point: Point, params : WiggleParams): Point{
|
|
val xDel = Random.nextInt(0, params.xWiggle)
|
|
val yDel = Random.nextInt(0, params.yWiggle)
|
|
val xDir = if (Random.nextDouble() > 0.5) {
|
|
1
|
|
} else {
|
|
-1
|
|
}
|
|
val yDir = if (Random.nextDouble() > 0.5) {
|
|
1
|
|
} else {
|
|
-1
|
|
}
|
|
return Point(point.x + (xDel * xDir), point.y + (yDel + yDir))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface for full-featured desktop automation controllers.
|
|
*
|
|
* Automaton combines capabilities from other interfaces to create a controller that can:
|
|
*
|
|
* - Get desktop and mouse state information like pointer location via [DesktopController]
|
|
*
|
|
* - Perform mouse and keyboard input like clicks, key presses, and scrolling via [InputController]
|
|
*
|
|
* - Handle timing and delays between actions using [TemporalController]
|
|
*
|
|
* By composing multiple capabilities, Automaton aims to provide a simple yet powerful interface for implementing
|
|
* desktop automation routines.
|
|
*
|
|
* Typical usage involves:
|
|
*
|
|
* 1. Obtaining an Automaton instance bound to the current OS/desktop
|
|
* 2. Calling methods like [moveMouse] and [mouseClick] to perform actions
|
|
* 3. Using [sleep] and [sleepWithVariance] to add delays
|
|
*
|
|
* This interface allows the underlying OS/desktop implementation details to be abstracted and swapped as needed.
|
|
*/
|
|
interface Automaton : DesktopController, InputController, TemporalController
|
|
|
|
/**
|
|
* Data class to hold wiggle parameters for mouse movement.
|
|
*
|
|
* This simple data class holds two integer properties for x and y wiggle amounts.
|
|
* These are used when generating simulated mouse movements to add some variance
|
|
* and randomness to the coordinates.
|
|
*
|
|
* For example, if a target destination point is (100, 200), the wiggle params
|
|
* might generate an actual movement point like (102, 198) to add some randomness.
|
|
*
|
|
* @param xWiggle The max amount of variance in x direction. Default 25.
|
|
* @param yWiggle The max amount of variance in y direction. Default 25.
|
|
*/
|
|
data class WiggleParams(
|
|
val xWiggle: Int = 25,
|
|
val yWiggle: Int = 25
|
|
)
|
|
|
|
/**
|
|
* Implementation of [DesktopInputController] using [java.awt.Robot].
|
|
*
|
|
* This class implements desktop automation capabilities using the Robot
|
|
* class from the AWT library. It provides methods for:
|
|
*
|
|
* - Getting mouse/pointer info
|
|
* - Mouse and keyboard input
|
|
* - Adding timing and delays
|
|
*
|
|
* @param robot The Robot instance to use. A default is created if not provided.
|
|
*/
|
|
open class RobotController(private val robot: Robot = Robot()) : Automaton {
|
|
/**
|
|
* Moves the mouse cursor to the given [Point].
|
|
*
|
|
* This uses the Robot [mouseMove] method to move the mouse cursor
|
|
* to the x and y coordinates specified by the provided [Point] p.
|
|
*
|
|
* Usage:
|
|
*```
|
|
* val doer = Doer()
|
|
*
|
|
* // Create target point
|
|
* val target = Point(100, 200)
|
|
*
|
|
* // Move mouse to target
|
|
* doer.mouseMove(target)
|
|
*```
|
|
*
|
|
*
|
|
* @param point The [Point] representing the x and y coordinates to move the mouse to.
|
|
*/
|
|
override fun moveMouse(point: Point) {
|
|
robot.mouseMove(point.x, point.y)
|
|
}
|
|
|
|
/**
|
|
* Performs a mouse click of the specified button.
|
|
*
|
|
* This uses the Robot to press and release the given mouse button.
|
|
*
|
|
* A random sleep is added in between pressing and releasing the button
|
|
* to add variance and avoid robotic timing.
|
|
*
|
|
* @param button The button to click. Must be a valid constant like [InputEvent.BUTTON1_MASK].
|
|
*
|
|
* Returns immediately If button is negative. Button must be a positive integer.
|
|
*/
|
|
override fun mouseClick(button: Int) {
|
|
//guardian logic
|
|
if (button < 0) {
|
|
return
|
|
}
|
|
robot.mousePress(button)
|
|
|
|
//we add in some random time variance here to appear less robotic
|
|
sleepWithVariance(8, 8)
|
|
|
|
robot.mouseRelease(button)
|
|
}
|
|
|
|
/**
|
|
* Presses and releases the given key.
|
|
*
|
|
* This uses the Robot to simulate pressing and releasing the key with the given key code.
|
|
*
|
|
* A random sleep is added after pressing the key before releasing it to add variance
|
|
* and avoid robotic timing.
|
|
*
|
|
* @param key The key code of the key to press, such as [KeyEvent.VK_A].
|
|
*
|
|
* returns immediately if key < 0. This can be useful for skipping actions with a -1
|
|
*/
|
|
override fun keyPress(key: Int) {
|
|
//guardian logic
|
|
if (key < 0) {
|
|
return
|
|
}
|
|
|
|
robot.keyPress(key)
|
|
|
|
//we add in some random time variance here to appear less robotic
|
|
sleepWithVariance(8, 8)
|
|
|
|
robot.keyRelease(key)
|
|
}
|
|
|
|
/**
|
|
* Scrolls the mouse wheel down by one unit.
|
|
*
|
|
* Uses the Robot [mouseWheel] method to scroll up and then sleeps
|
|
* for a random duration between 16-32ms to pace the scrolling.
|
|
*/
|
|
override fun scrollOut(sleepDur: Long, sleepDurVariance: Long) {
|
|
robot.mouseWheel(1)
|
|
sleepWithVariance(sleepDur, sleepDurVariance)
|
|
}
|
|
|
|
/**
|
|
* Scrolls the mouse wheel up by one unit.
|
|
*
|
|
* Uses the Robot [mouseWheel] method to scroll up and then sleeps
|
|
* for a random duration between 16-32ms to pace the scrolling.
|
|
*/
|
|
override fun scrollIn(sleepDur: Long, sleepDurVariance: Long) {
|
|
robot.mouseWheel(-1)
|
|
sleepWithVariance(sleepDur, sleepDurVariance)
|
|
}
|
|
|
|
} |