265 lines
8.6 KiB
Kotlin
265 lines
8.6 KiB
Kotlin
package controllers
|
|
|
|
import com.sun.jna.Native
|
|
import com.sun.jna.Pointer
|
|
import com.sun.jna.win32.StdCallLibrary
|
|
import params.MouseWiggleParams
|
|
import java.awt.MouseInfo
|
|
import java.awt.Point
|
|
import kotlin.random.Random
|
|
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* This returns a [Point] representing the x, y coordinates of the mouse pointer on the screen.
|
|
*
|
|
* For example:
|
|
*
|
|
* ```
|
|
* val mouseLocation = getPointerLocation()
|
|
*
|
|
* println(mouseLocation) // Might print "Point[x=1920,y=1080]"
|
|
* ```
|
|
*
|
|
* @return The current [Point] location of the mouse pointer on the screen.
|
|
*/
|
|
fun getPointerLocation(): Point {
|
|
return MouseInfo.getPointerInfo().location
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets a point near the given [point], with some random wiggle.
|
|
*
|
|
* This generates a new point that is randomly offset from the given [point]
|
|
* by an amount controlled by the given [MouseWiggleParams].
|
|
*
|
|
* Usage example:
|
|
*
|
|
* ```
|
|
* val base = Point(10, 20)
|
|
* val params = WiggleParams(xWiggle = 5, yWiggle = 5)
|
|
* val randomPoint = getAlmostPoint(base, params)
|
|
*
|
|
* // randomPoint might be (8, 22)
|
|
* ```
|
|
*
|
|
* Alternatively, params may be omitted to use the default WiggleParams values
|
|
* ```
|
|
* val base = Point(10, 20)
|
|
* val randomPoint = getAlmostPoint(base)
|
|
*
|
|
* // randomPoint might be (8, 22)
|
|
* ```
|
|
*
|
|
* @param point The base point to start from
|
|
* @param params The wiggle parameters that control the random offset amount
|
|
* @return A new [Point] near the given point with some random wiggle applied
|
|
*/
|
|
fun getAlmostPoint(point: Point, params: MouseWiggleParams = MouseWiggleParams()): 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 OSProxy{
|
|
fun getActiveWindowName(): String
|
|
}
|
|
|
|
/**
|
|
* Windows implementation of [DesktopController].
|
|
*
|
|
* This class provides methods to interact with the desktop on Windows
|
|
* by calling Win32 APIs.
|
|
*
|
|
* It implements the [DesktopController] interface to provide desktop
|
|
* functionality like getting the mouse pointer location on Windows.
|
|
*/
|
|
class WindowsDesktopController : DesktopController, OSProxy {
|
|
|
|
companion object{
|
|
/**
|
|
* Converts a native byte buffer to a String.
|
|
*
|
|
* This takes a byte array [byteBuffer] containing text from a native Win32 call,
|
|
* converts it to a String using JNA, and trims whitespace characters.
|
|
*
|
|
* Usage example:
|
|
*
|
|
* ```
|
|
* val buffer = ByteArray(256)
|
|
* GetWindowTextA(hwnd, buffer, buffer.size) // Win32 call
|
|
*
|
|
* val windowTitle = nativeByteBufferToString(buffer)
|
|
* println(windowTitle) // Print title string
|
|
* ```
|
|
*
|
|
* @param byteBuffer Byte array containing text from a native call
|
|
* @return The native text as a String
|
|
*/
|
|
private fun nativeByteBufferToString(byteBuffer: ByteArray): String{
|
|
// I guess this prunes anything that isn't on the ascii table?
|
|
val wText = Native.toString(byteBuffer).trim { it <= ' ' }
|
|
return wText
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interface for calling Windows User32 API functions.
|
|
*
|
|
* This defines an interface extending StdCallLibrary to call native
|
|
* Windows User32 library functions like EnumWindows, GetWindowTextA etc.
|
|
*
|
|
* Classes can implement this interface to make direct calls to the
|
|
* User32 DLL on Windows.
|
|
*/
|
|
internal interface User32 : StdCallLibrary {
|
|
|
|
/**
|
|
* Interface for a Windows callback function to enumerate windows.
|
|
*
|
|
* This extends the StdCallLibrary.StdCallCallback to define a callback
|
|
* method that will be invoked by the Windows API EnumWindows function.
|
|
*
|
|
* The callback method accepts a window handle (HWND) and a user-defined
|
|
* pointer, and returns a Boolean indicating whether to continue enumeration.
|
|
*
|
|
* Usage example:
|
|
* ```
|
|
* val callback = object : WNDENUMPROC {
|
|
* override fun callback(hWnd: Pointer?, arg: Pointer?): Boolean {
|
|
* // Check if hWnd matches target window
|
|
* if (matchesTarget(hWnd)) {
|
|
* // Found target window, stop enumeration
|
|
* return false
|
|
* }
|
|
* // Keep enumerating
|
|
* return true
|
|
* }
|
|
* }
|
|
* ```
|
|
*
|
|
* @param hWnd Window handle (HWND) for the current enumerated window.
|
|
* @param arg User-defined data pointer passed to EnumWindows.
|
|
* @return True to continue enumerating windows, false to stop.
|
|
*/
|
|
interface WNDENUMPROC : StdCallLibrary.StdCallCallback {
|
|
fun callback(hWnd: Pointer?, arg: Pointer?): Boolean
|
|
}
|
|
|
|
/**
|
|
* Enumerates windows on the system.
|
|
*
|
|
* This calls the Windows API EnumWindows function to enumerate all top-level windows.
|
|
*
|
|
* For each window, it calls the provided [WNDENUMPROC] callback function,
|
|
* passing the window handle [hWnd] and user-defined [userData] pointer.
|
|
*
|
|
* Enumeration can be stopped by returning false from the callback.
|
|
*
|
|
* Usage example:
|
|
*
|
|
* ```
|
|
* val windows = mutableListOf<HWND>()
|
|
*
|
|
* val callback = object : WNDENUMPROC {
|
|
* override fun callback(hWnd: Pointer?, arg: Pointer?): Boolean {
|
|
* windows.add(hWnd) // Add hWnd to list
|
|
* return true // Continue enumerating
|
|
* }
|
|
* }
|
|
*
|
|
* EnumWindows(callback, null) // Get all top-level windows
|
|
*
|
|
* println(windows) // Print list of HWNDs
|
|
* ```
|
|
*
|
|
* @param lpEnumFunc The [WNDENUMPROC] callback to call for each window.
|
|
* @param userData Optional user-defined data to pass to the callback.
|
|
* @return True if successful, false otherwise.
|
|
*/
|
|
fun EnumWindows(lpEnumFunc: WNDENUMPROC?, userData: Pointer?): Boolean
|
|
|
|
/**
|
|
* Gets the title text of the specified window.
|
|
*
|
|
* This calls the Win32 API GetWindowTextA function to get the title
|
|
* text for the window handle [hWnd].
|
|
*
|
|
* The window text is copied into the [lpString] buffer up to [nMaxCount] characters.
|
|
*
|
|
* Usage example:
|
|
*
|
|
* ```
|
|
* val buffer = ByteArray(256)
|
|
* val hwnd = getWindowHandle() // get some HWND
|
|
*
|
|
* GetWindowTextA(hwnd, buffer, buffer.size)
|
|
*
|
|
* val windowTitle = String(buffer)
|
|
* println(windowTitle)
|
|
* ```
|
|
*
|
|
* @param hWnd The window handle (HWND).
|
|
* @param lpString The buffer to receive the window text.
|
|
* @param nMaxCount The maximum number of characters to copy to the buffer.
|
|
* @return The length of the window text (excluding null-terminator).
|
|
*/
|
|
fun GetWindowTextA(hWnd: Pointer?, lpString: ByteArray?, nMaxCount: Int): Int
|
|
|
|
fun GetForegroundWindow(): Pointer?
|
|
|
|
companion object {
|
|
val INSTANCE = Native.load("user32", User32::class.java) as User32
|
|
}
|
|
|
|
|
|
}
|
|
|
|
/**
|
|
* Gets the title of the active/foreground window.
|
|
*
|
|
* This calls Win32 APIs to get the handle of the foreground window,
|
|
* then gets its title text.
|
|
*
|
|
* Usage example:
|
|
*
|
|
* ```
|
|
* val activeWindowName = getActiveWindowName()
|
|
*
|
|
* println(activeWindowName) // Prints foreground window title
|
|
* ```
|
|
*
|
|
* @return The title text of the current foreground window.
|
|
*/
|
|
override fun getActiveWindowName(): String {
|
|
val user32 = User32.INSTANCE
|
|
val textBuffer = ByteArray(512)
|
|
|
|
user32.GetWindowTextA(user32.GetForegroundWindow(), textBuffer, 512)
|
|
|
|
return nativeByteBufferToString(textBuffer)
|
|
}
|
|
} |