1490 lines
38 KiB
Go
1490 lines
38 KiB
Go
//go:build windows
|
|
|
|
package capture
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
var (
|
|
ole32DLL = syscall.NewLazyDLL("ole32.dll")
|
|
procCoInitializeEx = ole32DLL.NewProc("CoInitializeEx")
|
|
procCoCreateInstance = ole32DLL.NewProc("CoCreateInstance")
|
|
procCoUninitialize = ole32DLL.NewProc("CoUninitialize")
|
|
|
|
procGetClassNameW = user32.NewProc("GetClassNameW")
|
|
procEnumChildWindows = user32.NewProc("EnumChildWindows")
|
|
procRealChildWindowFromPoint = user32.NewProc("RealChildWindowFromPoint")
|
|
)
|
|
|
|
const (
|
|
COINIT_MULTITHREADED = 0x0
|
|
CLSCTX_INPROC_SERVER = 0x1
|
|
CLSCTX_ALL = 0x17
|
|
|
|
uiaTreeScopeElement = 0x1
|
|
uiaTreeScopeChildren = 0x2
|
|
uiaTreeScopeSubtree = 0x7
|
|
uiaTreeScopeDescendants = 0x4
|
|
|
|
uiaInvokePatternID = 10000
|
|
uiaSelectionPatternID = 10001
|
|
uiaValuePatternID = 10002
|
|
uiaRangeValuePatternID = 10003
|
|
uiaScrollPatternID = 10004
|
|
uiaExpandCollapsePatternID = 10005
|
|
uiaTogglePatternID = 10015
|
|
uiaTransformPatternID = 10016
|
|
uiaScrollItemPatternID = 10017
|
|
uiaSelectionItemPatternID = 10010
|
|
|
|
uiaScrollAmountLargeDecrement = 0
|
|
uiaScrollAmountSmallDecrement = 1
|
|
uiaScrollAmountNoAmount = 2
|
|
uiaScrollAmountLargeIncrement = 3
|
|
uiaScrollAmountSmallIncrement = 4
|
|
|
|
uiaExpandCollapseStateCollapsed = 0
|
|
uiaExpandCollapseStateExpanded = 1
|
|
uiaExpandCollapseStatePartiallyExpanded = 2
|
|
uiaExpandCollapseStateLeafNode = 3
|
|
|
|
uiaBoundingRectanglePropertyID = 30001
|
|
uiaClassNamePropertyID = 30012
|
|
uiaNativeWindowHandlePropertyID = 30020
|
|
uiaIsEnabledPropertyID = 30010
|
|
uiaControlTypePropertyID = 30003
|
|
uiaNamePropertyID = 30005
|
|
uiaHasKeyboardFocusPropertyID = 30008
|
|
|
|
VT_EMPTY = 0
|
|
VT_I4 = 3
|
|
VT_BSTR = 8
|
|
VT_R8 = 5
|
|
VT_BOOL = 11
|
|
|
|
WM_POINTERUPDATE = 0x0245
|
|
WM_POINTERDOWN = 0x0246
|
|
WM_POINTERUP = 0x0247
|
|
WM_POINTERWHEEL = 0x024E
|
|
|
|
WM_CONTEXTMENU = 0x007B
|
|
)
|
|
|
|
var (
|
|
CLSID_CUIAutomation = windows.GUID{
|
|
Data1: 0xFF48DBA4, Data2: 0x60EF, Data3: 0x4201,
|
|
Data4: [8]byte{0xAA, 0x87, 0x54, 0x10, 0x3E, 0xEF, 0x59, 0x4E},
|
|
}
|
|
IID_IUIAutomation = windows.GUID{
|
|
Data1: 0x30CBE57D, Data2: 0xD9D0, Data3: 0x452A,
|
|
Data4: [8]byte{0xAB, 0x13, 0x7A, 0xC5, 0xAC, 0x48, 0x25, 0xEE},
|
|
}
|
|
IID_IUIAutomationElement = windows.GUID{
|
|
Data1: 0xD22108AA, Data2: 0x8AC5, Data3: 0x49A5,
|
|
Data4: [8]byte{0x83, 0x7B, 0x37, 0xBB, 0xB3, 0xD7, 0x59, 0x1E},
|
|
}
|
|
IID_IUIAutomationInvokePattern = windows.GUID{
|
|
Data1: 0xFB377FBE, Data2: 0x8EA6, Data3: 0x46D5,
|
|
Data4: [8]byte{0x9C, 0x73, 0x64, 0x99, 0x64, 0x2D, 0x30, 0x59},
|
|
}
|
|
IID_IUIAutomationTogglePattern = windows.GUID{
|
|
Data1: 0x94CF8058, Data2: 0x9B8D, Data3: 0x4AB9,
|
|
Data4: [8]byte{0x8B, 0xFD, 0x4C, 0xD0, 0xA3, 0x3C, 0x8C, 0x70},
|
|
}
|
|
IID_IUIAutomationScrollPattern = windows.GUID{
|
|
Data1: 0x88F4D42A, Data2: 0xE5F9, Data3: 0x459B,
|
|
Data4: [8]byte{0xB1, 0xDE, 0x77, 0xE3, 0x5F, 0x1C, 0x8D, 0x3A},
|
|
}
|
|
IID_IUIAutomationValuePattern = windows.GUID{
|
|
Data1: 0xA94CD8B1, Data2: 0x0844, Data3: 0x4CD6,
|
|
Data4: [8]byte{0x9D, 0x2D, 0x64, 0x05, 0x37, 0xAB, 0x39, 0xE9},
|
|
}
|
|
IID_IUIAutomationExpandCollapsePattern = windows.GUID{
|
|
Data1: 0x619BE086, Data2: 0x1F4E, Data3: 0x4EE4,
|
|
Data4: [8]byte{0xBA, 0xFA, 0x21, 0x01, 0x28, 0x73, 0x87, 0x30},
|
|
}
|
|
IID_IUIAutomationSelectionItemPattern = windows.GUID{
|
|
Data1: 0xA8EFA66A, Data2: 0x0FDA, Data3: 0x421A,
|
|
Data4: [8]byte{0x91, 0x94, 0x38, 0x02, 0x1F, 0x35, 0x78, 0xEA},
|
|
}
|
|
IID_IUIAutomationTransformPattern = windows.GUID{
|
|
Data1: 0xA9B55844, Data2: 0xA55B, Data3: 0x4EF0,
|
|
Data4: [8]byte{0x92, 0x6D, 0x56, 0x9C, 0x16, 0xFF, 0x89, 0xBB},
|
|
}
|
|
)
|
|
|
|
type iuiAutomationVtbl struct {
|
|
// IUnknown (0-2)
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
// IUIAutomation (3+)
|
|
CompareElements uintptr // 3
|
|
CompareRuntimeIds uintptr // 4
|
|
GetRootElement uintptr // 5
|
|
ElementFromHandle uintptr // 6
|
|
ElementFromPoint uintptr // 7
|
|
GetFocusedElement uintptr // 8
|
|
GetRootElementBuildCache uintptr // 9
|
|
ElementFromHandleBuildCache uintptr // 10
|
|
ElementFromPointBuildCache uintptr // 11
|
|
GetFocusedElementBuildCache uintptr // 12
|
|
CreateTreeWalker uintptr // 13
|
|
ControlViewWalker uintptr // 14
|
|
ContentViewWalker uintptr // 15
|
|
RawViewWalker uintptr // 16
|
|
RawViewCondition uintptr // 17
|
|
ControlViewCondition uintptr // 18
|
|
ContentViewCondition uintptr // 19
|
|
CreateCacheRequest uintptr // 20
|
|
CreateTrueCondition uintptr // 21
|
|
CreateFalseCondition uintptr // 22
|
|
CreatePropertyCondition uintptr // 23
|
|
CreatePropertyConditionEx uintptr // 24
|
|
CreateAndCondition uintptr // 25
|
|
CreateAndConditionFromArray uintptr // 26
|
|
CreateAndConditionFromNativeArray uintptr // 27
|
|
CreateOrCondition uintptr // 28
|
|
CreateOrConditionFromArray uintptr // 29
|
|
CreateOrConditionFromNativeArray uintptr // 30
|
|
CreateNotCondition uintptr // 31
|
|
}
|
|
|
|
type iuiAutomation struct {
|
|
lpVtbl *iuiAutomationVtbl
|
|
}
|
|
|
|
func (a *iuiAutomation) Release() {
|
|
if a == nil || a.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(a.lpVtbl.Release, uintptr(unsafe.Pointer(a)))
|
|
}
|
|
|
|
func (a *iuiAutomation) ElementFromHandle(hwnd uintptr) *iuiAutomationElement {
|
|
var elem *iuiAutomationElement
|
|
hr := callSyscallN(a.lpVtbl.ElementFromHandle,
|
|
uintptr(unsafe.Pointer(a)),
|
|
hwnd,
|
|
uintptr(unsafe.Pointer(&elem)),
|
|
)
|
|
if hr != S_OK || elem == nil {
|
|
return nil
|
|
}
|
|
return elem
|
|
}
|
|
|
|
func (a *iuiAutomation) ElementFromPoint(pt point) *iuiAutomationElement {
|
|
var elem *iuiAutomationElement
|
|
hr := callSyscallN(a.lpVtbl.ElementFromPoint,
|
|
uintptr(unsafe.Pointer(a)),
|
|
uintptr(*(*int64)(unsafe.Pointer(&pt))),
|
|
uintptr(unsafe.Pointer(&elem)),
|
|
)
|
|
if hr != S_OK || elem == nil {
|
|
return nil
|
|
}
|
|
return elem
|
|
}
|
|
|
|
func (a *iuiAutomation) GetFocusedElement() *iuiAutomationElement {
|
|
var elem *iuiAutomationElement
|
|
hr := callSyscallN(a.lpVtbl.GetFocusedElement,
|
|
uintptr(unsafe.Pointer(a)),
|
|
uintptr(unsafe.Pointer(&elem)),
|
|
)
|
|
if hr != S_OK || elem == nil {
|
|
return nil
|
|
}
|
|
return elem
|
|
}
|
|
|
|
func (a *iuiAutomation) CreateTrueCondition() *iuiAutomationCondition {
|
|
var cond *iuiAutomationCondition
|
|
hr := callSyscallN(a.lpVtbl.CreateTrueCondition,
|
|
uintptr(unsafe.Pointer(a)),
|
|
uintptr(unsafe.Pointer(&cond)),
|
|
)
|
|
if hr != S_OK || cond == nil {
|
|
return nil
|
|
}
|
|
return cond
|
|
}
|
|
|
|
func (a *iuiAutomation) GetControlViewWalker() *iuiAutomationTreeWalker {
|
|
var walker *iuiAutomationTreeWalker
|
|
hr := callSyscallN(a.lpVtbl.ControlViewWalker,
|
|
uintptr(unsafe.Pointer(a)),
|
|
uintptr(unsafe.Pointer(&walker)),
|
|
)
|
|
if hr != S_OK || walker == nil {
|
|
return nil
|
|
}
|
|
return walker
|
|
}
|
|
|
|
type iuiAutomationTreeWalkerVtbl struct {
|
|
QueryInterface uintptr // 0
|
|
AddRef uintptr // 1
|
|
Release uintptr // 2
|
|
GetParentElement uintptr // 3
|
|
GetFirstChildElement uintptr // 4
|
|
GetLastChildElement uintptr // 5
|
|
GetNextSiblingElement uintptr // 6
|
|
GetPreviousSiblingElement uintptr // 7
|
|
NormalizeElement uintptr // 8
|
|
GetParentElementBuildCache uintptr // 9
|
|
GetFirstChildElementBuildCache uintptr // 10
|
|
GetLastChildElementBuildCache uintptr // 11
|
|
GetNextSiblingElementBuildCache uintptr // 12
|
|
GetPreviousSiblingElementBuildCache uintptr // 13
|
|
NormalizeElementBuildCache uintptr // 14
|
|
Condition uintptr // 15
|
|
}
|
|
|
|
type iuiAutomationTreeWalker struct {
|
|
lpVtbl *iuiAutomationTreeWalkerVtbl
|
|
}
|
|
|
|
func (w *iuiAutomationTreeWalker) Release() {
|
|
if w == nil || w.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(w.lpVtbl.Release, uintptr(unsafe.Pointer(w)))
|
|
}
|
|
|
|
func (w *iuiAutomationTreeWalker) GetParentElement(elem *iuiAutomationElement) *iuiAutomationElement {
|
|
var parent *iuiAutomationElement
|
|
hr := callSyscallN(w.lpVtbl.GetParentElement,
|
|
uintptr(unsafe.Pointer(w)),
|
|
uintptr(unsafe.Pointer(elem)),
|
|
uintptr(unsafe.Pointer(&parent)),
|
|
)
|
|
if hr != S_OK || parent == nil {
|
|
return nil
|
|
}
|
|
return parent
|
|
}
|
|
|
|
type iuiAutomationConditionVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
}
|
|
|
|
type iuiAutomationCondition struct {
|
|
lpVtbl *iuiAutomationConditionVtbl
|
|
}
|
|
|
|
func (c *iuiAutomationCondition) Release() {
|
|
if c == nil || c.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(c.lpVtbl.Release, uintptr(unsafe.Pointer(c)))
|
|
}
|
|
|
|
type iuiAutomationElementVtbl struct {
|
|
QueryInterface uintptr // 0
|
|
AddRef uintptr // 1
|
|
Release uintptr // 2
|
|
|
|
SetFocus uintptr // 3
|
|
GetRuntimeId uintptr // 4
|
|
FindFirst uintptr // 5
|
|
FindAll uintptr // 6
|
|
FindFirstBuildCache uintptr // 7
|
|
FindAllBuildCache uintptr // 8
|
|
BuildUpdatedCache uintptr // 9
|
|
GetCurrentPropertyValue uintptr // 10
|
|
GetCurrentPropertyValueEx uintptr // 11
|
|
GetCachedPropertyValue uintptr // 12
|
|
GetCachedPropertyValueEx uintptr // 13
|
|
GetCurrentPatternAs uintptr // 14
|
|
GetCachedPatternAs uintptr // 15
|
|
GetCurrentPattern uintptr // 16
|
|
GetCachedPattern uintptr // 17
|
|
GetCachedParent uintptr // 18
|
|
GetCachedChildren uintptr // 19
|
|
CurrentProcessId uintptr // 20
|
|
CurrentControlType uintptr // 21
|
|
CurrentLocalizedControlType uintptr // 22
|
|
CurrentName uintptr // 23
|
|
CurrentAcceleratorKey uintptr // 24
|
|
CurrentAccessKey uintptr // 25
|
|
CurrentHasKeyboardFocus uintptr // 26
|
|
CurrentIsKeyboardFocusable uintptr // 27
|
|
CurrentIsEnabled uintptr // 28
|
|
CurrentAutomationId uintptr // 29
|
|
CurrentClassName uintptr // 30
|
|
CurrentHelpText uintptr // 31
|
|
CurrentCulture uintptr // 32
|
|
CurrentIsControlElement uintptr // 33
|
|
CurrentIsContentElement uintptr // 34
|
|
CurrentIsPassword uintptr // 35
|
|
CurrentNativeWindowHandle uintptr // 36
|
|
CurrentItemType uintptr // 37
|
|
CurrentIsOffscreen uintptr // 38
|
|
CurrentOrientation uintptr // 39
|
|
CurrentFrameworkId uintptr // 40
|
|
CurrentIsRequiredForForm uintptr // 41
|
|
CurrentItemStatus uintptr // 42
|
|
CurrentBoundingRectangle uintptr // 43
|
|
CurrentLabeledBy uintptr // 44
|
|
CurrentAriaRole uintptr // 45
|
|
CurrentAriaProperties uintptr // 46
|
|
CurrentIsDataValidForForm uintptr // 47
|
|
CurrentControllerFor uintptr // 48
|
|
CurrentDescribedBy uintptr // 49
|
|
CurrentFlowsTo uintptr // 50
|
|
CurrentProviderDescription uintptr // 51
|
|
CachedProcessId uintptr // 52
|
|
CachedControlType uintptr // 53
|
|
CachedLocalizedControlType uintptr // 54
|
|
CachedName uintptr // 55
|
|
CachedAcceleratorKey uintptr // 56
|
|
CachedAccessKey uintptr // 57
|
|
CachedHasKeyboardFocus uintptr // 58
|
|
CachedIsKeyboardFocusable uintptr // 59
|
|
CachedIsEnabled uintptr // 60
|
|
CachedAutomationId uintptr // 61
|
|
CachedClassName uintptr // 62
|
|
CachedHelpText uintptr // 63
|
|
CachedCulture uintptr // 64
|
|
CachedIsControlElement uintptr // 65
|
|
CachedIsContentElement uintptr // 66
|
|
CachedIsPassword uintptr // 67
|
|
CachedNativeWindowHandle uintptr // 68
|
|
CachedItemType uintptr // 69
|
|
CachedIsOffscreen uintptr // 70
|
|
CachedOrientation uintptr // 71
|
|
CachedFrameworkId uintptr // 72
|
|
CachedIsRequiredForForm uintptr // 73
|
|
CachedItemStatus uintptr // 74
|
|
CachedBoundingRectangle uintptr // 75
|
|
CachedLabeledBy uintptr // 76
|
|
CachedAriaRole uintptr // 77
|
|
CachedAriaProperties uintptr // 78
|
|
CachedIsDataValidForForm uintptr // 79
|
|
CachedControllerFor uintptr // 80
|
|
CachedDescribedBy uintptr // 81
|
|
CachedFlowsTo uintptr // 82
|
|
CachedProviderDescription uintptr // 83
|
|
GetClickablePoint uintptr // 84
|
|
}
|
|
|
|
type iuiAutomationElement struct {
|
|
lpVtbl *iuiAutomationElementVtbl
|
|
}
|
|
|
|
func (e *iuiAutomationElement) Release() {
|
|
if e == nil || e.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(e.lpVtbl.Release, uintptr(unsafe.Pointer(e)))
|
|
}
|
|
|
|
func (e *iuiAutomationElement) SetFocus() uintptr {
|
|
return callSyscallN(e.lpVtbl.SetFocus, uintptr(unsafe.Pointer(e)))
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetCurrentPattern(patternID int32) *iunknown {
|
|
var pat *iunknown
|
|
hr := callSyscallN(e.lpVtbl.GetCurrentPattern,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(patternID),
|
|
uintptr(unsafe.Pointer(&pat)),
|
|
)
|
|
if hr != S_OK || pat == nil {
|
|
return nil
|
|
}
|
|
return pat
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetCurrentPropertyValue(propID int32, val *uiaVariant) uintptr {
|
|
return callSyscallN(e.lpVtbl.GetCurrentPropertyValue,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(propID),
|
|
uintptr(unsafe.Pointer(val)),
|
|
)
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetCurrentName() string {
|
|
var bstr uintptr
|
|
hr := callSyscallN(e.lpVtbl.CurrentName,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(unsafe.Pointer(&bstr)),
|
|
)
|
|
if hr != S_OK || bstr == 0 {
|
|
return ""
|
|
}
|
|
s := bstrToString(bstr)
|
|
sysFreeString(bstr)
|
|
return s
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetCurrentClassName() string {
|
|
var bstr uintptr
|
|
hr := callSyscallN(e.lpVtbl.CurrentClassName,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(unsafe.Pointer(&bstr)),
|
|
)
|
|
if hr != S_OK || bstr == 0 {
|
|
return ""
|
|
}
|
|
s := bstrToString(bstr)
|
|
sysFreeString(bstr)
|
|
return s
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetCurrentBoundingRectangle() (left, top, right, bottom float64) {
|
|
var r [4]float64
|
|
callSyscallN(e.lpVtbl.CurrentBoundingRectangle,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(unsafe.Pointer(&r[0])),
|
|
)
|
|
return r[0], r[1], r[0] + r[2], r[1] + r[3]
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetClickablePoint() (x, y float64, gotIt bool) {
|
|
var pt [2]float64
|
|
var ok int32
|
|
callSyscallN(e.lpVtbl.GetClickablePoint,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(unsafe.Pointer(&pt[0])),
|
|
uintptr(unsafe.Pointer(&ok)),
|
|
)
|
|
return pt[0], pt[1], ok != 0
|
|
}
|
|
|
|
func (e *iuiAutomationElement) FindFirst(scope int32, condition *iuiAutomationCondition) *iuiAutomationElement {
|
|
var found *iuiAutomationElement
|
|
hr := callSyscallN(e.lpVtbl.FindFirst,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(scope),
|
|
uintptr(unsafe.Pointer(condition)),
|
|
uintptr(unsafe.Pointer(&found)),
|
|
)
|
|
if hr != S_OK || found == nil {
|
|
return nil
|
|
}
|
|
return found
|
|
}
|
|
|
|
func (e *iuiAutomationElement) GetNativeWindowHandle() uintptr {
|
|
var hwnd uintptr
|
|
callSyscallN(e.lpVtbl.CurrentNativeWindowHandle,
|
|
uintptr(unsafe.Pointer(e)),
|
|
uintptr(unsafe.Pointer(&hwnd)),
|
|
)
|
|
return hwnd
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UIA Pattern vtable structs
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// IUIAutomationInvokePattern: IUnknown(0-2), Invoke(3)
|
|
type iuiAutomationInvokePatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Invoke uintptr // 3
|
|
}
|
|
|
|
type iuiAutomationInvokePattern struct {
|
|
lpVtbl *iuiAutomationInvokePatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationInvokePattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationInvokePattern) Invoke() uintptr {
|
|
return callSyscallN(p.lpVtbl.Invoke, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
// IUIAutomationTogglePattern: IUnknown(0-2), Toggle(3), CurrentToggleState(4)
|
|
type iuiAutomationTogglePatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Toggle uintptr // 3
|
|
CurrentToggleState uintptr // 4
|
|
CachedToggleState uintptr // 5
|
|
}
|
|
|
|
type iuiAutomationTogglePattern struct {
|
|
lpVtbl *iuiAutomationTogglePatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationTogglePattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationTogglePattern) Toggle() uintptr {
|
|
return callSyscallN(p.lpVtbl.Toggle, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
// IUIAutomationExpandCollapsePattern: IUnknown(0-2), Expand(3), Collapse(4), CurrentExpandCollapseState(5)
|
|
type iuiAutomationExpandCollapsePatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Expand uintptr // 3
|
|
Collapse uintptr // 4
|
|
CurrentExpandCollapseState uintptr // 5
|
|
CachedExpandCollapseState uintptr // 6
|
|
}
|
|
|
|
type iuiAutomationExpandCollapsePattern struct {
|
|
lpVtbl *iuiAutomationExpandCollapsePatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationExpandCollapsePattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationExpandCollapsePattern) Expand() uintptr {
|
|
return callSyscallN(p.lpVtbl.Expand, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationExpandCollapsePattern) Collapse() uintptr {
|
|
return callSyscallN(p.lpVtbl.Collapse, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationExpandCollapsePattern) GetCurrentExpandCollapseState() int32 {
|
|
var state int32
|
|
callSyscallN(p.lpVtbl.CurrentExpandCollapseState,
|
|
uintptr(unsafe.Pointer(p)),
|
|
uintptr(unsafe.Pointer(&state)),
|
|
)
|
|
return state
|
|
}
|
|
|
|
// IUIAutomationSelectionItemPattern: IUnknown(0-2), Select(3), AddToSelection(4), RemoveFromSelection(5), CurrentIsSelected(6)
|
|
type iuiAutomationSelectionItemPatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Select uintptr // 3
|
|
AddToSelection uintptr // 4
|
|
RemoveFromSelection uintptr // 5
|
|
CurrentIsSelected uintptr // 6
|
|
CurrentSelectionContainer uintptr // 7
|
|
CachedIsSelected uintptr // 8
|
|
CachedSelectionContainer uintptr // 9
|
|
}
|
|
|
|
type iuiAutomationSelectionItemPattern struct {
|
|
lpVtbl *iuiAutomationSelectionItemPatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationSelectionItemPattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationSelectionItemPattern) Select() uintptr {
|
|
return callSyscallN(p.lpVtbl.Select, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
// IUIAutomationScrollPattern: IUnknown(0-2), Scroll(3), SetScrollPercent(4), get_CurrentHorizontalScrollPercent(5)...
|
|
type iuiAutomationScrollPatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Scroll uintptr // 3
|
|
SetScrollPercent uintptr // 4
|
|
CurrentHorizontalScrollPercent uintptr // 5
|
|
CurrentVerticalScrollPercent uintptr // 6
|
|
CurrentHorizontalViewSize uintptr // 7
|
|
CurrentVerticalViewSize uintptr // 8
|
|
CurrentHorizontallyScrollable uintptr // 9
|
|
CurrentVerticallyScrollable uintptr // 10
|
|
CachedHorizontalScrollPercent uintptr // 11
|
|
CachedVerticalScrollPercent uintptr // 12
|
|
CachedHorizontalViewSize uintptr // 13
|
|
CachedVerticalViewSize uintptr // 14
|
|
CachedHorizontallyScrollable uintptr // 15
|
|
CachedVerticallyScrollable uintptr // 16
|
|
}
|
|
|
|
type iuiAutomationScrollPattern struct {
|
|
lpVtbl *iuiAutomationScrollPatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationScrollPattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationScrollPattern) Scroll(horizAmount, vertAmount int32) uintptr {
|
|
return callSyscallN(p.lpVtbl.Scroll,
|
|
uintptr(unsafe.Pointer(p)),
|
|
uintptr(horizAmount),
|
|
uintptr(vertAmount),
|
|
)
|
|
}
|
|
|
|
func (p *iuiAutomationScrollPattern) SetScrollPercent(horizPercent, vertPercent float64) uintptr {
|
|
return callSyscallN(p.lpVtbl.SetScrollPercent,
|
|
uintptr(unsafe.Pointer(p)),
|
|
uintptr(*(*uint64)(unsafe.Pointer(&horizPercent))),
|
|
uintptr(*(*uint64)(unsafe.Pointer(&vertPercent))),
|
|
)
|
|
}
|
|
|
|
// IUIAutomationValuePattern: IUnknown(0-2), SetValue(3), CurrentValue(4)
|
|
type iuiAutomationValuePatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetValue uintptr // 3
|
|
CurrentValue uintptr // 4
|
|
CurrentIsReadOnly uintptr // 5
|
|
CachedValue uintptr // 6
|
|
CachedIsReadOnly uintptr // 7
|
|
}
|
|
|
|
type iuiAutomationValuePattern struct {
|
|
lpVtbl *iuiAutomationValuePatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationValuePattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationValuePattern) SetValue(val string) uintptr {
|
|
bstr := stringToBSTR(val)
|
|
if bstr == 0 {
|
|
return 1
|
|
}
|
|
defer sysFreeString(bstr)
|
|
return callSyscallN(p.lpVtbl.SetValue,
|
|
uintptr(unsafe.Pointer(p)),
|
|
bstr,
|
|
)
|
|
}
|
|
|
|
func (p *iuiAutomationValuePattern) GetCurrentValue() string {
|
|
var bstr uintptr
|
|
hr := callSyscallN(p.lpVtbl.CurrentValue,
|
|
uintptr(unsafe.Pointer(p)),
|
|
uintptr(unsafe.Pointer(&bstr)),
|
|
)
|
|
if hr != S_OK || bstr == 0 {
|
|
return ""
|
|
}
|
|
s := bstrToString(bstr)
|
|
sysFreeString(bstr)
|
|
return s
|
|
}
|
|
|
|
// IUIAutomationTransformPattern: IUnknown(0-2), Move(3), Resize(4), Rotate(5)
|
|
type iuiAutomationTransformPatternVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
Move uintptr // 3
|
|
Resize uintptr // 4
|
|
Rotate uintptr // 5
|
|
CurrentCanMove uintptr // 6
|
|
CurrentCanResize uintptr // 7
|
|
CurrentCanRotate uintptr // 8
|
|
CachedCanMove uintptr // 9
|
|
CachedCanResize uintptr // 10
|
|
CachedCanRotate uintptr // 11
|
|
}
|
|
|
|
type iuiAutomationTransformPattern struct {
|
|
lpVtbl *iuiAutomationTransformPatternVtbl
|
|
}
|
|
|
|
func (p *iuiAutomationTransformPattern) Release() {
|
|
if p == nil || p.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(p.lpVtbl.Release, uintptr(unsafe.Pointer(p)))
|
|
}
|
|
|
|
func (p *iuiAutomationTransformPattern) Move(x, y float64) uintptr {
|
|
return callSyscallN(p.lpVtbl.Move,
|
|
uintptr(unsafe.Pointer(p)),
|
|
uintptr(*(*uint64)(unsafe.Pointer(&x))),
|
|
uintptr(*(*uint64)(unsafe.Pointer(&y))),
|
|
)
|
|
}
|
|
|
|
var (
|
|
oleaut32DLL = syscall.NewLazyDLL("oleaut32.dll")
|
|
procSysAllocString = oleaut32DLL.NewProc("SysAllocString")
|
|
procSysFreeString = oleaut32DLL.NewProc("SysFreeString")
|
|
procSysStringLen = oleaut32DLL.NewProc("SysStringLen")
|
|
)
|
|
|
|
func stringToBSTR(s string) uintptr {
|
|
u16, err := syscall.UTF16PtrFromString(s)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
bstr, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(u16)))
|
|
return bstr
|
|
}
|
|
|
|
func sysFreeString(bstr uintptr) {
|
|
if bstr != 0 {
|
|
procSysFreeString.Call(bstr)
|
|
}
|
|
}
|
|
|
|
func bstrToString(bstr uintptr) string {
|
|
if bstr == 0 {
|
|
return ""
|
|
}
|
|
length, _, _ := procSysStringLen.Call(bstr)
|
|
if length == 0 {
|
|
return ""
|
|
}
|
|
slice := unsafe.Slice((*uint16)(unsafe.Pointer(bstr)), length)
|
|
return syscall.UTF16ToString(slice)
|
|
}
|
|
|
|
type uiaVariant struct {
|
|
vt uint16
|
|
reserved [6]byte
|
|
val [8]byte // union
|
|
}
|
|
|
|
func (v *uiaVariant) Int32() int32 {
|
|
return *(*int32)(unsafe.Pointer(&v.val[0]))
|
|
}
|
|
|
|
func (v *uiaVariant) Float64() float64 {
|
|
return *(*float64)(unsafe.Pointer(&v.val[0]))
|
|
}
|
|
|
|
func (v *uiaVariant) BSTR() uintptr {
|
|
return *(*uintptr)(unsafe.Pointer(&v.val[0]))
|
|
}
|
|
|
|
var (
|
|
uiaSingleton *iuiAutomation
|
|
uiaTreeWalker *iuiAutomationTreeWalker
|
|
uiaInitOnce sync.Once
|
|
uiaInitErr error
|
|
uiaCOMInitialized bool
|
|
)
|
|
|
|
func uiaInit() error {
|
|
uiaInitOnce.Do(func() {
|
|
hr, _, _ := procCoInitializeEx.Call(0, COINIT_MULTITHREADED)
|
|
if hr != 0 && hr != 1 {
|
|
uiaInitErr = fmt.Errorf("CoInitializeEx failed: 0x%x", hr)
|
|
return
|
|
}
|
|
uiaCOMInitialized = true
|
|
|
|
hr, _, _ = procCoCreateInstance.Call(
|
|
uintptr(unsafe.Pointer(&CLSID_CUIAutomation)),
|
|
0,
|
|
CLSCTX_INPROC_SERVER,
|
|
uintptr(unsafe.Pointer(&IID_IUIAutomation)),
|
|
uintptr(unsafe.Pointer(&uiaSingleton)),
|
|
)
|
|
if hr != S_OK || uiaSingleton == nil {
|
|
uiaInitErr = fmt.Errorf("CoCreateInstance(CUIAutomation) failed: 0x%x", hr)
|
|
if uiaCOMInitialized {
|
|
procCoUninitialize.Call()
|
|
uiaCOMInitialized = false
|
|
}
|
|
return
|
|
}
|
|
uiaTreeWalker = uiaSingleton.GetControlViewWalker()
|
|
log.Printf("hvnc uia: IUIAutomation initialized successfully (walker=%v)", uiaTreeWalker != nil)
|
|
})
|
|
return uiaInitErr
|
|
}
|
|
|
|
func uiaCleanup() {
|
|
uiaStopWorker()
|
|
|
|
if uiaTreeWalker != nil {
|
|
uiaTreeWalker.Release()
|
|
uiaTreeWalker = nil
|
|
}
|
|
if uiaSingleton != nil {
|
|
uiaSingleton.Release()
|
|
uiaSingleton = nil
|
|
}
|
|
if uiaCOMInitialized {
|
|
procCoUninitialize.Call()
|
|
uiaCOMInitialized = false
|
|
}
|
|
uiaInitOnce = sync.Once{}
|
|
uiaInitErr = nil
|
|
}
|
|
|
|
var (
|
|
uiaWorkerCh chan func()
|
|
uiaWorkerOnce sync.Once
|
|
uiaWorkerDone chan struct{}
|
|
)
|
|
|
|
func uiaEnsureWorker() {
|
|
uiaWorkerOnce.Do(func() {
|
|
uiaWorkerCh = make(chan func(), 64)
|
|
uiaWorkerDone = make(chan struct{})
|
|
go uiaWorkerLoop()
|
|
})
|
|
}
|
|
|
|
func uiaWorkerLoop() {
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
defer close(uiaWorkerDone)
|
|
|
|
hvncDesktopMu.Lock()
|
|
dh := hvncDesktopHandle
|
|
hvncDesktopMu.Unlock()
|
|
if dh != 0 {
|
|
r, _, err := procSetThreadDesktop.Call(dh)
|
|
if r == 0 {
|
|
log.Printf("hvnc uia: worker SetThreadDesktop failed: %v", err)
|
|
for range uiaWorkerCh {
|
|
}
|
|
return
|
|
}
|
|
} else {
|
|
log.Printf("hvnc uia: warning — worker started before HVNC desktop exists")
|
|
}
|
|
|
|
if err := uiaInit(); err != nil {
|
|
log.Printf("hvnc uia: worker init failed: %v", err)
|
|
for range uiaWorkerCh {
|
|
}
|
|
return
|
|
}
|
|
|
|
for fn := range uiaWorkerCh {
|
|
fn()
|
|
}
|
|
}
|
|
|
|
func uiaRunAsync(fn func()) {
|
|
uiaEnsureWorker()
|
|
select {
|
|
case uiaWorkerCh <- fn:
|
|
default:
|
|
}
|
|
}
|
|
|
|
func uiaStopWorker() {
|
|
if uiaWorkerCh != nil {
|
|
close(uiaWorkerCh)
|
|
<-uiaWorkerDone
|
|
uiaWorkerCh = nil
|
|
}
|
|
uiaWorkerOnce = sync.Once{}
|
|
}
|
|
|
|
var (
|
|
winui3CacheMu sync.Mutex
|
|
winui3Cache map[uintptr]winui3CacheEntry
|
|
winui3CacheClean time.Time
|
|
)
|
|
|
|
type winui3CacheEntry struct {
|
|
isWinUI3 bool
|
|
ts time.Time
|
|
}
|
|
|
|
const winui3CacheTTL = 5 * time.Second
|
|
const winui3CacheMaxSize = 256
|
|
|
|
func getWindowClassName(hwnd uintptr) string {
|
|
buf := make([]uint16, 256)
|
|
n, _, _ := procGetClassNameW.Call(hwnd, uintptr(unsafe.Pointer(&buf[0])), 256)
|
|
if n == 0 {
|
|
return ""
|
|
}
|
|
return syscall.UTF16ToString(buf[:n])
|
|
}
|
|
|
|
var winui3ClassPrefixes = []string{
|
|
"WinUIDesktopWin32WindowClass",
|
|
"Microsoft.UI.Content.DesktopChildSiteBridge",
|
|
"Windows.UI.Input.InputSite.WindowClass",
|
|
"Microsoft.UI.Windowing.DesktopWindowBridge",
|
|
"Windows.UI.Composition.DesktopWindowContentBridge",
|
|
"DesktopWindowXamlSource",
|
|
}
|
|
|
|
func classMatchesWinUI3(cls string) bool {
|
|
if cls == "" {
|
|
return false
|
|
}
|
|
for _, prefix := range winui3ClassPrefixes {
|
|
if cls == prefix {
|
|
return true
|
|
}
|
|
}
|
|
// Also match generic "Microsoft.UI." prefix
|
|
if len(cls) > 13 && cls[:13] == "Microsoft.UI." {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isWinUI3Window(hwnd uintptr) bool {
|
|
if hwnd == 0 {
|
|
return false
|
|
}
|
|
|
|
now := time.Now()
|
|
winui3CacheMu.Lock()
|
|
if winui3Cache != nil {
|
|
if entry, ok := winui3Cache[hwnd]; ok && now.Sub(entry.ts) < winui3CacheTTL {
|
|
winui3CacheMu.Unlock()
|
|
return entry.isWinUI3
|
|
}
|
|
}
|
|
winui3CacheMu.Unlock()
|
|
|
|
result := detectWinUI3(hwnd)
|
|
|
|
winui3CacheMu.Lock()
|
|
if winui3Cache == nil {
|
|
winui3Cache = make(map[uintptr]winui3CacheEntry, 32)
|
|
}
|
|
if now.Sub(winui3CacheClean) > 10*time.Second {
|
|
for k, v := range winui3Cache {
|
|
if now.Sub(v.ts) > winui3CacheTTL*2 {
|
|
delete(winui3Cache, k)
|
|
}
|
|
}
|
|
if len(winui3Cache) > winui3CacheMaxSize {
|
|
winui3Cache = make(map[uintptr]winui3CacheEntry, 32)
|
|
}
|
|
winui3CacheClean = now
|
|
}
|
|
winui3Cache[hwnd] = winui3CacheEntry{isWinUI3: result, ts: now}
|
|
winui3CacheMu.Unlock()
|
|
return result
|
|
}
|
|
|
|
func detectWinUI3(hwnd uintptr) bool {
|
|
cls := getWindowClassName(hwnd)
|
|
if classMatchesWinUI3(cls) {
|
|
return true
|
|
}
|
|
root := rootWindow(hwnd)
|
|
if root != 0 && root != hwnd {
|
|
cls = getWindowClassName(root)
|
|
if classMatchesWinUI3(cls) {
|
|
return true
|
|
}
|
|
}
|
|
parent := hwnd
|
|
for i := 0; i < 4; i++ {
|
|
p, _, _ := procGetAncestor.Call(parent, 1) // GA_PARENT=1
|
|
if p == 0 || p == parent {
|
|
break
|
|
}
|
|
cls = getWindowClassName(p)
|
|
if classMatchesWinUI3(cls) {
|
|
return true
|
|
}
|
|
parent = p
|
|
}
|
|
return false
|
|
}
|
|
|
|
func resetWinUI3Cache() {
|
|
winui3CacheMu.Lock()
|
|
winui3Cache = nil
|
|
winui3CacheMu.Unlock()
|
|
}
|
|
|
|
var (
|
|
inputSiteCacheMu sync.Mutex
|
|
inputSiteCache map[uintptr]inputSiteCacheEntry
|
|
)
|
|
|
|
type inputSiteCacheEntry struct {
|
|
inputSite uintptr
|
|
ts time.Time
|
|
}
|
|
|
|
func findInputSiteChild(hwnd uintptr) uintptr {
|
|
if hwnd == 0 {
|
|
return 0
|
|
}
|
|
root := rootWindow(hwnd)
|
|
if root == 0 {
|
|
root = hwnd
|
|
}
|
|
|
|
now := time.Now()
|
|
inputSiteCacheMu.Lock()
|
|
if inputSiteCache != nil {
|
|
if entry, ok := inputSiteCache[root]; ok && now.Sub(entry.ts) < 10*time.Second {
|
|
inputSiteCacheMu.Unlock()
|
|
return entry.inputSite
|
|
}
|
|
}
|
|
inputSiteCacheMu.Unlock()
|
|
|
|
found := enumChildrenForInputSite(root)
|
|
|
|
inputSiteCacheMu.Lock()
|
|
if inputSiteCache == nil {
|
|
inputSiteCache = make(map[uintptr]inputSiteCacheEntry, 16)
|
|
}
|
|
inputSiteCache[root] = inputSiteCacheEntry{inputSite: found, ts: now}
|
|
inputSiteCacheMu.Unlock()
|
|
return found
|
|
}
|
|
|
|
func enumChildrenForInputSite(parent uintptr) uintptr {
|
|
var result uintptr
|
|
cb := syscall.NewCallback(func(hwnd uintptr, lparam uintptr) uintptr {
|
|
cls := getWindowClassName(hwnd)
|
|
if cls == "Windows.UI.Input.InputSite.WindowClass" {
|
|
*(*uintptr)(unsafe.Pointer(lparam)) = hwnd
|
|
return 0 // stop enumeration
|
|
}
|
|
child := enumChildrenForInputSite(hwnd)
|
|
if child != 0 {
|
|
*(*uintptr)(unsafe.Pointer(lparam)) = child
|
|
return 0
|
|
}
|
|
return 1 // continue
|
|
})
|
|
procEnumChildWindows.Call(parent, cb, uintptr(unsafe.Pointer(&result)))
|
|
return result
|
|
}
|
|
|
|
func resetInputSiteCache() {
|
|
inputSiteCacheMu.Lock()
|
|
inputSiteCache = nil
|
|
inputSiteCacheMu.Unlock()
|
|
}
|
|
|
|
var (
|
|
uiaActiveElement *iuiAutomationElement
|
|
uiaActiveMu sync.Mutex
|
|
uiaDragActive bool
|
|
uiaDragStartPt point
|
|
uiaLastClickTime time.Time
|
|
uiaLastClickHwnd uintptr
|
|
)
|
|
|
|
func uiaSetActiveElement(elem *iuiAutomationElement) {
|
|
uiaActiveMu.Lock()
|
|
if uiaActiveElement != nil {
|
|
uiaActiveElement.Release()
|
|
}
|
|
uiaActiveElement = elem
|
|
uiaActiveMu.Unlock()
|
|
}
|
|
|
|
func uiaGetActiveElement() *iuiAutomationElement {
|
|
uiaActiveMu.Lock()
|
|
e := uiaActiveElement
|
|
uiaActiveMu.Unlock()
|
|
return e
|
|
}
|
|
|
|
func uiaClearActiveElement() {
|
|
uiaSetActiveElement(nil)
|
|
}
|
|
|
|
func uiaHandleMouseMove(hwnd uintptr, screenPt point) {
|
|
uiaEnsureWorker()
|
|
|
|
inputSite := findInputSiteChild(hwnd)
|
|
if inputSite != 0 {
|
|
clientPt := screenPt
|
|
procScreenToClient.Call(inputSite, uintptr(unsafe.Pointer(&clientPt)))
|
|
postMouseMessage(inputSite, WM_MOUSEMOVE, uintptr(currentMouseButtons()), clientPt)
|
|
}
|
|
|
|
uiaActiveMu.Lock()
|
|
dragging := uiaDragActive
|
|
elem := uiaActiveElement
|
|
uiaActiveMu.Unlock()
|
|
|
|
if dragging && elem != nil {
|
|
pt := screenPt // copy for closure
|
|
uiaRunAsync(func() {
|
|
uiaActiveMu.Lock()
|
|
startPt := uiaDragStartPt
|
|
uiaActiveMu.Unlock()
|
|
|
|
dx := float64(pt.x - startPt.x)
|
|
dy := float64(pt.y - startPt.y)
|
|
pat := elem.GetCurrentPattern(uiaTransformPatternID)
|
|
if pat != nil {
|
|
tp := (*iuiAutomationTransformPattern)(unsafe.Pointer(pat))
|
|
tp.Move(dx, dy)
|
|
tp.Release()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func uiaHandleMouseButton(hwnd uintptr, screenPt point, button int, down bool) error {
|
|
uiaEnsureWorker()
|
|
|
|
if down {
|
|
uiaActiveMu.Lock()
|
|
uiaDragStartPt = screenPt
|
|
uiaDragActive = false
|
|
uiaActiveMu.Unlock()
|
|
|
|
inputSite := findInputSiteChild(hwnd)
|
|
if inputSite != 0 {
|
|
clientPt := screenPt
|
|
procScreenToClient.Call(inputSite, uintptr(unsafe.Pointer(&clientPt)))
|
|
switch button {
|
|
case 0:
|
|
postMouseMessage(inputSite, WM_LBUTTONDOWN, MK_LBUTTON, clientPt)
|
|
case 2:
|
|
postMouseMessage(inputSite, WM_RBUTTONDOWN, MK_RBUTTON, clientPt)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
inputSite := findInputSiteChild(hwnd)
|
|
if inputSite != 0 {
|
|
clientPt := screenPt
|
|
procScreenToClient.Call(inputSite, uintptr(unsafe.Pointer(&clientPt)))
|
|
switch button {
|
|
case 0:
|
|
postMouseMessage(inputSite, WM_LBUTTONUP, 0, clientPt)
|
|
case 2:
|
|
postMouseMessage(inputSite, WM_RBUTTONUP, 0, clientPt)
|
|
}
|
|
}
|
|
|
|
uiaActiveMu.Lock()
|
|
wasInDrag := uiaDragActive
|
|
uiaDragActive = false
|
|
uiaActiveMu.Unlock()
|
|
|
|
if wasInDrag {
|
|
return nil
|
|
}
|
|
|
|
now := time.Now()
|
|
isDoubleClick := now.Sub(uiaLastClickTime) < 400*time.Millisecond && uiaLastClickHwnd == hwnd
|
|
uiaLastClickTime = now
|
|
uiaLastClickHwnd = hwnd
|
|
|
|
pt := screenPt // copy for closure
|
|
btn := button
|
|
isDbl := isDoubleClick
|
|
is := inputSite
|
|
h := hwnd
|
|
uiaRunAsync(func() {
|
|
uiaMouseUpWorker(h, pt, btn, isDbl, is)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func uiaMouseUpWorker(hwnd uintptr, screenPt point, button int, isDoubleClick bool, inputSite uintptr) {
|
|
if uiaSingleton == nil {
|
|
return
|
|
}
|
|
|
|
elem := uiaSingleton.ElementFromPoint(screenPt)
|
|
if elem == nil {
|
|
elem = uiaSingleton.ElementFromHandle(hwnd)
|
|
}
|
|
if elem == nil {
|
|
return
|
|
}
|
|
defer elem.Release()
|
|
|
|
switch button {
|
|
case 0: // Left click
|
|
if uiaTryAction(elem, isDoubleClick) {
|
|
return
|
|
}
|
|
if uiaTreeWalker != nil {
|
|
current := elem
|
|
for i := 0; i < 6; i++ {
|
|
parent := uiaTreeWalker.GetParentElement(current)
|
|
if parent == nil {
|
|
break
|
|
}
|
|
if current != elem {
|
|
current.Release()
|
|
}
|
|
current = parent
|
|
if uiaTryAction(current, isDoubleClick) {
|
|
if current != elem {
|
|
current.Release()
|
|
}
|
|
return
|
|
}
|
|
}
|
|
if current != elem {
|
|
current.Release()
|
|
}
|
|
}
|
|
elem.SetFocus()
|
|
|
|
case 2: // Right click — context menu
|
|
if inputSite != 0 {
|
|
procPostMessageW.Call(inputSite, WM_CONTEXTMENU, inputSite, makeLParam(screenPt.x, screenPt.y))
|
|
}
|
|
}
|
|
}
|
|
|
|
func uiaTryAction(elem *iuiAutomationElement, isDoubleClick bool) bool {
|
|
if uiaInvoke(elem) {
|
|
if isDoubleClick {
|
|
uiaInvoke(elem)
|
|
}
|
|
return true
|
|
}
|
|
if uiaToggle(elem) {
|
|
return true
|
|
}
|
|
if uiaExpandCollapse(elem) {
|
|
return true
|
|
}
|
|
if uiaSelectItem(elem) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func uiaHandleMouseWheel(hwnd uintptr, screenPt point, delta int32) error {
|
|
uiaEnsureWorker()
|
|
|
|
inputSite := findInputSiteChild(hwnd)
|
|
if inputSite != 0 {
|
|
wparam := (uintptr(uint16(delta)) << 16) | uintptr(currentMouseButtons())
|
|
procPostMessageW.Call(inputSite, WM_MOUSEWHEEL, wparam, makeLParam(screenPt.x, screenPt.y))
|
|
}
|
|
|
|
pt := screenPt
|
|
h := hwnd
|
|
d := delta
|
|
uiaRunAsync(func() {
|
|
if uiaSingleton == nil {
|
|
return
|
|
}
|
|
elem := uiaSingleton.ElementFromPoint(pt)
|
|
if elem == nil {
|
|
elem = uiaSingleton.ElementFromHandle(h)
|
|
}
|
|
if elem == nil {
|
|
return
|
|
}
|
|
defer elem.Release()
|
|
uiaScrollElement(elem, d)
|
|
})
|
|
return nil
|
|
}
|
|
|
|
func uiaHandleKey(hwnd uintptr, vk uint16, down bool) error {
|
|
uiaEnsureWorker()
|
|
|
|
inputSite := findInputSiteChild(hwnd)
|
|
if inputSite != 0 {
|
|
if down {
|
|
if ch := virtualKeyToChars(vk); len(ch) > 0 && !isNonPrintableVK(vk) {
|
|
for _, r := range ch {
|
|
procPostMessageW.Call(inputSite, WM_CHAR, uintptr(r), uintptr(1))
|
|
}
|
|
} else {
|
|
postKeyMessage(inputSite, WM_KEYDOWN, vk)
|
|
}
|
|
} else {
|
|
postKeyMessage(inputSite, WM_KEYUP, vk)
|
|
}
|
|
}
|
|
|
|
if down {
|
|
key := vk
|
|
uiaRunAsync(func() {
|
|
if uiaSingleton == nil {
|
|
return
|
|
}
|
|
if !isModifierVK(key) && !isNonPrintableVK(key) {
|
|
elem := uiaSingleton.GetFocusedElement()
|
|
if elem != nil {
|
|
uiaTypeChar(elem, key)
|
|
elem.Release()
|
|
}
|
|
} else if key == 0x08 || key == 0x2E {
|
|
elem := uiaSingleton.GetFocusedElement()
|
|
if elem != nil {
|
|
uiaHandleDeleteKey(elem, key)
|
|
elem.Release()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func uiaInvoke(elem *iuiAutomationElement) bool {
|
|
pat := elem.GetCurrentPattern(uiaInvokePatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
ip := (*iuiAutomationInvokePattern)(unsafe.Pointer(pat))
|
|
defer ip.Release()
|
|
ip.Invoke()
|
|
return true
|
|
}
|
|
|
|
func uiaToggle(elem *iuiAutomationElement) bool {
|
|
pat := elem.GetCurrentPattern(uiaTogglePatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
tp := (*iuiAutomationTogglePattern)(unsafe.Pointer(pat))
|
|
defer tp.Release()
|
|
tp.Toggle()
|
|
return true
|
|
}
|
|
|
|
func uiaExpandCollapse(elem *iuiAutomationElement) bool {
|
|
pat := elem.GetCurrentPattern(uiaExpandCollapsePatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
ecp := (*iuiAutomationExpandCollapsePattern)(unsafe.Pointer(pat))
|
|
defer ecp.Release()
|
|
state := ecp.GetCurrentExpandCollapseState()
|
|
if state == uiaExpandCollapseStateCollapsed || state == uiaExpandCollapseStatePartiallyExpanded {
|
|
ecp.Expand()
|
|
} else if state == uiaExpandCollapseStateExpanded {
|
|
ecp.Collapse()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func uiaSelectItem(elem *iuiAutomationElement) bool {
|
|
pat := elem.GetCurrentPattern(uiaSelectionItemPatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
sip := (*iuiAutomationSelectionItemPattern)(unsafe.Pointer(pat))
|
|
defer sip.Release()
|
|
sip.Select()
|
|
return true
|
|
}
|
|
|
|
func uiaScrollElement(elem *iuiAutomationElement, delta int32) bool {
|
|
pat := elem.GetCurrentPattern(uiaScrollPatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
sp := (*iuiAutomationScrollPattern)(unsafe.Pointer(pat))
|
|
defer sp.Release()
|
|
|
|
var vertAmount int32
|
|
if delta > 0 {
|
|
vertAmount = uiaScrollAmountSmallDecrement // scroll up
|
|
} else {
|
|
vertAmount = uiaScrollAmountSmallIncrement // scroll down
|
|
}
|
|
notches := int(delta / 120)
|
|
if notches < 0 {
|
|
notches = -notches
|
|
}
|
|
if notches < 1 {
|
|
notches = 1
|
|
}
|
|
for i := 0; i < notches; i++ {
|
|
sp.Scroll(uiaScrollAmountNoAmount, vertAmount)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func uiaTypeChar(elem *iuiAutomationElement, vk uint16) bool {
|
|
pat := elem.GetCurrentPattern(uiaValuePatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
vp := (*iuiAutomationValuePattern)(unsafe.Pointer(pat))
|
|
defer vp.Release()
|
|
|
|
ch := virtualKeyToChars(vk)
|
|
if len(ch) == 0 {
|
|
return false
|
|
}
|
|
|
|
current := vp.GetCurrentValue()
|
|
newVal := current + string(ch)
|
|
vp.SetValue(newVal)
|
|
return true
|
|
}
|
|
|
|
func uiaHandleDeleteKey(elem *iuiAutomationElement, vk uint16) bool {
|
|
pat := elem.GetCurrentPattern(uiaValuePatternID)
|
|
if pat == nil {
|
|
return false
|
|
}
|
|
vp := (*iuiAutomationValuePattern)(unsafe.Pointer(pat))
|
|
defer vp.Release()
|
|
|
|
current := vp.GetCurrentValue()
|
|
if len(current) == 0 {
|
|
return true
|
|
}
|
|
|
|
if vk == 0x08 { // Backspace — remove last char
|
|
runes := []rune(current)
|
|
if len(runes) > 0 {
|
|
vp.SetValue(string(runes[:len(runes)-1]))
|
|
}
|
|
}
|
|
|
|
return vk == 0x08
|
|
}
|
|
|
|
func uiaHandleDragMove(screenPt point) {
|
|
uiaActiveMu.Lock()
|
|
if !uiaDragActive {
|
|
dx := screenPt.x - uiaDragStartPt.x
|
|
dy := screenPt.y - uiaDragStartPt.y
|
|
if dx*dx+dy*dy > 25 {
|
|
uiaDragActive = true
|
|
}
|
|
}
|
|
uiaActiveMu.Unlock()
|
|
}
|