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) } }