RuneFactory/src/main/kotlin/controllers/DesktopController.kt

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