wine/dlls/uiautomationcore/uia_provider.c
Connor McAdams 68d0c88b06 uiautomationcore: Allow for refusal of HWND providers on node creation by non-nested node providers.
Signed-off-by: Connor McAdams <cmcadams@codeweavers.com>
2023-07-10 12:18:32 +02:00

2025 lines
59 KiB
C

/*
* Copyright 2022 Connor McAdams for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "uia_private.h"
#include "ocidl.h"
#include "wine/debug.h"
#include "initguid.h"
#include "wine/iaccessible2.h"
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
DEFINE_GUID(SID_AccFromDAWrapper, 0x33f139ee, 0xe509, 0x47f7, 0xbf,0x39, 0x83,0x76,0x44,0xf7,0x45,0x76);
static BOOL msaa_check_acc_state(IAccessible *acc, VARIANT cid, ULONG flag)
{
HRESULT hr;
VARIANT v;
VariantInit(&v);
hr = IAccessible_get_accState(acc, cid, &v);
if (SUCCEEDED(hr) && V_VT(&v) == VT_I4 && (V_I4(&v) & flag))
return TRUE;
return FALSE;
}
static IAccessible2 *msaa_acc_get_ia2(IAccessible *acc)
{
IServiceProvider *serv_prov;
IAccessible2 *ia2 = NULL;
HRESULT hr;
hr = IAccessible_QueryInterface(acc, &IID_IServiceProvider, (void **)&serv_prov);
if (SUCCEEDED(hr))
{
hr = IServiceProvider_QueryService(serv_prov, &IID_IAccessible2, &IID_IAccessible2, (void **)&ia2);
IServiceProvider_Release(serv_prov);
if (SUCCEEDED(hr) && ia2)
return ia2;
}
hr = IAccessible_QueryInterface(acc, &IID_IAccessible2, (void **)&ia2);
if (SUCCEEDED(hr) && ia2)
return ia2;
return NULL;
}
static IAccessible *msaa_acc_da_unwrap(IAccessible *acc)
{
IServiceProvider *sp;
IAccessible *acc2;
HRESULT hr;
hr = IAccessible_QueryInterface(acc, &IID_IServiceProvider, (void**)&sp);
if (SUCCEEDED(hr))
{
hr = IServiceProvider_QueryService(sp, &SID_AccFromDAWrapper, &IID_IAccessible, (void**)&acc2);
IServiceProvider_Release(sp);
}
if (SUCCEEDED(hr) && acc2)
return acc2;
IAccessible_AddRef(acc);
return acc;
}
/*
* Compare role, state, child count, and location properties of the two
* IAccessibles. If all four are successfully retrieved and are equal, this is
* considered a match.
*/
static HRESULT msaa_acc_prop_match(IAccessible *acc, IAccessible *acc2)
{
BOOL role_match, state_match, child_count_match, location_match;
LONG child_count[2], left[2], top[2], width[2], height[2];
VARIANT cid, v, v2;
HRESULT hr, hr2;
role_match = state_match = child_count_match = location_match = FALSE;
variant_init_i4(&cid, CHILDID_SELF);
hr = IAccessible_get_accRole(acc, cid, &v);
if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
{
VariantInit(&v2);
hr = IAccessible_get_accRole(acc2, cid, &v2);
if (SUCCEEDED(hr) && (V_VT(&v2) == VT_I4))
{
if (V_I4(&v) != V_I4(&v2))
return E_FAIL;
role_match = TRUE;
}
}
VariantInit(&v);
hr = IAccessible_get_accState(acc, cid, &v);
if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
{
VariantInit(&v2);
hr = IAccessible_get_accState(acc2, cid, &v2);
if (SUCCEEDED(hr) && (V_VT(&v2) == VT_I4))
{
if (V_I4(&v) != V_I4(&v2))
return E_FAIL;
state_match = TRUE;
}
}
hr = IAccessible_get_accChildCount(acc, &child_count[0]);
hr2 = IAccessible_get_accChildCount(acc2, &child_count[1]);
if (SUCCEEDED(hr) && SUCCEEDED(hr2))
{
if (child_count[0] != child_count[1])
return E_FAIL;
child_count_match = TRUE;
}
hr = IAccessible_accLocation(acc, &left[0], &top[0], &width[0], &height[0], cid);
if (SUCCEEDED(hr))
{
hr = IAccessible_accLocation(acc2, &left[1], &top[1], &width[1], &height[1], cid);
if (SUCCEEDED(hr))
{
if ((left[0] != left[1]) || (top[0] != top[1]) || (width[0] != width[1]) ||
(height[0] != height[1]))
return E_FAIL;
location_match = TRUE;
}
}
if (role_match && state_match && child_count_match && location_match)
return S_OK;
return S_FALSE;
}
static BOOL msaa_acc_compare(IAccessible *acc, IAccessible *acc2)
{
IAccessible2 *ia2[2] = { NULL, NULL };
IUnknown *unk, *unk2;
BOOL matched = FALSE;
LONG unique_id[2];
BSTR name[2];
VARIANT cid;
HRESULT hr;
acc = msaa_acc_da_unwrap(acc);
acc2 = msaa_acc_da_unwrap(acc2);
IAccessible_QueryInterface(acc, &IID_IUnknown, (void**)&unk);
IAccessible_QueryInterface(acc2, &IID_IUnknown, (void**)&unk2);
if (unk == unk2)
{
matched = TRUE;
goto exit;
}
ia2[0] = msaa_acc_get_ia2(acc);
ia2[1] = msaa_acc_get_ia2(acc2);
if (!ia2[0] != !ia2[1])
goto exit;
if (ia2[0])
{
hr = IAccessible2_get_uniqueID(ia2[0], &unique_id[0]);
if (SUCCEEDED(hr))
{
hr = IAccessible2_get_uniqueID(ia2[1], &unique_id[1]);
if (SUCCEEDED(hr))
{
if (unique_id[0] == unique_id[1])
matched = TRUE;
goto exit;
}
}
}
hr = msaa_acc_prop_match(acc, acc2);
if (FAILED(hr))
goto exit;
if (hr == S_OK)
matched = TRUE;
variant_init_i4(&cid, CHILDID_SELF);
hr = IAccessible_get_accName(acc, cid, &name[0]);
if (SUCCEEDED(hr))
{
hr = IAccessible_get_accName(acc2, cid, &name[1]);
if (SUCCEEDED(hr))
{
if (!name[0] && !name[1])
matched = TRUE;
else if (!name[0] || !name[1])
matched = FALSE;
else
{
if (!wcscmp(name[0], name[1]))
matched = TRUE;
else
matched = FALSE;
}
SysFreeString(name[1]);
}
SysFreeString(name[0]);
}
exit:
IUnknown_Release(unk);
IUnknown_Release(unk2);
IAccessible_Release(acc);
IAccessible_Release(acc2);
if (ia2[0])
IAccessible2_Release(ia2[0]);
if (ia2[1])
IAccessible2_Release(ia2[1]);
return matched;
}
static HRESULT msaa_acc_get_parent(IAccessible *acc, IAccessible **parent)
{
IDispatch *disp = NULL;
HRESULT hr;
*parent = NULL;
hr = IAccessible_get_accParent(acc, &disp);
if (FAILED(hr) || !disp)
return hr;
hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void**)parent);
IDispatch_Release(disp);
return hr;
}
#define DIR_FORWARD 0
#define DIR_REVERSE 1
static HRESULT msaa_acc_get_next_child(IAccessible *acc, LONG start_pos, LONG direction,
IAccessible **child, LONG *child_id, LONG *child_pos, BOOL check_visible)
{
LONG child_count, cur_pos;
IDispatch *disp;
VARIANT cid;
HRESULT hr;
*child = NULL;
*child_id = 0;
cur_pos = start_pos;
while (1)
{
hr = IAccessible_get_accChildCount(acc, &child_count);
if (FAILED(hr) || (cur_pos > child_count))
break;
variant_init_i4(&cid, cur_pos);
hr = IAccessible_get_accChild(acc, cid, &disp);
if (FAILED(hr))
break;
if (hr == S_FALSE)
{
if (!check_visible || !msaa_check_acc_state(acc, cid, STATE_SYSTEM_INVISIBLE))
{
*child = acc;
*child_id = *child_pos = cur_pos;
IAccessible_AddRef(acc);
return S_OK;
}
}
else
{
IAccessible *acc_child = NULL;
hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void **)&acc_child);
IDispatch_Release(disp);
if (FAILED(hr))
break;
variant_init_i4(&cid, CHILDID_SELF);
if (!check_visible || !msaa_check_acc_state(acc_child, cid, STATE_SYSTEM_INVISIBLE))
{
*child = acc_child;
*child_id = CHILDID_SELF;
*child_pos = cur_pos;
return S_OK;
}
IAccessible_Release(acc_child);
}
if (direction == DIR_FORWARD)
cur_pos++;
else
cur_pos--;
if ((cur_pos > child_count) || (cur_pos <= 0))
break;
}
return hr;
}
static HRESULT msaa_acc_get_child_pos(IAccessible *acc, IAccessible **out_parent,
LONG *out_child_pos)
{
LONG child_count, child_id, child_pos, match_pos;
IAccessible *child, *parent, *match, **children;
HRESULT hr;
int i;
*out_parent = NULL;
*out_child_pos = 0;
hr = msaa_acc_get_parent(acc, &parent);
if (FAILED(hr) || !parent)
return hr;
hr = IAccessible_get_accChildCount(parent, &child_count);
if (FAILED(hr) || !child_count)
{
IAccessible_Release(parent);
return hr;
}
children = heap_alloc_zero(sizeof(*children) * child_count);
if (!children)
return E_OUTOFMEMORY;
match = NULL;
for (i = 0; i < child_count; i++)
{
hr = msaa_acc_get_next_child(parent, i + 1, DIR_FORWARD, &child, &child_id, &child_pos, FALSE);
if (FAILED(hr) || !child)
goto exit;
if (child != parent)
children[i] = child;
else
IAccessible_Release(child);
}
for (i = 0; i < child_count; i++)
{
if (!children[i])
continue;
if (msaa_acc_compare(acc, children[i]))
{
if (!match)
{
match = children[i];
match_pos = i + 1;
}
/* Can't have more than one IAccessible match. */
else
{
match = NULL;
match_pos = 0;
break;
}
}
}
exit:
if (match)
{
*out_parent = parent;
*out_child_pos = match_pos;
}
else
IAccessible_Release(parent);
for (i = 0; i < child_count; i++)
{
if (children[i])
IAccessible_Release(children[i]);
}
heap_free(children);
return hr;
}
static LONG msaa_role_to_uia_control_type(LONG role)
{
switch (role)
{
case ROLE_SYSTEM_TITLEBAR: return UIA_TitleBarControlTypeId;
case ROLE_SYSTEM_MENUBAR: return UIA_MenuBarControlTypeId;
case ROLE_SYSTEM_SCROLLBAR: return UIA_ScrollBarControlTypeId;
case ROLE_SYSTEM_INDICATOR:
case ROLE_SYSTEM_GRIP: return UIA_ThumbControlTypeId;
case ROLE_SYSTEM_APPLICATION:
case ROLE_SYSTEM_WINDOW: return UIA_WindowControlTypeId;
case ROLE_SYSTEM_MENUPOPUP: return UIA_MenuControlTypeId;
case ROLE_SYSTEM_TOOLTIP: return UIA_ToolTipControlTypeId;
case ROLE_SYSTEM_DOCUMENT: return UIA_DocumentControlTypeId;
case ROLE_SYSTEM_PANE: return UIA_PaneControlTypeId;
case ROLE_SYSTEM_GROUPING: return UIA_GroupControlTypeId;
case ROLE_SYSTEM_SEPARATOR: return UIA_SeparatorControlTypeId;
case ROLE_SYSTEM_TOOLBAR: return UIA_ToolBarControlTypeId;
case ROLE_SYSTEM_STATUSBAR: return UIA_StatusBarControlTypeId;
case ROLE_SYSTEM_TABLE: return UIA_TableControlTypeId;
case ROLE_SYSTEM_COLUMNHEADER:
case ROLE_SYSTEM_ROWHEADER: return UIA_HeaderControlTypeId;
case ROLE_SYSTEM_CELL: return UIA_DataItemControlTypeId;
case ROLE_SYSTEM_LINK: return UIA_HyperlinkControlTypeId;
case ROLE_SYSTEM_LIST: return UIA_ListControlTypeId;
case ROLE_SYSTEM_LISTITEM: return UIA_ListItemControlTypeId;
case ROLE_SYSTEM_OUTLINE: return UIA_TreeControlTypeId;
case ROLE_SYSTEM_OUTLINEITEM: return UIA_TreeItemControlTypeId;
case ROLE_SYSTEM_PAGETAB: return UIA_TabItemControlTypeId;
case ROLE_SYSTEM_GRAPHIC: return UIA_ImageControlTypeId;
case ROLE_SYSTEM_STATICTEXT: return UIA_TextControlTypeId;
case ROLE_SYSTEM_TEXT: return UIA_EditControlTypeId;
case ROLE_SYSTEM_CLOCK:
case ROLE_SYSTEM_BUTTONDROPDOWNGRID:
case ROLE_SYSTEM_PUSHBUTTON: return UIA_ButtonControlTypeId;
case ROLE_SYSTEM_CHECKBUTTON: return UIA_CheckBoxControlTypeId;
case ROLE_SYSTEM_RADIOBUTTON: return UIA_RadioButtonControlTypeId;
case ROLE_SYSTEM_COMBOBOX: return UIA_ComboBoxControlTypeId;
case ROLE_SYSTEM_PROGRESSBAR: return UIA_ProgressBarControlTypeId;
case ROLE_SYSTEM_SLIDER: return UIA_SliderControlTypeId;
case ROLE_SYSTEM_SPINBUTTON: return UIA_SpinnerControlTypeId;
case ROLE_SYSTEM_BUTTONMENU:
case ROLE_SYSTEM_MENUITEM: return UIA_MenuItemControlTypeId;
case ROLE_SYSTEM_PAGETABLIST: return UIA_TabControlTypeId;
case ROLE_SYSTEM_BUTTONDROPDOWN:
case ROLE_SYSTEM_SPLITBUTTON: return UIA_SplitButtonControlTypeId;
case ROLE_SYSTEM_SOUND:
case ROLE_SYSTEM_CURSOR:
case ROLE_SYSTEM_CARET:
case ROLE_SYSTEM_ALERT:
case ROLE_SYSTEM_CLIENT:
case ROLE_SYSTEM_CHART:
case ROLE_SYSTEM_DIALOG:
case ROLE_SYSTEM_BORDER:
case ROLE_SYSTEM_COLUMN:
case ROLE_SYSTEM_ROW:
case ROLE_SYSTEM_HELPBALLOON:
case ROLE_SYSTEM_CHARACTER:
case ROLE_SYSTEM_PROPERTYPAGE:
case ROLE_SYSTEM_DROPLIST:
case ROLE_SYSTEM_DIAL:
case ROLE_SYSTEM_HOTKEYFIELD:
case ROLE_SYSTEM_DIAGRAM:
case ROLE_SYSTEM_ANIMATION:
case ROLE_SYSTEM_EQUATION:
case ROLE_SYSTEM_WHITESPACE:
case ROLE_SYSTEM_IPADDRESS:
case ROLE_SYSTEM_OUTLINEBUTTON:
WARN("No UIA control type mapping for MSAA role %ld\n", role);
break;
default:
FIXME("UIA control type mapping unimplemented for MSAA role %ld\n", role);
break;
}
return 0;
}
/*
* UiaProviderFromIAccessible IRawElementProviderSimple interface.
*/
struct msaa_provider {
IRawElementProviderSimple IRawElementProviderSimple_iface;
IRawElementProviderFragment IRawElementProviderFragment_iface;
ILegacyIAccessibleProvider ILegacyIAccessibleProvider_iface;
LONG refcount;
IAccessible *acc;
IAccessible2 *ia2;
VARIANT cid;
HWND hwnd;
LONG control_type;
BOOL root_acc_check_ran;
BOOL is_root_acc;
IAccessible *parent;
INT child_pos;
};
static BOOL msaa_check_root_acc(struct msaa_provider *msaa_prov)
{
IAccessible *acc;
HRESULT hr;
if (msaa_prov->root_acc_check_ran)
return msaa_prov->is_root_acc;
msaa_prov->root_acc_check_ran = TRUE;
if (V_I4(&msaa_prov->cid) != CHILDID_SELF || msaa_prov->parent)
return FALSE;
hr = AccessibleObjectFromWindow(msaa_prov->hwnd, OBJID_CLIENT, &IID_IAccessible, (void **)&acc);
if (FAILED(hr))
return FALSE;
if (msaa_acc_compare(msaa_prov->acc, acc))
msaa_prov->is_root_acc = TRUE;
IAccessible_Release(acc);
return msaa_prov->is_root_acc;
}
static inline struct msaa_provider *impl_from_msaa_provider(IRawElementProviderSimple *iface)
{
return CONTAINING_RECORD(iface, struct msaa_provider, IRawElementProviderSimple_iface);
}
HRESULT WINAPI msaa_provider_QueryInterface(IRawElementProviderSimple *iface, REFIID riid, void **ppv)
{
struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
*ppv = NULL;
if (IsEqualIID(riid, &IID_IRawElementProviderSimple) || IsEqualIID(riid, &IID_IUnknown))
*ppv = iface;
else if (IsEqualIID(riid, &IID_IRawElementProviderFragment))
*ppv = &msaa_prov->IRawElementProviderFragment_iface;
else if (IsEqualIID(riid, &IID_ILegacyIAccessibleProvider))
*ppv = &msaa_prov->ILegacyIAccessibleProvider_iface;
else
return E_NOINTERFACE;
IRawElementProviderSimple_AddRef(iface);
return S_OK;
}
ULONG WINAPI msaa_provider_AddRef(IRawElementProviderSimple *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
ULONG refcount = InterlockedIncrement(&msaa_prov->refcount);
TRACE("%p, refcount %ld\n", iface, refcount);
return refcount;
}
ULONG WINAPI msaa_provider_Release(IRawElementProviderSimple *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
ULONG refcount = InterlockedDecrement(&msaa_prov->refcount);
TRACE("%p, refcount %ld\n", iface, refcount);
if (!refcount)
{
IAccessible_Release(msaa_prov->acc);
if (msaa_prov->parent)
IAccessible_Release(msaa_prov->parent);
if (msaa_prov->ia2)
IAccessible2_Release(msaa_prov->ia2);
heap_free(msaa_prov);
}
return refcount;
}
HRESULT WINAPI msaa_provider_get_ProviderOptions(IRawElementProviderSimple *iface,
enum ProviderOptions *ret_val)
{
TRACE("%p, %p\n", iface, ret_val);
*ret_val = ProviderOptions_ServerSideProvider | ProviderOptions_UseComThreading;
return S_OK;
}
HRESULT WINAPI msaa_provider_GetPatternProvider(IRawElementProviderSimple *iface,
PATTERNID pattern_id, IUnknown **ret_val)
{
TRACE("%p, %d, %p\n", iface, pattern_id, ret_val);
*ret_val = NULL;
switch (pattern_id)
{
case UIA_LegacyIAccessiblePatternId:
return IRawElementProviderSimple_QueryInterface(iface, &IID_IUnknown, (void **)ret_val);
default:
FIXME("Unimplemented patternId %d\n", pattern_id);
break;
}
return S_OK;
}
HRESULT WINAPI msaa_provider_GetPropertyValue(IRawElementProviderSimple *iface,
PROPERTYID prop_id, VARIANT *ret_val)
{
struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
HRESULT hr;
VARIANT v;
TRACE("%p, %d, %p\n", iface, prop_id, ret_val);
VariantInit(ret_val);
VariantInit(&v);
switch (prop_id)
{
case UIA_ProviderDescriptionPropertyId:
V_VT(ret_val) = VT_BSTR;
V_BSTR(ret_val) = SysAllocString(L"Wine: MSAA Proxy");
break;
case UIA_ControlTypePropertyId:
if (!msaa_prov->control_type)
{
hr = IAccessible_get_accRole(msaa_prov->acc, msaa_prov->cid, &v);
if (SUCCEEDED(hr) && (V_VT(&v) == VT_I4))
msaa_prov->control_type = msaa_role_to_uia_control_type(V_I4(&v));
}
if (msaa_prov->control_type)
variant_init_i4(ret_val, msaa_prov->control_type);
break;
case UIA_HasKeyboardFocusPropertyId:
variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
STATE_SYSTEM_FOCUSED));
break;
case UIA_IsKeyboardFocusablePropertyId:
variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
STATE_SYSTEM_FOCUSABLE));
break;
case UIA_IsEnabledPropertyId:
variant_init_bool(ret_val, !msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
STATE_SYSTEM_UNAVAILABLE));
break;
case UIA_IsPasswordPropertyId:
variant_init_bool(ret_val, msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid,
STATE_SYSTEM_PROTECTED));
break;
case UIA_NamePropertyId:
{
BSTR name;
hr = IAccessible_get_accName(msaa_prov->acc, msaa_prov->cid, &name);
if (SUCCEEDED(hr) && name)
{
V_VT(ret_val) = VT_BSTR;
V_BSTR(ret_val) = name;
}
break;
}
case UIA_IsOffscreenPropertyId:
{
RECT rect[2] = { 0 };
RECT intersect_rect;
LONG width, height;
variant_init_bool(ret_val, FALSE);
if (msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid, STATE_SYSTEM_OFFSCREEN))
{
variant_init_bool(ret_val, TRUE);
break;
}
hr = IAccessible_accLocation(msaa_prov->acc, &rect[0].left, &rect[0].top, &width, &height, msaa_prov->cid);
if (FAILED(hr))
break;
rect[0].right = rect[0].left + width;
rect[0].bottom = rect[0].top + height;
SetLastError(NOERROR);
if (!GetClientRect(msaa_prov->hwnd, &rect[1]))
{
if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
variant_init_bool(ret_val, TRUE);
break;
}
SetLastError(NOERROR);
if (!MapWindowPoints(msaa_prov->hwnd, NULL, (POINT *)&rect[1], 2) && GetLastError())
{
if (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
variant_init_bool(ret_val, TRUE);
break;
}
variant_init_bool(ret_val, !IntersectRect(&intersect_rect, &rect[0], &rect[1]));
break;
}
default:
FIXME("Unimplemented propertyId %d\n", prop_id);
break;
}
return S_OK;
}
HRESULT WINAPI msaa_provider_get_HostRawElementProvider(IRawElementProviderSimple *iface,
IRawElementProviderSimple **ret_val)
{
struct msaa_provider *msaa_prov = impl_from_msaa_provider(iface);
TRACE("%p, %p\n", iface, ret_val);
*ret_val = NULL;
if (msaa_check_root_acc(msaa_prov))
return UiaHostProviderFromHwnd(msaa_prov->hwnd, ret_val);
return S_OK;
}
static const IRawElementProviderSimpleVtbl msaa_provider_vtbl = {
msaa_provider_QueryInterface,
msaa_provider_AddRef,
msaa_provider_Release,
msaa_provider_get_ProviderOptions,
msaa_provider_GetPatternProvider,
msaa_provider_GetPropertyValue,
msaa_provider_get_HostRawElementProvider,
};
/*
* IRawElementProviderFragment interface for UiaProviderFromIAccessible
* providers.
*/
static inline struct msaa_provider *impl_from_msaa_fragment(IRawElementProviderFragment *iface)
{
return CONTAINING_RECORD(iface, struct msaa_provider, IRawElementProviderFragment_iface);
}
static HRESULT WINAPI msaa_fragment_QueryInterface(IRawElementProviderFragment *iface, REFIID riid,
void **ppv)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
}
static ULONG WINAPI msaa_fragment_AddRef(IRawElementProviderFragment *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
}
static ULONG WINAPI msaa_fragment_Release(IRawElementProviderFragment *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
}
static HRESULT WINAPI msaa_fragment_Navigate(IRawElementProviderFragment *iface,
enum NavigateDirection direction, IRawElementProviderFragment **ret_val)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
LONG child_count, child_id, child_pos;
IRawElementProviderSimple *elprov;
IAccessible *acc;
HRESULT hr;
TRACE("%p, %d, %p\n", iface, direction, ret_val);
*ret_val = NULL;
switch (direction)
{
case NavigateDirection_Parent:
if (msaa_check_root_acc(msaa_prov))
break;
if (V_I4(&msaa_prov->cid) == CHILDID_SELF)
{
hr = msaa_acc_get_parent(msaa_prov->acc, &acc);
if (FAILED(hr) || !acc)
break;
}
else
acc = msaa_prov->acc;
hr = create_msaa_provider(acc, CHILDID_SELF, NULL, FALSE, &elprov);
if (SUCCEEDED(hr))
{
struct msaa_provider *prov = impl_from_msaa_provider(elprov);
*ret_val = &prov->IRawElementProviderFragment_iface;
}
if (acc != msaa_prov->acc)
IAccessible_Release(acc);
break;
case NavigateDirection_FirstChild:
case NavigateDirection_LastChild:
if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
break;
hr = IAccessible_get_accChildCount(msaa_prov->acc, &child_count);
if (FAILED(hr) || !child_count)
break;
if (direction == NavigateDirection_FirstChild)
hr = msaa_acc_get_next_child(msaa_prov->acc, 1, DIR_FORWARD, &acc, &child_id,
&child_pos, TRUE);
else
hr = msaa_acc_get_next_child(msaa_prov->acc, child_count, DIR_REVERSE, &acc, &child_id,
&child_pos, TRUE);
if (FAILED(hr) || !acc)
break;
hr = create_msaa_provider(acc, child_id, NULL, FALSE, &elprov);
if (SUCCEEDED(hr))
{
struct msaa_provider *prov = impl_from_msaa_provider(elprov);
*ret_val = &prov->IRawElementProviderFragment_iface;
prov->parent = msaa_prov->acc;
IAccessible_AddRef(msaa_prov->acc);
if (acc != msaa_prov->acc)
prov->child_pos = child_pos;
else
prov->child_pos = child_id;
}
IAccessible_Release(acc);
break;
case NavigateDirection_NextSibling:
case NavigateDirection_PreviousSibling:
if (msaa_check_root_acc(msaa_prov))
break;
if (!msaa_prov->parent)
{
if (V_I4(&msaa_prov->cid) != CHILDID_SELF)
{
msaa_prov->parent = msaa_prov->acc;
IAccessible_AddRef(msaa_prov->acc);
msaa_prov->child_pos = V_I4(&msaa_prov->cid);
}
else
{
hr = msaa_acc_get_child_pos(msaa_prov->acc, &acc, &child_pos);
if (FAILED(hr) || !acc)
break;
msaa_prov->parent = acc;
msaa_prov->child_pos = child_pos;
}
}
if (direction == NavigateDirection_NextSibling)
hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos + 1, DIR_FORWARD,
&acc, &child_id, &child_pos, TRUE);
else
hr = msaa_acc_get_next_child(msaa_prov->parent, msaa_prov->child_pos - 1, DIR_REVERSE,
&acc, &child_id, &child_pos, TRUE);
if (FAILED(hr) || !acc)
break;
hr = create_msaa_provider(acc, child_id, NULL, FALSE, &elprov);
if (SUCCEEDED(hr))
{
struct msaa_provider *prov = impl_from_msaa_provider(elprov);
*ret_val = &prov->IRawElementProviderFragment_iface;
prov->parent = msaa_prov->parent;
IAccessible_AddRef(msaa_prov->parent);
if (acc != msaa_prov->acc)
prov->child_pos = child_pos;
else
prov->child_pos = child_id;
}
IAccessible_Release(acc);
break;
default:
FIXME("Invalid NavigateDirection %d\n", direction);
return E_INVALIDARG;
}
return S_OK;
}
static HRESULT WINAPI msaa_fragment_GetRuntimeId(IRawElementProviderFragment *iface,
SAFEARRAY **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_fragment_get_BoundingRectangle(IRawElementProviderFragment *iface,
struct UiaRect *ret_val)
{
struct msaa_provider *msaa_prov = impl_from_msaa_fragment(iface);
LONG left, top, width, height;
HRESULT hr;
TRACE("%p, %p\n", iface, ret_val);
memset(ret_val, 0, sizeof(*ret_val));
/*
* If this IAccessible is at the root of its HWND, the BaseHwnd provider
* will supply the bounding rectangle.
*/
if (msaa_check_root_acc(msaa_prov))
return S_OK;
if (msaa_check_acc_state(msaa_prov->acc, msaa_prov->cid, STATE_SYSTEM_OFFSCREEN))
return S_OK;
hr = IAccessible_accLocation(msaa_prov->acc, &left, &top, &width, &height, msaa_prov->cid);
if (FAILED(hr))
return hr;
ret_val->left = left;
ret_val->top = top;
ret_val->width = width;
ret_val->height = height;
return S_OK;
}
static HRESULT WINAPI msaa_fragment_GetEmbeddedFragmentRoots(IRawElementProviderFragment *iface,
SAFEARRAY **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_fragment_SetFocus(IRawElementProviderFragment *iface)
{
FIXME("%p: stub!\n", iface);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_fragment_get_FragmentRoot(IRawElementProviderFragment *iface,
IRawElementProviderFragmentRoot **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static const IRawElementProviderFragmentVtbl msaa_fragment_vtbl = {
msaa_fragment_QueryInterface,
msaa_fragment_AddRef,
msaa_fragment_Release,
msaa_fragment_Navigate,
msaa_fragment_GetRuntimeId,
msaa_fragment_get_BoundingRectangle,
msaa_fragment_GetEmbeddedFragmentRoots,
msaa_fragment_SetFocus,
msaa_fragment_get_FragmentRoot,
};
/*
* ILegacyIAccessibleProvider interface for UiaProviderFromIAccessible
* providers.
*/
static inline struct msaa_provider *impl_from_msaa_acc_provider(ILegacyIAccessibleProvider *iface)
{
return CONTAINING_RECORD(iface, struct msaa_provider, ILegacyIAccessibleProvider_iface);
}
static HRESULT WINAPI msaa_acc_provider_QueryInterface(ILegacyIAccessibleProvider *iface, REFIID riid, void **ppv)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
return IRawElementProviderSimple_QueryInterface(&msaa_prov->IRawElementProviderSimple_iface, riid, ppv);
}
static ULONG WINAPI msaa_acc_provider_AddRef(ILegacyIAccessibleProvider *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
return IRawElementProviderSimple_AddRef(&msaa_prov->IRawElementProviderSimple_iface);
}
static ULONG WINAPI msaa_acc_provider_Release(ILegacyIAccessibleProvider *iface)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
return IRawElementProviderSimple_Release(&msaa_prov->IRawElementProviderSimple_iface);
}
static HRESULT WINAPI msaa_acc_provider_Select(ILegacyIAccessibleProvider *iface, LONG select_flags)
{
FIXME("%p, %#lx: stub!\n", iface, select_flags);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_DoDefaultAction(ILegacyIAccessibleProvider *iface)
{
FIXME("%p: stub!\n", iface);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_SetValue(ILegacyIAccessibleProvider *iface, LPCWSTR val)
{
FIXME("%p, %p<%s>: stub!\n", iface, val, debugstr_w(val));
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_GetIAccessible(ILegacyIAccessibleProvider *iface,
IAccessible **out_acc)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
TRACE("%p, %p\n", iface, out_acc);
IAccessible_AddRef(msaa_prov->acc);
*out_acc = msaa_prov->acc;
return S_OK;
}
static HRESULT WINAPI msaa_acc_provider_get_ChildId(ILegacyIAccessibleProvider *iface, int *out_cid)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
TRACE("%p, %p\n", iface, out_cid);
*out_cid = V_I4(&msaa_prov->cid);
return S_OK;
}
static HRESULT WINAPI msaa_acc_provider_get_Name(ILegacyIAccessibleProvider *iface, BSTR *out_name)
{
FIXME("%p, %p: stub!\n", iface, out_name);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_Value(ILegacyIAccessibleProvider *iface, BSTR *out_value)
{
FIXME("%p, %p: stub!\n", iface, out_value);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_Description(ILegacyIAccessibleProvider *iface,
BSTR *out_description)
{
FIXME("%p, %p: stub!\n", iface, out_description);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_Role(ILegacyIAccessibleProvider *iface, DWORD *out_role)
{
struct msaa_provider *msaa_prov = impl_from_msaa_acc_provider(iface);
HRESULT hr;
VARIANT v;
TRACE("%p, %p\n", iface, out_role);
*out_role = 0;
VariantInit(&v);
hr = IAccessible_get_accRole(msaa_prov->acc, msaa_prov->cid, &v);
if (SUCCEEDED(hr) && V_VT(&v) == VT_I4)
*out_role = V_I4(&v);
return S_OK;
}
static HRESULT WINAPI msaa_acc_provider_get_State(ILegacyIAccessibleProvider *iface, DWORD *out_state)
{
FIXME("%p, %p: stub!\n", iface, out_state);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_Help(ILegacyIAccessibleProvider *iface, BSTR *out_help)
{
FIXME("%p, %p: stub!\n", iface, out_help);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_KeyboardShortcut(ILegacyIAccessibleProvider *iface,
BSTR *out_kbd_shortcut)
{
FIXME("%p, %p: stub!\n", iface, out_kbd_shortcut);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_GetSelection(ILegacyIAccessibleProvider *iface,
SAFEARRAY **out_selected)
{
FIXME("%p, %p: stub!\n", iface, out_selected);
return E_NOTIMPL;
}
static HRESULT WINAPI msaa_acc_provider_get_DefaultAction(ILegacyIAccessibleProvider *iface,
BSTR *out_default_action)
{
FIXME("%p, %p: stub!\n", iface, out_default_action);
return E_NOTIMPL;
}
static const ILegacyIAccessibleProviderVtbl msaa_acc_provider_vtbl = {
msaa_acc_provider_QueryInterface,
msaa_acc_provider_AddRef,
msaa_acc_provider_Release,
msaa_acc_provider_Select,
msaa_acc_provider_DoDefaultAction,
msaa_acc_provider_SetValue,
msaa_acc_provider_GetIAccessible,
msaa_acc_provider_get_ChildId,
msaa_acc_provider_get_Name,
msaa_acc_provider_get_Value,
msaa_acc_provider_get_Description,
msaa_acc_provider_get_Role,
msaa_acc_provider_get_State,
msaa_acc_provider_get_Help,
msaa_acc_provider_get_KeyboardShortcut,
msaa_acc_provider_GetSelection,
msaa_acc_provider_get_DefaultAction,
};
HRESULT create_msaa_provider(IAccessible *acc, LONG child_id, HWND hwnd, BOOL known_root_acc,
IRawElementProviderSimple **elprov)
{
struct msaa_provider *msaa_prov = heap_alloc_zero(sizeof(*msaa_prov));
if (!msaa_prov)
return E_OUTOFMEMORY;
msaa_prov->IRawElementProviderSimple_iface.lpVtbl = &msaa_provider_vtbl;
msaa_prov->IRawElementProviderFragment_iface.lpVtbl = &msaa_fragment_vtbl;
msaa_prov->ILegacyIAccessibleProvider_iface.lpVtbl = &msaa_acc_provider_vtbl;
msaa_prov->refcount = 1;
variant_init_i4(&msaa_prov->cid, child_id);
msaa_prov->acc = acc;
IAccessible_AddRef(acc);
msaa_prov->ia2 = msaa_acc_get_ia2(acc);
if (!hwnd)
{
HRESULT hr;
hr = WindowFromAccessibleObject(acc, &msaa_prov->hwnd);
if (FAILED(hr))
WARN("WindowFromAccessibleObject failed with hr %#lx\n", hr);
}
else
msaa_prov->hwnd = hwnd;
if (known_root_acc)
msaa_prov->root_acc_check_ran = msaa_prov->is_root_acc = TRUE;
*elprov = &msaa_prov->IRawElementProviderSimple_iface;
return S_OK;
}
/***********************************************************************
* UiaProviderFromIAccessible (uiautomationcore.@)
*/
HRESULT WINAPI UiaProviderFromIAccessible(IAccessible *acc, LONG child_id, DWORD flags,
IRawElementProviderSimple **elprov)
{
IServiceProvider *serv_prov;
HWND hwnd = NULL;
HRESULT hr;
TRACE("(%p, %ld, %#lx, %p)\n", acc, child_id, flags, elprov);
if (elprov)
*elprov = NULL;
if (!elprov)
return E_POINTER;
if (!acc)
return E_INVALIDARG;
if (flags != UIA_PFIA_DEFAULT)
{
FIXME("unsupported flags %#lx\n", flags);
return E_NOTIMPL;
}
hr = IAccessible_QueryInterface(acc, &IID_IServiceProvider, (void **)&serv_prov);
if (SUCCEEDED(hr))
{
IUnknown *unk;
hr = IServiceProvider_QueryService(serv_prov, &IIS_IsOleaccProxy, &IID_IUnknown, (void **)&unk);
if (SUCCEEDED(hr))
{
WARN("Cannot wrap an oleacc proxy IAccessible!\n");
IUnknown_Release(unk);
IServiceProvider_Release(serv_prov);
return E_INVALIDARG;
}
IServiceProvider_Release(serv_prov);
}
hr = WindowFromAccessibleObject(acc, &hwnd);
if (FAILED(hr))
return hr;
if (!hwnd)
return E_FAIL;
return create_msaa_provider(acc, child_id, hwnd, FALSE, elprov);
}
static HRESULT uia_get_hr_for_last_error(void)
{
DWORD last_err = GetLastError();
switch (last_err)
{
case ERROR_INVALID_WINDOW_HANDLE:
return UIA_E_ELEMENTNOTAVAILABLE;
case ERROR_TIMEOUT:
return UIA_E_TIMEOUT;
default:
return E_FAIL;
}
}
#define UIA_DEFAULT_MSG_TIMEOUT 10000
static HRESULT uia_send_message_timeout(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, UINT timeout, LRESULT *lres)
{
*lres = 0;
if (!SendMessageTimeoutW(hwnd, msg, wparam, lparam, SMTO_NORMAL, timeout, (PDWORD_PTR)lres))
return uia_get_hr_for_last_error();
return S_OK;
}
static BOOL is_top_level_hwnd(HWND hwnd)
{
return GetAncestor(hwnd, GA_PARENT) == GetDesktopWindow();
}
static HRESULT get_uia_control_type_for_hwnd(HWND hwnd, int *control_type)
{
LONG_PTR style, ex_style;
*control_type = 0;
if ((ex_style = GetWindowLongPtrW(hwnd, GWL_EXSTYLE)) & WS_EX_APPWINDOW)
{
*control_type = UIA_WindowControlTypeId;
return S_OK;
}
SetLastError(NO_ERROR);
if (!(style = GetWindowLongPtrW(hwnd, GWL_STYLE)) && (GetLastError() != NO_ERROR))
return uia_get_hr_for_last_error();
/*
* Non-caption HWNDs that are popups or tool windows aren't considered full
* windows, only panes.
*/
if (((style & WS_CAPTION) != WS_CAPTION) && ((ex_style & WS_EX_TOOLWINDOW) || (style & WS_POPUP)))
{
*control_type = UIA_PaneControlTypeId;
return S_OK;
}
/* Non top-level HWNDs are considered panes as well. */
if (!is_top_level_hwnd(hwnd))
*control_type = UIA_PaneControlTypeId;
else
*control_type = UIA_WindowControlTypeId;
return S_OK;
}
/*
* Default ProviderType_BaseHwnd IRawElementProviderSimple interface.
*/
struct base_hwnd_provider {
IRawElementProviderSimple IRawElementProviderSimple_iface;
IRawElementProviderFragment IRawElementProviderFragment_iface;
LONG refcount;
HWND hwnd;
};
static inline struct base_hwnd_provider *impl_from_base_hwnd_provider(IRawElementProviderSimple *iface)
{
return CONTAINING_RECORD(iface, struct base_hwnd_provider, IRawElementProviderSimple_iface);
}
static HRESULT WINAPI base_hwnd_provider_QueryInterface(IRawElementProviderSimple *iface, REFIID riid, void **ppv)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
*ppv = NULL;
if (IsEqualIID(riid, &IID_IRawElementProviderSimple) || IsEqualIID(riid, &IID_IUnknown))
*ppv = iface;
else if (IsEqualIID(riid, &IID_IRawElementProviderFragment))
*ppv = &base_hwnd_prov->IRawElementProviderFragment_iface;
else
return E_NOINTERFACE;
IRawElementProviderSimple_AddRef(iface);
return S_OK;
}
static ULONG WINAPI base_hwnd_provider_AddRef(IRawElementProviderSimple *iface)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
ULONG refcount = InterlockedIncrement(&base_hwnd_prov->refcount);
TRACE("%p, refcount %ld\n", iface, refcount);
return refcount;
}
static ULONG WINAPI base_hwnd_provider_Release(IRawElementProviderSimple *iface)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
ULONG refcount = InterlockedDecrement(&base_hwnd_prov->refcount);
TRACE("%p, refcount %ld\n", iface, refcount);
if (!refcount)
heap_free(base_hwnd_prov);
return refcount;
}
static HRESULT WINAPI base_hwnd_provider_get_ProviderOptions(IRawElementProviderSimple *iface,
enum ProviderOptions *ret_val)
{
TRACE("%p, %p\n", iface, ret_val);
*ret_val = ProviderOptions_ClientSideProvider;
return S_OK;
}
static HRESULT WINAPI base_hwnd_provider_GetPatternProvider(IRawElementProviderSimple *iface,
PATTERNID pattern_id, IUnknown **ret_val)
{
FIXME("%p, %d, %p: stub\n", iface, pattern_id, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static HRESULT WINAPI base_hwnd_provider_GetPropertyValue(IRawElementProviderSimple *iface,
PROPERTYID prop_id, VARIANT *ret_val)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_provider(iface);
HRESULT hr = S_OK;
TRACE("%p, %d, %p\n", iface, prop_id, ret_val);
VariantInit(ret_val);
if (!IsWindow(base_hwnd_prov->hwnd))
return UIA_E_ELEMENTNOTAVAILABLE;
switch (prop_id)
{
case UIA_ProviderDescriptionPropertyId:
V_VT(ret_val) = VT_BSTR;
V_BSTR(ret_val) = SysAllocString(L"Wine: HWND Proxy");
break;
case UIA_NativeWindowHandlePropertyId:
V_VT(ret_val) = VT_I4;
V_I4(ret_val) = HandleToUlong(base_hwnd_prov->hwnd);
break;
case UIA_ProcessIdPropertyId:
{
DWORD pid;
if (!GetWindowThreadProcessId(base_hwnd_prov->hwnd, &pid))
return UIA_E_ELEMENTNOTAVAILABLE;
V_VT(ret_val) = VT_I4;
V_I4(ret_val) = pid;
break;
}
case UIA_ClassNamePropertyId:
{
WCHAR buf[256] = { 0 };
if (!GetClassNameW(base_hwnd_prov->hwnd, buf, ARRAY_SIZE(buf)))
hr = uia_get_hr_for_last_error();
else
{
V_VT(ret_val) = VT_BSTR;
V_BSTR(ret_val) = SysAllocString(buf);
}
break;
}
case UIA_NamePropertyId:
{
LRESULT lres;
V_VT(ret_val) = VT_BSTR;
V_BSTR(ret_val) = SysAllocString(L"");
hr = uia_send_message_timeout(base_hwnd_prov->hwnd, WM_GETTEXTLENGTH, 0, 0, UIA_DEFAULT_MSG_TIMEOUT, &lres);
if (FAILED(hr) || !lres)
break;
if (!SysReAllocStringLen(&V_BSTR(ret_val), NULL, lres))
{
hr = E_OUTOFMEMORY;
break;
}
hr = uia_send_message_timeout(base_hwnd_prov->hwnd, WM_GETTEXT, SysStringLen(V_BSTR(ret_val)) + 1,
(LPARAM)V_BSTR(ret_val), UIA_DEFAULT_MSG_TIMEOUT, &lres);
break;
}
case UIA_ControlTypePropertyId:
{
int control_type;
hr = get_uia_control_type_for_hwnd(base_hwnd_prov->hwnd, &control_type);
if (SUCCEEDED(hr))
{
V_VT(ret_val) = VT_I4;
V_I4(ret_val) = control_type;
}
break;
}
default:
break;
}
if (FAILED(hr))
VariantClear(ret_val);
return hr;
}
static HRESULT WINAPI base_hwnd_provider_get_HostRawElementProvider(IRawElementProviderSimple *iface,
IRawElementProviderSimple **ret_val)
{
TRACE("%p, %p\n", iface, ret_val);
*ret_val = NULL;
return S_OK;
}
static const IRawElementProviderSimpleVtbl base_hwnd_provider_vtbl = {
base_hwnd_provider_QueryInterface,
base_hwnd_provider_AddRef,
base_hwnd_provider_Release,
base_hwnd_provider_get_ProviderOptions,
base_hwnd_provider_GetPatternProvider,
base_hwnd_provider_GetPropertyValue,
base_hwnd_provider_get_HostRawElementProvider,
};
/*
* IRawElementProviderFragment interface for default ProviderType_BaseHwnd
* providers.
*/
static inline struct base_hwnd_provider *impl_from_base_hwnd_fragment(IRawElementProviderFragment *iface)
{
return CONTAINING_RECORD(iface, struct base_hwnd_provider, IRawElementProviderFragment_iface);
}
static HRESULT WINAPI base_hwnd_fragment_QueryInterface(IRawElementProviderFragment *iface, REFIID riid,
void **ppv)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
return IRawElementProviderSimple_QueryInterface(&base_hwnd_prov->IRawElementProviderSimple_iface, riid, ppv);
}
static ULONG WINAPI base_hwnd_fragment_AddRef(IRawElementProviderFragment *iface)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
return IRawElementProviderSimple_AddRef(&base_hwnd_prov->IRawElementProviderSimple_iface);
}
static ULONG WINAPI base_hwnd_fragment_Release(IRawElementProviderFragment *iface)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
return IRawElementProviderSimple_Release(&base_hwnd_prov->IRawElementProviderSimple_iface);
}
static HRESULT WINAPI base_hwnd_fragment_Navigate(IRawElementProviderFragment *iface,
enum NavigateDirection direction, IRawElementProviderFragment **ret_val)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
IRawElementProviderSimple *elprov = NULL;
HRESULT hr = S_OK;
TRACE("%p, %d, %p\n", iface, direction, ret_val);
*ret_val = NULL;
switch (direction)
{
case NavigateDirection_Parent:
{
HWND parent, owner;
/*
* Top level owned windows have their owner window as a parent instead
* of the desktop window.
*/
if (is_top_level_hwnd(base_hwnd_prov->hwnd) && (owner = GetWindow(base_hwnd_prov->hwnd, GW_OWNER)))
parent = owner;
else
parent = GetAncestor(base_hwnd_prov->hwnd, GA_PARENT);
if (parent)
hr = create_base_hwnd_provider(parent, &elprov);
break;
}
case NavigateDirection_FirstChild:
case NavigateDirection_LastChild:
case NavigateDirection_PreviousSibling:
case NavigateDirection_NextSibling:
FIXME("Unimplemented NavigateDirection %d\n", direction);
return E_NOTIMPL;
default:
FIXME("Invalid NavigateDirection %d\n", direction);
return E_INVALIDARG;
}
if (elprov)
{
hr = IRawElementProviderSimple_QueryInterface(elprov, &IID_IRawElementProviderFragment, (void **)ret_val);
IRawElementProviderSimple_Release(elprov);
}
return hr;
}
static HRESULT WINAPI base_hwnd_fragment_GetRuntimeId(IRawElementProviderFragment *iface,
SAFEARRAY **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static HRESULT WINAPI base_hwnd_fragment_get_BoundingRectangle(IRawElementProviderFragment *iface,
struct UiaRect *ret_val)
{
struct base_hwnd_provider *base_hwnd_prov = impl_from_base_hwnd_fragment(iface);
RECT rect = { 0 };
TRACE("%p, %p\n", iface, ret_val);
memset(ret_val, 0, sizeof(*ret_val));
/* Top level minimized window - Return empty rect. */
if (is_top_level_hwnd(base_hwnd_prov->hwnd) && IsIconic(base_hwnd_prov->hwnd))
return S_OK;
if (!GetWindowRect(base_hwnd_prov->hwnd, &rect))
return uia_get_hr_for_last_error();
ret_val->left = rect.left;
ret_val->top = rect.top;
ret_val->width = (rect.right - rect.left);
ret_val->height = (rect.bottom - rect.top);
return S_OK;
}
static HRESULT WINAPI base_hwnd_fragment_GetEmbeddedFragmentRoots(IRawElementProviderFragment *iface,
SAFEARRAY **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static HRESULT WINAPI base_hwnd_fragment_SetFocus(IRawElementProviderFragment *iface)
{
FIXME("%p: stub!\n", iface);
return E_NOTIMPL;
}
static HRESULT WINAPI base_hwnd_fragment_get_FragmentRoot(IRawElementProviderFragment *iface,
IRawElementProviderFragmentRoot **ret_val)
{
FIXME("%p, %p: stub!\n", iface, ret_val);
*ret_val = NULL;
return E_NOTIMPL;
}
static const IRawElementProviderFragmentVtbl base_hwnd_fragment_vtbl = {
base_hwnd_fragment_QueryInterface,
base_hwnd_fragment_AddRef,
base_hwnd_fragment_Release,
base_hwnd_fragment_Navigate,
base_hwnd_fragment_GetRuntimeId,
base_hwnd_fragment_get_BoundingRectangle,
base_hwnd_fragment_GetEmbeddedFragmentRoots,
base_hwnd_fragment_SetFocus,
base_hwnd_fragment_get_FragmentRoot,
};
HRESULT create_base_hwnd_provider(HWND hwnd, IRawElementProviderSimple **elprov)
{
struct base_hwnd_provider *base_hwnd_prov;
*elprov = NULL;
if (!hwnd)
return E_INVALIDARG;
if (!IsWindow(hwnd))
return UIA_E_ELEMENTNOTAVAILABLE;
if (!(base_hwnd_prov = heap_alloc_zero(sizeof(*base_hwnd_prov))))
return E_OUTOFMEMORY;
base_hwnd_prov->IRawElementProviderSimple_iface.lpVtbl = &base_hwnd_provider_vtbl;
base_hwnd_prov->IRawElementProviderFragment_iface.lpVtbl = &base_hwnd_fragment_vtbl;
base_hwnd_prov->refcount = 1;
base_hwnd_prov->hwnd = hwnd;
*elprov = &base_hwnd_prov->IRawElementProviderSimple_iface;
return S_OK;
}
/*
* UI Automation provider thread functions.
*/
struct uia_provider_thread
{
struct rb_tree node_map;
struct list nodes_list;
HANDLE hthread;
HWND hwnd;
LONG ref;
};
static struct uia_provider_thread provider_thread;
static CRITICAL_SECTION provider_thread_cs;
static CRITICAL_SECTION_DEBUG provider_thread_cs_debug =
{
0, 0, &provider_thread_cs,
{ &provider_thread_cs_debug.ProcessLocksList, &provider_thread_cs_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": provider_thread_cs") }
};
static CRITICAL_SECTION provider_thread_cs = { &provider_thread_cs_debug, -1, 0, 0, 0, 0 };
struct uia_provider_thread_map_entry
{
struct rb_entry entry;
SAFEARRAY *runtime_id;
struct list nodes_list;
};
static int uia_runtime_id_compare(const void *key, const struct rb_entry *entry)
{
struct uia_provider_thread_map_entry *prov_entry = RB_ENTRY_VALUE(entry, struct uia_provider_thread_map_entry, entry);
return uia_compare_safearrays(prov_entry->runtime_id, (SAFEARRAY *)key, UIAutomationType_IntArray);
}
void uia_provider_thread_remove_node(HUIANODE node)
{
struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
TRACE("Removing node %p\n", node);
EnterCriticalSection(&provider_thread_cs);
list_remove(&node_data->prov_thread_list_entry);
list_init(&node_data->prov_thread_list_entry);
if (!list_empty(&node_data->node_map_list_entry))
{
list_remove(&node_data->node_map_list_entry);
list_init(&node_data->node_map_list_entry);
if (list_empty(&node_data->map->nodes_list))
{
rb_remove(&provider_thread.node_map, &node_data->map->entry);
SafeArrayDestroy(node_data->map->runtime_id);
heap_free(node_data->map);
}
node_data->map = NULL;
}
LeaveCriticalSection(&provider_thread_cs);
}
static void uia_provider_thread_disconnect_node(SAFEARRAY *sa)
{
struct rb_entry *rb_entry;
EnterCriticalSection(&provider_thread_cs);
/* Provider thread hasn't been started, no nodes to disconnect. */
if (!provider_thread.ref)
goto exit;
rb_entry = rb_get(&provider_thread.node_map, sa);
if (rb_entry)
{
struct uia_provider_thread_map_entry *prov_map;
struct list *cursor, *cursor2;
struct uia_node *node_data;
prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry);
LIST_FOR_EACH_SAFE(cursor, cursor2, &prov_map->nodes_list)
{
node_data = LIST_ENTRY(cursor, struct uia_node, node_map_list_entry);
list_remove(cursor);
list_remove(&node_data->prov_thread_list_entry);
list_init(&node_data->prov_thread_list_entry);
list_init(&node_data->node_map_list_entry);
node_data->map = NULL;
IWineUiaNode_disconnect(&node_data->IWineUiaNode_iface);
}
rb_remove(&provider_thread.node_map, &prov_map->entry);
SafeArrayDestroy(prov_map->runtime_id);
heap_free(prov_map);
}
exit:
LeaveCriticalSection(&provider_thread_cs);
}
static HRESULT uia_provider_thread_add_node(HUIANODE node, SAFEARRAY *rt_id)
{
struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
int prov_type = get_node_provider_type_at_idx(node_data, 0);
struct uia_provider *prov_data;
HRESULT hr = S_OK;
prov_data = impl_from_IWineUiaProvider(node_data->prov[prov_type]);
node_data->nested_node = prov_data->return_nested_node = prov_data->refuse_hwnd_node_providers = TRUE;
TRACE("Adding node %p\n", node);
EnterCriticalSection(&provider_thread_cs);
list_add_tail(&provider_thread.nodes_list, &node_data->prov_thread_list_entry);
/* If we have a runtime ID, create an entry in the rb tree. */
if (rt_id)
{
struct uia_provider_thread_map_entry *prov_map;
struct rb_entry *rb_entry;
if ((rb_entry = rb_get(&provider_thread.node_map, rt_id)))
prov_map = RB_ENTRY_VALUE(rb_entry, struct uia_provider_thread_map_entry, entry);
else
{
prov_map = heap_alloc_zero(sizeof(*prov_map));
if (!prov_map)
{
hr = E_OUTOFMEMORY;
goto exit;
}
hr = SafeArrayCopy(rt_id, &prov_map->runtime_id);
if (FAILED(hr))
{
heap_free(prov_map);
goto exit;
}
list_init(&prov_map->nodes_list);
rb_put(&provider_thread.node_map, prov_map->runtime_id, &prov_map->entry);
}
list_add_tail(&prov_map->nodes_list, &node_data->node_map_list_entry);
node_data->map = prov_map;
}
exit:
LeaveCriticalSection(&provider_thread_cs);
return hr;
}
#define WM_GET_OBJECT_UIA_NODE (WM_USER + 1)
#define WM_UIA_PROVIDER_THREAD_STOP (WM_USER + 2)
static LRESULT CALLBACK uia_provider_thread_msg_proc(HWND hwnd, UINT msg, WPARAM wparam,
LPARAM lparam)
{
switch (msg)
{
case WM_GET_OBJECT_UIA_NODE:
{
SAFEARRAY *rt_id = (SAFEARRAY *)wparam;
HUIANODE node = (HUIANODE)lparam;
LRESULT lr;
if (FAILED(uia_provider_thread_add_node(node, rt_id)))
{
WARN("Failed to add node %p to provider thread list.\n", node);
return 0;
}
/*
* LresultFromObject returns an index into the global atom string table,
* which has a valid range of 0xc000-0xffff.
*/
lr = LresultFromObject(&IID_IWineUiaNode, 0, (IUnknown *)node);
if ((lr > 0xffff) || (lr < 0xc000))
{
WARN("Got invalid lresult %Ix\n", lr);
lr = 0;
}
return lr;
}
default:
break;
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
static DWORD WINAPI uia_provider_thread_proc(void *arg)
{
HANDLE initialized_event = arg;
HWND hwnd;
MSG msg;
CoInitializeEx(NULL, COINIT_MULTITHREADED);
hwnd = CreateWindowW(L"Message", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL);
if (!hwnd)
{
WARN("CreateWindow failed: %ld\n", GetLastError());
CoUninitialize();
FreeLibraryAndExitThread(huia_module, 1);
}
SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)uia_provider_thread_msg_proc);
provider_thread.hwnd = hwnd;
/* Initialization complete, thread can now process window messages. */
SetEvent(initialized_event);
TRACE("Provider thread started.\n");
while (GetMessageW(&msg, NULL, 0, 0))
{
if (msg.message == WM_UIA_PROVIDER_THREAD_STOP)
break;
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
TRACE("Shutting down UI Automation provider thread.\n");
DestroyWindow(hwnd);
CoUninitialize();
FreeLibraryAndExitThread(huia_module, 0);
}
static BOOL uia_start_provider_thread(void)
{
BOOL started = TRUE;
EnterCriticalSection(&provider_thread_cs);
if (++provider_thread.ref == 1)
{
HANDLE ready_event;
HANDLE events[2];
HMODULE hmodule;
DWORD wait_obj;
/* Increment DLL reference count. */
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(const WCHAR *)uia_start_provider_thread, &hmodule);
list_init(&provider_thread.nodes_list);
rb_init(&provider_thread.node_map, uia_runtime_id_compare);
events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL);
if (!(provider_thread.hthread = CreateThread(NULL, 0, uia_provider_thread_proc,
ready_event, 0, NULL)))
{
FreeLibrary(hmodule);
started = FALSE;
goto exit;
}
events[1] = provider_thread.hthread;
wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (wait_obj != WAIT_OBJECT_0)
{
CloseHandle(provider_thread.hthread);
started = FALSE;
}
exit:
CloseHandle(ready_event);
if (!started)
{
WARN("Failed to start provider thread\n");
memset(&provider_thread, 0, sizeof(provider_thread));
}
}
LeaveCriticalSection(&provider_thread_cs);
return started;
}
void uia_stop_provider_thread(void)
{
EnterCriticalSection(&provider_thread_cs);
if (!--provider_thread.ref)
{
PostMessageW(provider_thread.hwnd, WM_UIA_PROVIDER_THREAD_STOP, 0, 0);
CloseHandle(provider_thread.hthread);
if (!list_empty(&provider_thread.nodes_list))
ERR("Provider thread shutdown with nodes still in the list\n");
memset(&provider_thread, 0, sizeof(provider_thread));
}
LeaveCriticalSection(&provider_thread_cs);
}
/*
* Pass our IWineUiaNode interface to the provider thread for marshaling. UI
* Automation has to work regardless of whether or not COM is initialized on
* the thread calling UiaReturnRawElementProvider.
*/
LRESULT uia_lresult_from_node(HUIANODE huianode)
{
SAFEARRAY *rt_id;
LRESULT lr = 0;
HRESULT hr;
hr = UiaGetRuntimeId(huianode, &rt_id);
if (SUCCEEDED(hr) && uia_start_provider_thread())
lr = SendMessageW(provider_thread.hwnd, WM_GET_OBJECT_UIA_NODE, (WPARAM)rt_id, (LPARAM)huianode);
if (FAILED(hr))
WARN("UiaGetRuntimeId failed with hr %#lx\n", hr);
/*
* LresultFromObject increases refcnt by 1. If LresultFromObject
* failed or wasn't called, this is expected to release the node.
*/
UiaNodeRelease(huianode);
SafeArrayDestroy(rt_id);
return lr;
}
/***********************************************************************
* UiaReturnRawElementProvider (uiautomationcore.@)
*/
LRESULT WINAPI UiaReturnRawElementProvider(HWND hwnd, WPARAM wparam,
LPARAM lparam, IRawElementProviderSimple *elprov)
{
HUIANODE node;
HRESULT hr;
TRACE("(%p, %Ix, %#Ix, %p)\n", hwnd, wparam, lparam, elprov);
if (!wparam && !lparam && !elprov)
{
FIXME("UIA-to-MSAA bridge not implemented, no provider map to free.\n");
return 0;
}
if (lparam != UiaRootObjectId)
{
FIXME("Unsupported object id %Id, ignoring.\n", lparam);
return 0;
}
hr = create_uia_node_from_elprov(elprov, &node, FALSE);
if (FAILED(hr))
{
WARN("Failed to create HUIANODE with hr %#lx\n", hr);
return 0;
}
return uia_lresult_from_node(node);
}
/***********************************************************************
* UiaDisconnectProvider (uiautomationcore.@)
*/
HRESULT WINAPI UiaDisconnectProvider(IRawElementProviderSimple *elprov)
{
SAFEARRAY *sa;
HUIANODE node;
HRESULT hr;
TRACE("(%p)\n", elprov);
hr = create_uia_node_from_elprov(elprov, &node, FALSE);
if (FAILED(hr))
return hr;
hr = UiaGetRuntimeId(node, &sa);
UiaNodeRelease(node);
if (FAILED(hr))
return hr;
if (!sa)
return E_INVALIDARG;
uia_provider_thread_disconnect_node(sa);
SafeArrayDestroy(sa);
return S_OK;
}