19using System.Runtime.InteropServices;
21using System.Windows.Automation;
41 internal static class MsaaHelper
43 private const uint OBJID_CLIENT = 0xFFFFFFFC;
44 private const int STATE_SYSTEM_SELECTED = 0x00000002;
45 private const int SELFLAG_TAKEFOCUS = 0x00000001;
46 private const int SELFLAG_TAKESELECTION = 0x00000002;
47 private const int CHILDID_SELF = 0;
52 private const int DepthLimit = 12;
53 private const int NodeLimit = 4000;
57 private const double RectSlack = 2.0;
59 private static Guid IID_IAccessible =
new Guid(
"618736E0-3C3D-11CF-810C-00AA00389B71");
61 [DllImport(
"oleacc.dll")]
62 private static extern int AccessibleObjectFromWindow(IntPtr window, uint objectId, ref Guid interfaceId,
63 [MarshalAs(UnmanagedType.Interface)] out
object accessible);
65 [DllImport(
"oleacc.dll")]
66 private static extern int AccessibleChildren([MarshalAs(UnmanagedType.Interface)] IAccessible container,
67 int startingChild,
int wanted,
68 [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct)]
object[] children,
81 internal IAccessible Owner;
82 internal object ChildId;
91 internal static bool? Selected(Application application, AutomationElement element)
94 Node node = NodeFor(application, element);
98 int state = StateOf(node);
100 retVal = STATE_SYSTEM_SELECTED == (state & STATE_SYSTEM_SELECTED);
118 internal static bool Select(Application application, AutomationElement element)
121 Node node = NodeFor(application, element);
127 node.Owner.accSelect(SELFLAG_TAKEFOCUS | SELFLAG_TAKESELECTION, node.ChildId);
129 retVal = STATE_SYSTEM_SELECTED == (StateOf(node) & STATE_SYSTEM_SELECTED);
136 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"MSAA would not select [{NameOf(node)}], so it was not selected that way.", application, GPALObjectType.Application);
155 internal static bool Press(Application application, AutomationElement element)
158 Node node = NodeFor(application, element);
164 node.Owner.accDoDefaultAction(node.ChildId);
172 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"MSAA has no default action for [{NameOf(node)}], so it was not pressed that way.", application, GPALObjectType.Application);
186 private static Node NodeFor(Application application, AutomationElement element)
189 Rect wanted = Rect.Empty;
190 IntPtr window = IntPtr.Zero;
197 wanted =
null == element ? Rect.Empty : element.Current.BoundingRectangle;
198 window = WindowOf(element);
207 if (
false == wanted.IsEmpty && IntPtr.Zero != window)
211 if (0 == AccessibleObjectFromWindow(window, OBJID_CLIENT, ref IID_IAccessible, out root) && root is IAccessible accessible)
217 retVal = Search(
new Node { Owner = accessible, ChildId = CHILDID_SELF }, wanted, 0, ref looked);
228 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"MSAA node at [{wanted}] in window [{window}] was [{(null == retVal ? "not found
" : NameOf(retVal))}].", application, GPALObjectType.Application);
241 private static Node Search(Node node, Rect wanted,
int depth, ref
int looked)
245 if (depth <= DepthLimit && ++looked <= NodeLimit)
247 if (
true == Matches(node, wanted))
251 foreach (Node child
in ChildrenOf(node))
253 retVal = Search(child, wanted, depth + 1, ref looked);
274 private static System.Collections.Generic.List<Node> ChildrenOf(Node node)
276 var retVal =
new System.Collections.Generic.List<Node>();
280 if (
true == IsSelf(node.ChildId))
286 count = node.Owner.accChildCount;
296 object[] children =
new object[count];
299 if (0 == AccessibleChildren(node.Owner, 0, count, children, out obtained))
300 for (
int child = 0; child < obtained; child++)
301 if (children[child] is IAccessible ownObject)
302 retVal.Add(
new Node { Owner = ownObject, ChildId = CHILDID_SELF });
303 else if (
null != children[child])
304 retVal.Add(
new Node { Owner = node.Owner, ChildId = children[child] });
315 private static bool Matches(Node node, Rect wanted)
318 int left, top, width, height;
322 node.Owner.accLocation(out left, out top, out width, out height, node.ChildId);
324 retVal = RectSlack > Math.Abs(left - wanted.Left)
325 && RectSlack > Math.Abs(top - wanted.Top)
326 && RectSlack > Math.Abs(width - wanted.Width)
327 && RectSlack > Math.Abs(height - wanted.Height);
342 private static int StateOf(Node node)
348 object state = node.Owner.get_accState(node.ChildId);
350 if (state is
int bits)
364 private static string NameOf(Node node)
366 string retVal =
"unnamed";
370 string name = node.Owner.get_accName(node.ChildId);
372 if (
false ==
string.IsNullOrWhiteSpace(name))
386 private static bool IsSelf(
object childId)
388 return childId is
int id && CHILDID_SELF == id;
401 private static IntPtr WindowOf(AutomationElement element)
403 IntPtr retVal = IntPtr.Zero;
404 AutomationElement current = element;
406 for (
int level = 0; level <= DepthLimit && IntPtr.Zero == retVal &&
null != current; level++)
408 if (0 != current.Current.NativeWindowHandle)
409 retVal =
new IntPtr(current.Current.NativeWindowHandle);
411 current = TreeWalker.ControlViewWalker.GetParent(current);