487 lines
12 KiB
C
487 lines
12 KiB
C
/*
|
|
* Copyright 2023 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 "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
|
|
|
|
/*
|
|
* Global interface table helper functions.
|
|
*/
|
|
static HRESULT get_global_interface_table(IGlobalInterfaceTable **git)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = CoCreateInstance(&CLSID_StdGlobalInterfaceTable, NULL,
|
|
CLSCTX_INPROC_SERVER, &IID_IGlobalInterfaceTable, (void **)git);
|
|
if (FAILED(hr))
|
|
WARN("Failed to get GlobalInterfaceTable, hr %#lx\n", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT register_interface_in_git(IUnknown *iface, REFIID riid, DWORD *ret_cookie)
|
|
{
|
|
IGlobalInterfaceTable *git;
|
|
DWORD git_cookie;
|
|
HRESULT hr;
|
|
|
|
*ret_cookie = 0;
|
|
hr = get_global_interface_table(&git);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = IGlobalInterfaceTable_RegisterInterfaceInGlobal(git, iface, riid, &git_cookie);
|
|
if (FAILED(hr))
|
|
{
|
|
WARN("Failed to register interface in GlobalInterfaceTable, hr %#lx\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
*ret_cookie = git_cookie;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT unregister_interface_in_git(DWORD git_cookie)
|
|
{
|
|
IGlobalInterfaceTable *git;
|
|
HRESULT hr;
|
|
|
|
hr = get_global_interface_table(&git);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = IGlobalInterfaceTable_RevokeInterfaceFromGlobal(git, git_cookie);
|
|
if (FAILED(hr))
|
|
WARN("Failed to revoke interface from GlobalInterfaceTable, hr %#lx\n", hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT get_interface_in_git(REFIID riid, DWORD git_cookie, IUnknown **ret_iface)
|
|
{
|
|
IGlobalInterfaceTable *git;
|
|
IUnknown *iface;
|
|
HRESULT hr;
|
|
|
|
hr = get_global_interface_table(&git);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = IGlobalInterfaceTable_GetInterfaceFromGlobal(git, git_cookie, riid, (void **)&iface);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("Failed to get interface from Global Interface Table, hr %#lx\n", hr);
|
|
return hr;
|
|
}
|
|
|
|
*ret_iface = iface;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#define UIA_RUNTIME_ID_PREFIX 42
|
|
HRESULT write_runtime_id_base(SAFEARRAY *sa, HWND hwnd)
|
|
{
|
|
const int rt_id[2] = { UIA_RUNTIME_ID_PREFIX, HandleToUlong(hwnd) };
|
|
HRESULT hr;
|
|
LONG idx;
|
|
|
|
for (idx = 0; idx < ARRAY_SIZE(rt_id); idx++)
|
|
{
|
|
hr = SafeArrayPutElement(sa, &idx, (void *)&rt_id[idx]);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
/*
|
|
* UiaCondition cloning functions.
|
|
*/
|
|
static void uia_condition_destroy(struct UiaCondition *cond)
|
|
{
|
|
if (!cond)
|
|
return;
|
|
|
|
switch (cond->ConditionType)
|
|
{
|
|
case ConditionType_Property:
|
|
{
|
|
struct UiaPropertyCondition *prop_cond = (struct UiaPropertyCondition *)cond;
|
|
|
|
VariantClear(&prop_cond->Value);
|
|
break;
|
|
}
|
|
|
|
case ConditionType_Not:
|
|
{
|
|
struct UiaNotCondition *not_cond = (struct UiaNotCondition *)cond;
|
|
|
|
uia_condition_destroy(not_cond->pConditions);
|
|
break;
|
|
}
|
|
|
|
case ConditionType_And:
|
|
case ConditionType_Or:
|
|
{
|
|
struct UiaAndOrCondition *and_or_cond = (struct UiaAndOrCondition *)cond;
|
|
int i;
|
|
|
|
for (i = 0; i < and_or_cond->cConditions; i++)
|
|
uia_condition_destroy(and_or_cond->ppConditions[i]);
|
|
free(and_or_cond->ppConditions);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
free(cond);
|
|
}
|
|
|
|
static HRESULT uia_condition_clone(struct UiaCondition **dst, struct UiaCondition *src)
|
|
{
|
|
HRESULT hr = S_OK;
|
|
|
|
*dst = NULL;
|
|
switch (src->ConditionType)
|
|
{
|
|
case ConditionType_True:
|
|
case ConditionType_False:
|
|
if (!(*dst = calloc(1, sizeof(**dst))))
|
|
return E_OUTOFMEMORY;
|
|
|
|
(*dst)->ConditionType = src->ConditionType;
|
|
break;
|
|
|
|
case ConditionType_Property:
|
|
{
|
|
struct UiaPropertyCondition *prop_cond = calloc(1, sizeof(*prop_cond));
|
|
struct UiaPropertyCondition *src_cond = (struct UiaPropertyCondition *)src;
|
|
|
|
if (!prop_cond)
|
|
return E_OUTOFMEMORY;
|
|
|
|
*dst = (struct UiaCondition *)prop_cond;
|
|
prop_cond->ConditionType = ConditionType_Property;
|
|
prop_cond->PropertyId = src_cond->PropertyId;
|
|
prop_cond->Flags = src_cond->Flags;
|
|
VariantInit(&prop_cond->Value);
|
|
hr = VariantCopy(&prop_cond->Value, &src_cond->Value);
|
|
break;
|
|
}
|
|
|
|
case ConditionType_Not:
|
|
{
|
|
struct UiaNotCondition *not_cond = calloc(1, sizeof(*not_cond));
|
|
struct UiaNotCondition *src_cond = (struct UiaNotCondition *)src;
|
|
|
|
if (!not_cond)
|
|
return E_OUTOFMEMORY;
|
|
|
|
*dst = (struct UiaCondition *)not_cond;
|
|
not_cond->ConditionType = ConditionType_Not;
|
|
hr = uia_condition_clone(¬_cond->pConditions, src_cond->pConditions);
|
|
break;
|
|
}
|
|
|
|
case ConditionType_And:
|
|
case ConditionType_Or:
|
|
{
|
|
struct UiaAndOrCondition *and_or_cond = calloc(1, sizeof(*and_or_cond));
|
|
struct UiaAndOrCondition *src_cond = (struct UiaAndOrCondition *)src;
|
|
int i;
|
|
|
|
if (!and_or_cond)
|
|
return E_OUTOFMEMORY;
|
|
|
|
*dst = (struct UiaCondition *)and_or_cond;
|
|
and_or_cond->ConditionType = src_cond->ConditionType;
|
|
and_or_cond->ppConditions = calloc(src_cond->cConditions, sizeof(*and_or_cond->ppConditions));
|
|
if (!and_or_cond->ppConditions)
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
goto exit;
|
|
}
|
|
|
|
and_or_cond->cConditions = src_cond->cConditions;
|
|
for (i = 0; i < src_cond->cConditions; i++)
|
|
{
|
|
hr = uia_condition_clone(&and_or_cond->ppConditions[i], src_cond->ppConditions[i]);
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
WARN("Tried to clone condition with invalid type %d\n", src->ConditionType);
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
exit:
|
|
if (FAILED(hr))
|
|
{
|
|
uia_condition_destroy(*dst);
|
|
*dst = NULL;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/*
|
|
* UiaCacheRequest cloning functions.
|
|
*/
|
|
void uia_cache_request_destroy(struct UiaCacheRequest *cache_req)
|
|
{
|
|
uia_condition_destroy(cache_req->pViewCondition);
|
|
free(cache_req->pProperties);
|
|
free(cache_req->pPatterns);
|
|
}
|
|
|
|
HRESULT uia_cache_request_clone(struct UiaCacheRequest *dst, struct UiaCacheRequest *src)
|
|
{
|
|
HRESULT hr;
|
|
|
|
hr = uia_condition_clone(&dst->pViewCondition, src->pViewCondition);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
dst->Scope = src->Scope;
|
|
dst->automationElementMode = src->automationElementMode;
|
|
if (src->cProperties)
|
|
{
|
|
if (!(dst->pProperties = calloc(src->cProperties, sizeof(*dst->pProperties))))
|
|
{
|
|
uia_cache_request_destroy(dst);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
dst->cProperties = src->cProperties;
|
|
memcpy(dst->pProperties, src->pProperties, sizeof(*dst->pProperties) * dst->cProperties);
|
|
}
|
|
|
|
if (src->cPatterns)
|
|
{
|
|
if (!(dst->pPatterns = calloc(src->cPatterns, sizeof(*dst->pPatterns))))
|
|
{
|
|
uia_cache_request_destroy(dst);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
dst->cPatterns = src->cPatterns;
|
|
memcpy(dst->pPatterns, src->pPatterns, sizeof(*dst->pPatterns) * dst->cPatterns);
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT get_safearray_dim_bounds(SAFEARRAY *sa, UINT dim, LONG *lbound, LONG *elems)
|
|
{
|
|
LONG ubound;
|
|
HRESULT hr;
|
|
|
|
*lbound = *elems = 0;
|
|
hr = SafeArrayGetLBound(sa, dim, lbound);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
hr = SafeArrayGetUBound(sa, dim, &ubound);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
|
|
*elems = (ubound - (*lbound)) + 1;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT get_safearray_bounds(SAFEARRAY *sa, LONG *lbound, LONG *elems)
|
|
{
|
|
UINT dims;
|
|
|
|
*lbound = *elems = 0;
|
|
dims = SafeArrayGetDim(sa);
|
|
if (dims != 1)
|
|
{
|
|
WARN("Invalid dimensions %d for safearray.\n", dims);
|
|
return E_FAIL;
|
|
}
|
|
|
|
return get_safearray_dim_bounds(sa, 1, lbound, elems);
|
|
}
|
|
|
|
int uia_compare_safearrays(SAFEARRAY *sa1, SAFEARRAY *sa2, int prop_type)
|
|
{
|
|
LONG i, idx, lbound[2], elems[2];
|
|
int val[2];
|
|
HRESULT hr;
|
|
|
|
hr = get_safearray_bounds(sa1, &lbound[0], &elems[0]);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("Failed to get safearray bounds from sa1 with hr %#lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
hr = get_safearray_bounds(sa2, &lbound[1], &elems[1]);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("Failed to get safearray bounds from sa2 with hr %#lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
if (elems[0] != elems[1])
|
|
return (elems[0] > elems[1]) - (elems[0] < elems[1]);
|
|
|
|
if (prop_type != UIAutomationType_IntArray)
|
|
{
|
|
FIXME("Array type %#x value comparison currently unimplemented.\n", prop_type);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < elems[0]; i++)
|
|
{
|
|
idx = lbound[0] + i;
|
|
hr = SafeArrayGetElement(sa1, &idx, &val[0]);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("Failed to get element from sa1 with hr %#lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
idx = lbound[1] + i;
|
|
hr = SafeArrayGetElement(sa2, &idx, &val[1]);
|
|
if (FAILED(hr))
|
|
{
|
|
ERR("Failed to get element from sa2 with hr %#lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
if (val[0] != val[1])
|
|
return (val[0] > val[1]) - (val[0] < val[1]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* HWND related helper functions.
|
|
*/
|
|
BOOL uia_hwnd_is_visible(HWND hwnd)
|
|
{
|
|
RECT rect;
|
|
|
|
if (!IsWindowVisible(hwnd))
|
|
return FALSE;
|
|
|
|
if (!GetWindowRect(hwnd, &rect))
|
|
return FALSE;
|
|
|
|
if ((rect.right - rect.left) <= 0 || (rect.bottom - rect.top) <= 0)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL uia_is_top_level_hwnd(HWND hwnd)
|
|
{
|
|
return GetAncestor(hwnd, GA_PARENT) == GetDesktopWindow();
|
|
}
|
|
|
|
/*
|
|
* rbtree to efficiently store a collection of HWNDs.
|
|
*/
|
|
struct uia_hwnd_map_entry
|
|
{
|
|
struct rb_entry entry;
|
|
HWND hwnd;
|
|
};
|
|
|
|
static int uia_hwnd_map_hwnd_compare(const void *key, const struct rb_entry *entry)
|
|
{
|
|
struct uia_hwnd_map_entry *hwnd_entry = RB_ENTRY_VALUE(entry, struct uia_hwnd_map_entry, entry);
|
|
HWND hwnd = (HWND)key;
|
|
|
|
return (hwnd_entry->hwnd > hwnd) - (hwnd_entry->hwnd < hwnd);
|
|
}
|
|
|
|
static void uia_hwnd_map_free(struct rb_entry *entry, void *context)
|
|
{
|
|
struct uia_hwnd_map_entry *hwnd_entry = RB_ENTRY_VALUE(entry, struct uia_hwnd_map_entry, entry);
|
|
|
|
TRACE("Removing hwnd %p from map %p\n", hwnd_entry->hwnd, context);
|
|
free(hwnd_entry);
|
|
}
|
|
|
|
BOOL uia_hwnd_map_check_hwnd(struct rb_tree *hwnd_map, HWND hwnd)
|
|
{
|
|
return !!rb_get(hwnd_map, hwnd);
|
|
}
|
|
|
|
HRESULT uia_hwnd_map_add_hwnd(struct rb_tree *hwnd_map, HWND hwnd)
|
|
{
|
|
struct uia_hwnd_map_entry *entry;
|
|
|
|
if (uia_hwnd_map_check_hwnd(hwnd_map, hwnd))
|
|
{
|
|
TRACE("hwnd %p already in map %p\n", hwnd, hwnd_map);
|
|
return S_OK;
|
|
}
|
|
|
|
if (!(entry = calloc(1, sizeof(*entry))))
|
|
return E_OUTOFMEMORY;
|
|
|
|
TRACE("Adding hwnd %p to map %p\n", hwnd, hwnd_map);
|
|
entry->hwnd = hwnd;
|
|
rb_put(hwnd_map, hwnd, &entry->entry);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
void uia_hwnd_map_remove_hwnd(struct rb_tree *hwnd_map, HWND hwnd)
|
|
{
|
|
struct rb_entry *rb_entry = rb_get(hwnd_map, hwnd);
|
|
struct uia_hwnd_map_entry *entry;
|
|
|
|
if (!rb_entry)
|
|
{
|
|
TRACE("hwnd %p not in map %p, nothing to remove.\n", hwnd, hwnd_map);
|
|
return;
|
|
}
|
|
|
|
TRACE("Removing hwnd %p from map %p\n", hwnd, hwnd_map);
|
|
entry = RB_ENTRY_VALUE(rb_entry, struct uia_hwnd_map_entry, entry);
|
|
rb_remove(hwnd_map, &entry->entry);
|
|
free(entry);
|
|
}
|
|
|
|
void uia_hwnd_map_init(struct rb_tree *hwnd_map)
|
|
{
|
|
rb_init(hwnd_map, uia_hwnd_map_hwnd_compare);
|
|
}
|
|
|
|
void uia_hwnd_map_destroy(struct rb_tree *hwnd_map)
|
|
{
|
|
rb_destroy(hwnd_map, uia_hwnd_map_free, hwnd_map);
|
|
}
|