implemented some basic Windows desktop utilities using the User32 api
This commit is contained in:
parent
1cb68f19a7
commit
15a0321a37
@ -84,8 +84,12 @@ interface DesktopController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OSProxy{
|
interface OSProxy {
|
||||||
fun getActiveWindowName(): String
|
fun getActiveWindowName(): String
|
||||||
|
|
||||||
|
fun enumWindowNames(): ArrayList<String>
|
||||||
|
|
||||||
|
fun setForegroundWindowByName(name: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,7 +103,7 @@ interface OSProxy{
|
|||||||
*/
|
*/
|
||||||
class WindowsDesktopController : DesktopController, OSProxy {
|
class WindowsDesktopController : DesktopController, OSProxy {
|
||||||
|
|
||||||
companion object{
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Converts a native byte buffer to a String.
|
* Converts a native byte buffer to a String.
|
||||||
*
|
*
|
||||||
@ -119,7 +123,7 @@ class WindowsDesktopController : DesktopController, OSProxy {
|
|||||||
* @param byteBuffer Byte array containing text from a native call
|
* @param byteBuffer Byte array containing text from a native call
|
||||||
* @return The native text as a String
|
* @return The native text as a String
|
||||||
*/
|
*/
|
||||||
private fun nativeByteBufferToString(byteBuffer: ByteArray): String{
|
private fun nativeByteBufferToString(byteBuffer: ByteArray): String {
|
||||||
// I guess this prunes anything that isn't on the ascii table?
|
// I guess this prunes anything that isn't on the ascii table?
|
||||||
val wText = Native.toString(byteBuffer).trim { it <= ' ' }
|
val wText = Native.toString(byteBuffer).trim { it <= ' ' }
|
||||||
return wText
|
return wText
|
||||||
@ -229,13 +233,99 @@ class WindowsDesktopController : DesktopController, OSProxy {
|
|||||||
*/
|
*/
|
||||||
fun GetWindowTextA(hWnd: Pointer?, lpString: ByteArray?, nMaxCount: Int): Int
|
fun GetWindowTextA(hWnd: Pointer?, lpString: ByteArray?, nMaxCount: Int): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the handle of the foreground window.
|
||||||
|
*
|
||||||
|
* Calls the Win32 API [User32.GetForegroundWindow] to retrieve the window handle
|
||||||
|
* of the current foreground window - the one the user is currently interacting with.
|
||||||
|
*
|
||||||
|
* @return The window handle (HWND) of the current foreground window, or null if none.
|
||||||
|
*/
|
||||||
fun GetForegroundWindow(): Pointer?
|
fun GetForegroundWindow(): Pointer?
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the foreground window.
|
||||||
|
*
|
||||||
|
* Calls the Win32 API [User32.SetForegroundWindow] to bring the window with the given
|
||||||
|
* handle [hWnd] to the foreground.
|
||||||
|
*
|
||||||
|
* @param hWnd The native window handle (HWND) of the window to activate.
|
||||||
|
* @return True if successful, false otherwise.
|
||||||
|
*/
|
||||||
|
fun SetForegroundWindow(hWnd: Pointer?): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the User32.dll library and gets an instance of the [User32] interface.
|
||||||
|
*
|
||||||
|
* This uses JNA to dynamically load the user32.dll library at runtime. It casts
|
||||||
|
* the result to the [User32] interface to provide access to the Windows API
|
||||||
|
* functions defined there.
|
||||||
|
*
|
||||||
|
* The [User32] instance is stored in [INSTANCE] to be used throughout the class
|
||||||
|
* for calling Windows APIs.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* val user32 = User32.INSTANCE
|
||||||
|
*
|
||||||
|
* user32.EnumWindows(...) // Call Windows API
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see User32
|
||||||
|
*/
|
||||||
companion object {
|
companion object {
|
||||||
|
/**
|
||||||
|
* Loads the User32.dll library and gets an instance of the [User32] interface.
|
||||||
|
*
|
||||||
|
* This uses JNA to dynamically load the user32.dll library at runtime. It casts
|
||||||
|
* the result to the [User32] interface to provide access to the Windows API
|
||||||
|
* functions defined there.
|
||||||
|
*
|
||||||
|
* The [User32] instance is stored in [INSTANCE] to be used throughout the class
|
||||||
|
* for calling Windows APIs.
|
||||||
|
*
|
||||||
|
* For example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* val user32 = User32.INSTANCE
|
||||||
|
*
|
||||||
|
* user32.EnumWindows(...) // Call Windows API
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @see User32
|
||||||
|
*/
|
||||||
val INSTANCE = Native.load("user32", User32::class.java) as User32
|
val INSTANCE = Native.load("user32", User32::class.java) as User32
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback class used for enumerating windows.
|
||||||
|
*
|
||||||
|
* This implements the [User32.WNDENUMPROC] interface required by the
|
||||||
|
* [User32.EnumWindows] API. An instance of this class is passed to
|
||||||
|
* [User32.EnumWindows] to receive callbacks for each window.
|
||||||
|
*
|
||||||
|
* @param cb The callback function to invoke for each window. It should return
|
||||||
|
* true to continue enumeration or false to stop.
|
||||||
|
*/
|
||||||
|
internal class WindowEnumCallback(val cb: (Pointer?, Pointer?) -> Boolean) : User32.WNDENUMPROC {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by [User32.EnumWindows] for each window.
|
||||||
|
*
|
||||||
|
* Implements the callback method required by [User32.WNDENUMPROC].
|
||||||
|
* This simply invokes the provided [cb] callback and returns its result.
|
||||||
|
*
|
||||||
|
* @param hWnd The window handle being enumerated.
|
||||||
|
* @param userDataPtr A pointer to user data passed to [User32.EnumWindows].
|
||||||
|
* @return The return value from the [cb] callback.
|
||||||
|
*/
|
||||||
|
override fun callback(hWnd: Pointer?, userDataPtr: Pointer?): Boolean {
|
||||||
|
return cb(hWnd, userDataPtr)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,10 +346,93 @@ class WindowsDesktopController : DesktopController, OSProxy {
|
|||||||
*/
|
*/
|
||||||
override fun getActiveWindowName(): String {
|
override fun getActiveWindowName(): String {
|
||||||
val user32 = User32.INSTANCE
|
val user32 = User32.INSTANCE
|
||||||
val textBuffer = ByteArray(512)
|
val foregroundWindowHwnd = user32.GetForegroundWindow()
|
||||||
|
return getWindowName(foregroundWindowHwnd)
|
||||||
user32.GetWindowTextA(user32.GetForegroundWindow(), textBuffer, 512)
|
|
||||||
|
|
||||||
return nativeByteBufferToString(textBuffer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the title/name of the window for the given handle.
|
||||||
|
*
|
||||||
|
* This calls the Win32 API [User32.GetWindowTextA] to retrieve the title
|
||||||
|
* text for the window referenced by [hWnd].
|
||||||
|
*
|
||||||
|
* It allocates a [windowTitleBuffer] byte array to hold the result. This is
|
||||||
|
* passed to [User32.GetWindowTextA] to be populated.
|
||||||
|
*
|
||||||
|
* The buffer is then converted to a [String] via [nativeByteBufferToString].
|
||||||
|
*
|
||||||
|
* @param hWnd The native window handle to get the title for.
|
||||||
|
* @return The window title text as a [String].
|
||||||
|
*/
|
||||||
|
private fun getWindowName(hWnd: Pointer?): String {
|
||||||
|
val maxTitleLength = 512
|
||||||
|
val user32 = User32.INSTANCE
|
||||||
|
val windowTitleBuffer = ByteArray(maxTitleLength)
|
||||||
|
|
||||||
|
user32.GetWindowTextA(hWnd, windowTitleBuffer, windowTitleBuffer.size)
|
||||||
|
|
||||||
|
return nativeByteBufferToString(windowTitleBuffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumerates all open window names on the desktop.
|
||||||
|
*
|
||||||
|
* Calls the Win32 API [User32.EnumWindows] to iterate through all current open windows.
|
||||||
|
* For each window handle, it retrieves the window name using [getWindowName]
|
||||||
|
* and adds it to a list if the name is not blank. We filter out blank window names because that particular information
|
||||||
|
* is useless for any reason other than counting how many open windows there are. If we actually need that information,
|
||||||
|
* we can simply acquire a list of all HWND references.
|
||||||
|
*
|
||||||
|
* @return An [ArrayList] containing the name of each open window.
|
||||||
|
*/
|
||||||
|
override fun enumWindowNames(): ArrayList<String> {
|
||||||
|
val user32 = User32.INSTANCE
|
||||||
|
val windowNames = ArrayList<String>()
|
||||||
|
|
||||||
|
val iterationCallback = WindowEnumCallback { hWnd, _ ->
|
||||||
|
val windowName = getWindowName(hWnd)
|
||||||
|
if (windowName.isNotBlank()) {
|
||||||
|
windowNames.add(windowName)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
user32.EnumWindows(iterationCallback, null)
|
||||||
|
return windowNames
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the foreground window by name on Windows.
|
||||||
|
*
|
||||||
|
* This calls the Win32 API [User32.EnumWindows] to iterate through all
|
||||||
|
* top-level windows, compares their name to the given [name], and calls
|
||||||
|
* [User32.SetForegroundWindow] on the matching window to bring it to the foreground.
|
||||||
|
*
|
||||||
|
* @param name The window name to search for and activate.
|
||||||
|
*/
|
||||||
|
override fun setForegroundWindowByName(name: String) {
|
||||||
|
val user32 = User32.INSTANCE
|
||||||
|
|
||||||
|
val iterationCallback = WindowEnumCallback { hWnd, _ ->
|
||||||
|
val windowName = getWindowName(hWnd)
|
||||||
|
|
||||||
|
if (windowName.isBlank()) {
|
||||||
|
//if the window is blank, tell the system that we should continue
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
//otherwise, we need to check the window name
|
||||||
|
if (windowName == name) {
|
||||||
|
user32.SetForegroundWindow(hWnd)
|
||||||
|
//we found a match, so tell the system that we don't need to proceed any further
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user32.EnumWindows(iterationCallback, null)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -64,6 +64,6 @@ class DesktopControllerTest {
|
|||||||
@Test
|
@Test
|
||||||
fun devTest(){
|
fun devTest(){
|
||||||
val c = WindowsDesktopController()
|
val c = WindowsDesktopController()
|
||||||
println("TEXT: ${ c.getActiveWindowName()}")
|
c.setForegroundWindowByName("RuneScape")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user