wine/dlls/uiautomationcore/uia_event.c
Connor McAdams cc75dbd315 uiautomationcore: Add support for translating EVENT_OBJECT_FOCUS for native MSAA IAccessibles.
Signed-off-by: Connor McAdams <cmcadams@codeweavers.com>
2023-10-16 11:15:38 +02:00

1981 lines
59 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"
#include "wine/rbtree.h"
WINE_DEFAULT_DEBUG_CHANNEL(uiautomation);
static SAFEARRAY *uia_desktop_node_rt_id;
static BOOL WINAPI uia_init_desktop_rt_id(INIT_ONCE *once, void *param, void **ctx)
{
SAFEARRAY *sa;
if ((sa = SafeArrayCreateVector(VT_I4, 0, 2)))
{
if (SUCCEEDED(write_runtime_id_base(sa, GetDesktopWindow())))
uia_desktop_node_rt_id = sa;
else
SafeArrayDestroy(sa);
}
return !!uia_desktop_node_rt_id;
}
static SAFEARRAY *uia_get_desktop_rt_id(void)
{
static INIT_ONCE once = INIT_ONCE_STATIC_INIT;
if (!uia_desktop_node_rt_id)
InitOnceExecuteOnce(&once, uia_init_desktop_rt_id, NULL, NULL);
return uia_desktop_node_rt_id;
}
static int win_event_to_uia_event_id(int win_event)
{
switch (win_event)
{
case EVENT_OBJECT_FOCUS: return UIA_AutomationFocusChangedEventId;
case EVENT_SYSTEM_ALERT: return UIA_SystemAlertEventId;
case EVENT_OBJECT_SHOW: return UIA_StructureChangedEventId;
case EVENT_OBJECT_DESTROY: return UIA_StructureChangedEventId;
default:
break;
}
return 0;
}
static BOOL CALLBACK uia_win_event_enum_top_level_hwnds(HWND hwnd, LPARAM lparam)
{
struct rb_tree *hwnd_map = (struct rb_tree *)lparam;
HRESULT hr;
if (!uia_hwnd_is_visible(hwnd))
return TRUE;
hr = uia_hwnd_map_add_hwnd(hwnd_map, hwnd);
if (FAILED(hr))
WARN("Failed to add hwnd to map, hr %#lx\n", hr);
return TRUE;
}
HRESULT uia_event_add_win_event_hwnd(struct uia_event *event, HWND hwnd)
{
if (!uia_clientside_event_start_event_thread(event))
return E_FAIL;
if (hwnd == GetDesktopWindow())
EnumWindows(uia_win_event_enum_top_level_hwnds, (LPARAM)&event->u.clientside.win_event_hwnd_map);
return uia_hwnd_map_add_hwnd(&event->u.clientside.win_event_hwnd_map, hwnd);
}
/*
* UI Automation event map.
*/
static struct uia_event_map
{
struct rb_tree event_map;
LONG event_count;
/* rb_tree for serverside events, sorted by PID/event cookie. */
struct rb_tree serverside_event_map;
LONG serverside_event_count;
} uia_event_map;
struct uia_event_map_entry
{
struct rb_entry entry;
LONG refs;
int event_id;
/*
* List of registered events for this event ID. Events are only removed
* from the list when the event map entry reference count hits 0 and the
* entry is destroyed. This avoids dealing with mid-list removal while
* iterating over the list when an event is raised. Rather than remove
* an event from the list, we mark an event as being defunct so it is
* ignored.
*/
struct list events_list;
struct list serverside_events_list;
};
struct uia_event_identifier {
LONG event_cookie;
LONG proc_id;
};
static int uia_serverside_event_id_compare(const void *key, const struct rb_entry *entry)
{
struct uia_event *event = RB_ENTRY_VALUE(entry, struct uia_event, u.serverside.serverside_event_entry);
struct uia_event_identifier *event_id = (struct uia_event_identifier *)key;
if (event_id->proc_id != event->u.serverside.proc_id)
return (event_id->proc_id > event->u.serverside.proc_id) - (event_id->proc_id < event->u.serverside.proc_id);
else
return (event_id->event_cookie > event->event_cookie) - (event_id->event_cookie < event->event_cookie);
}
static CRITICAL_SECTION event_map_cs;
static CRITICAL_SECTION_DEBUG event_map_cs_debug =
{
0, 0, &event_map_cs,
{ &event_map_cs_debug.ProcessLocksList, &event_map_cs_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": event_map_cs") }
};
static CRITICAL_SECTION event_map_cs = { &event_map_cs_debug, -1, 0, 0, 0, 0 };
static int uia_event_map_id_compare(const void *key, const struct rb_entry *entry)
{
struct uia_event_map_entry *event_entry = RB_ENTRY_VALUE(entry, struct uia_event_map_entry, entry);
int event_id = *((int *)key);
return (event_entry->event_id > event_id) - (event_entry->event_id < event_id);
}
static struct uia_event_map_entry *uia_get_event_map_entry_for_event(int event_id)
{
struct uia_event_map_entry *map_entry = NULL;
struct rb_entry *rb_entry;
if (uia_event_map.event_count && (rb_entry = rb_get(&uia_event_map.event_map, &event_id)))
map_entry = RB_ENTRY_VALUE(rb_entry, struct uia_event_map_entry, entry);
return map_entry;
}
static HRESULT uia_event_map_add_event(struct uia_event *event)
{
const int subtree_scope = TreeScope_Element | TreeScope_Descendants;
struct uia_event_map_entry *event_entry;
if (((event->scope & subtree_scope) == subtree_scope) && event->runtime_id &&
!uia_compare_safearrays(uia_get_desktop_rt_id(), event->runtime_id, UIAutomationType_IntArray))
event->desktop_subtree_event = TRUE;
EnterCriticalSection(&event_map_cs);
if (!(event_entry = uia_get_event_map_entry_for_event(event->event_id)))
{
if (!(event_entry = calloc(1, sizeof(*event_entry))))
{
LeaveCriticalSection(&event_map_cs);
return E_OUTOFMEMORY;
}
event_entry->event_id = event->event_id;
list_init(&event_entry->events_list);
list_init(&event_entry->serverside_events_list);
if (!uia_event_map.event_count)
rb_init(&uia_event_map.event_map, uia_event_map_id_compare);
rb_put(&uia_event_map.event_map, &event->event_id, &event_entry->entry);
uia_event_map.event_count++;
}
IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface);
if (event->event_type == EVENT_TYPE_SERVERSIDE)
list_add_head(&event_entry->serverside_events_list, &event->event_list_entry);
else
list_add_head(&event_entry->events_list, &event->event_list_entry);
InterlockedIncrement(&event_entry->refs);
event->event_map_entry = event_entry;
LeaveCriticalSection(&event_map_cs);
return S_OK;
}
static void uia_event_map_entry_release(struct uia_event_map_entry *entry)
{
ULONG ref = InterlockedDecrement(&entry->refs);
if (!ref)
{
struct list *cursor, *cursor2;
EnterCriticalSection(&event_map_cs);
/*
* Someone grabbed this while we were waiting to enter the CS, abort
* destruction.
*/
if (InterlockedCompareExchange(&entry->refs, 0, 0) != 0)
{
LeaveCriticalSection(&event_map_cs);
return;
}
rb_remove(&uia_event_map.event_map, &entry->entry);
uia_event_map.event_count--;
LeaveCriticalSection(&event_map_cs);
/* Release all events in the list. */
LIST_FOR_EACH_SAFE(cursor, cursor2, &entry->events_list)
{
struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry);
IWineUiaEvent_Release(&event->IWineUiaEvent_iface);
}
LIST_FOR_EACH_SAFE(cursor, cursor2, &entry->serverside_events_list)
{
struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry);
IWineUiaEvent_Release(&event->IWineUiaEvent_iface);
}
free(entry);
}
}
HRESULT uia_event_for_each(int event_id, UiaWineEventForEachCallback *callback, void *user_data,
BOOL clientside_only)
{
struct uia_event_map_entry *event_entry;
HRESULT hr = S_OK;
int i;
EnterCriticalSection(&event_map_cs);
if ((event_entry = uia_get_event_map_entry_for_event(event_id)))
InterlockedIncrement(&event_entry->refs);
LeaveCriticalSection(&event_map_cs);
if (!event_entry)
return S_OK;
for (i = 0; i < 2; i++)
{
struct list *events = !i ? &event_entry->events_list : &event_entry->serverside_events_list;
struct list *cursor, *cursor2;
if (i && clientside_only)
break;
LIST_FOR_EACH_SAFE(cursor, cursor2, events)
{
struct uia_event *event = LIST_ENTRY(cursor, struct uia_event, event_list_entry);
/* Event is no longer valid. */
if (InterlockedCompareExchange(&event->event_defunct, 0, 0) != 0)
continue;
hr = callback(event, user_data);
if (FAILED(hr))
goto exit;
}
}
exit:
if (FAILED(hr))
WARN("Event callback failed with hr %#lx\n", hr);
uia_event_map_entry_release(event_entry);
return hr;
}
/*
* Functions for struct uia_event_args, a reference counted structure
* used to store event arguments. This is necessary for serverside events
* as they're raised on a background thread after the event raising
* function has returned.
*/
static struct uia_event_args *create_uia_event_args(const struct uia_event_info *event_info)
{
struct uia_event_args *args = calloc(1, sizeof(*args));
if (!args)
return NULL;
args->simple_args.Type = event_info->event_arg_type;
args->simple_args.EventId = event_info->event_id;
args->ref = 1;
return args;
}
static void uia_event_args_release(struct uia_event_args *args)
{
if (!InterlockedDecrement(&args->ref))
free(args);
}
struct event_sink_event
{
struct list event_sink_list_entry;
IRawElementProviderSimple *elprov;
struct uia_event_args *args;
};
static HRESULT uia_event_sink_list_add_event(struct list *sink_events, IRawElementProviderSimple *elprov,
struct uia_event_args *args)
{
struct event_sink_event *sink_event = calloc(1, sizeof(*sink_event));
if (!sink_event)
return E_OUTOFMEMORY;
IRawElementProviderSimple_AddRef(elprov);
InterlockedIncrement(&args->ref);
sink_event->elprov = elprov;
sink_event->args = args;
list_add_tail(sink_events, &sink_event->event_sink_list_entry);
return S_OK;
}
/*
* IProxyProviderWinEventSink interface implementation.
*/
struct uia_proxy_win_event_sink {
IProxyProviderWinEventSink IProxyProviderWinEventSink_iface;
LONG ref;
int event_id;
IUnknown *marshal;
LONG sink_defunct;
struct list sink_events;
};
static inline struct uia_proxy_win_event_sink *impl_from_IProxyProviderWinEventSink(IProxyProviderWinEventSink *iface)
{
return CONTAINING_RECORD(iface, struct uia_proxy_win_event_sink, IProxyProviderWinEventSink_iface);
}
static HRESULT WINAPI uia_proxy_win_event_sink_QueryInterface(IProxyProviderWinEventSink *iface, REFIID riid, void **obj)
{
struct uia_proxy_win_event_sink *sink = impl_from_IProxyProviderWinEventSink(iface);
*obj = NULL;
if (IsEqualIID(riid, &IID_IProxyProviderWinEventSink) || IsEqualIID(riid, &IID_IUnknown))
*obj = iface;
else if (IsEqualIID(riid, &IID_IMarshal))
return IUnknown_QueryInterface(sink->marshal, riid, obj);
else
return E_NOINTERFACE;
IProxyProviderWinEventSink_AddRef(iface);
return S_OK;
}
static ULONG WINAPI uia_proxy_win_event_sink_AddRef(IProxyProviderWinEventSink *iface)
{
struct uia_proxy_win_event_sink *sink = impl_from_IProxyProviderWinEventSink(iface);
ULONG ref = InterlockedIncrement(&sink->ref);
TRACE("%p, refcount %ld\n", sink, ref);
return ref;
}
static ULONG WINAPI uia_proxy_win_event_sink_Release(IProxyProviderWinEventSink *iface)
{
struct uia_proxy_win_event_sink *sink = impl_from_IProxyProviderWinEventSink(iface);
ULONG ref = InterlockedDecrement(&sink->ref);
TRACE("%p, refcount %ld\n", sink, ref);
if (!ref)
{
assert(list_empty(&sink->sink_events));
IUnknown_Release(sink->marshal);
free(sink);
}
return ref;
}
static HRESULT WINAPI uia_proxy_win_event_sink_AddAutomationPropertyChangedEvent(IProxyProviderWinEventSink *iface,
IRawElementProviderSimple *elprov, PROPERTYID prop_id, VARIANT new_value)
{
FIXME("%p, %p, %d, %s: stub\n", iface, elprov, prop_id, debugstr_variant(&new_value));
return E_NOTIMPL;
}
static HRESULT WINAPI uia_proxy_win_event_sink_AddAutomationEvent(IProxyProviderWinEventSink *iface,
IRawElementProviderSimple *elprov, EVENTID event_id)
{
struct uia_proxy_win_event_sink *sink = impl_from_IProxyProviderWinEventSink(iface);
struct uia_event_args *args;
HRESULT hr = S_OK;
TRACE("%p, %p, %d\n", iface, elprov, event_id);
if (event_id != sink->event_id)
return S_OK;
args = create_uia_event_args(uia_event_info_from_id(event_id));
if (!args)
return E_OUTOFMEMORY;
if (InterlockedCompareExchange(&sink->sink_defunct, 0, 0) == 0)
hr = uia_event_sink_list_add_event(&sink->sink_events, elprov, args);
uia_event_args_release(args);
return hr;
}
static HRESULT WINAPI uia_proxy_win_event_sink_AddStructureChangedEvent(IProxyProviderWinEventSink *iface,
IRawElementProviderSimple *elprov, enum StructureChangeType structure_change_type, SAFEARRAY *runtime_id)
{
FIXME("%p, %p, %d, %p: stub\n", iface, elprov, structure_change_type, runtime_id);
return E_NOTIMPL;
}
static const IProxyProviderWinEventSinkVtbl uia_proxy_event_sink_vtbl = {
uia_proxy_win_event_sink_QueryInterface,
uia_proxy_win_event_sink_AddRef,
uia_proxy_win_event_sink_Release,
uia_proxy_win_event_sink_AddAutomationPropertyChangedEvent,
uia_proxy_win_event_sink_AddAutomationEvent,
uia_proxy_win_event_sink_AddStructureChangedEvent,
};
static HRESULT create_proxy_win_event_sink(struct uia_proxy_win_event_sink **out_sink, int event_id)
{
struct uia_proxy_win_event_sink *sink = calloc(1, sizeof(*sink));
HRESULT hr;
*out_sink = NULL;
if (!sink)
return E_OUTOFMEMORY;
sink->IProxyProviderWinEventSink_iface.lpVtbl = &uia_proxy_event_sink_vtbl;
sink->ref = 1;
sink->event_id = event_id;
list_init(&sink->sink_events);
hr = CoCreateFreeThreadedMarshaler((IUnknown *)&sink->IProxyProviderWinEventSink_iface, &sink->marshal);
if (FAILED(hr))
{
free(sink);
return hr;
}
*out_sink = sink;
return S_OK;
}
/*
* UI Automation event thread.
*/
struct uia_event_thread
{
HANDLE hthread;
HWND hwnd;
LONG ref;
struct list *event_queue;
HWINEVENTHOOK hook;
};
#define WM_UIA_EVENT_THREAD_STOP (WM_USER + 1)
#define WM_UIA_EVENT_THREAD_PROCESS_QUEUE (WM_USER + 2)
static struct uia_event_thread event_thread;
static CRITICAL_SECTION event_thread_cs;
static CRITICAL_SECTION_DEBUG event_thread_cs_debug =
{
0, 0, &event_thread_cs,
{ &event_thread_cs_debug.ProcessLocksList, &event_thread_cs_debug.ProcessLocksList },
0, 0, { (DWORD_PTR)(__FILE__ ": event_thread_cs") }
};
static CRITICAL_SECTION event_thread_cs = { &event_thread_cs_debug, -1, 0, 0, 0, 0 };
enum uia_queue_event_type {
QUEUE_EVENT_TYPE_SERVERSIDE,
QUEUE_EVENT_TYPE_CLIENTSIDE,
QUEUE_EVENT_TYPE_WIN_EVENT,
};
struct uia_queue_event
{
struct list event_queue_entry;
int queue_event_type;
};
struct uia_queue_uia_event
{
struct uia_queue_event queue_entry;
struct uia_event_args *args;
struct uia_event *event;
union {
struct {
HUIANODE node;
HUIANODE nav_start_node;
} serverside;
struct {
LRESULT node;
LRESULT nav_start_node;
} clientside;
} u;
};
struct uia_queue_win_event
{
struct uia_queue_event queue_entry;
HWINEVENTHOOK hook;
DWORD event_id;
HWND hwnd;
LONG obj_id;
LONG child_id;
DWORD thread_id;
DWORD event_time;
};
static void uia_event_queue_push(struct uia_queue_event *event, int queue_event_type)
{
event->queue_event_type = queue_event_type;
EnterCriticalSection(&event_thread_cs);
if (queue_event_type == QUEUE_EVENT_TYPE_WIN_EVENT)
{
struct uia_queue_win_event *win_event = (struct uia_queue_win_event *)event;
if (win_event->hook != event_thread.hook)
{
free(event);
goto exit;
}
}
assert(event_thread.event_queue);
list_add_tail(event_thread.event_queue, &event->event_queue_entry);
PostMessageW(event_thread.hwnd, WM_UIA_EVENT_THREAD_PROCESS_QUEUE, 0, 0);
exit:
LeaveCriticalSection(&event_thread_cs);
}
static struct uia_queue_event *uia_event_queue_pop(struct list *event_queue)
{
struct uia_queue_event *queue_event = NULL;
EnterCriticalSection(&event_thread_cs);
if (!list_empty(event_queue))
{
queue_event = LIST_ENTRY(list_head(event_queue), struct uia_queue_event, event_queue_entry);
list_remove(list_head(event_queue));
}
LeaveCriticalSection(&event_thread_cs);
return queue_event;
}
static HRESULT uia_raise_clientside_event(struct uia_queue_uia_event *event)
{
HUIANODE node, nav_start_node;
HRESULT hr;
node = nav_start_node = NULL;
hr = uia_node_from_lresult(event->u.clientside.node, &node, 0);
if (FAILED(hr))
{
WARN("Failed to create node from lresult, hr %#lx\n", hr);
uia_node_lresult_release(event->u.clientside.nav_start_node);
return hr;
}
if (event->u.clientside.nav_start_node)
{
hr = uia_node_from_lresult(event->u.clientside.nav_start_node, &nav_start_node, 0);
if (FAILED(hr))
{
WARN("Failed to create nav_start_node from lresult, hr %#lx\n", hr);
UiaNodeRelease(node);
return hr;
}
}
hr = uia_event_invoke(node, nav_start_node, event->args, event->event);
UiaNodeRelease(node);
UiaNodeRelease(nav_start_node);
return hr;
}
static HRESULT uia_raise_serverside_event(struct uia_queue_uia_event *event)
{
HRESULT hr = S_OK;
LRESULT lr, lr2;
VARIANT v, v2;
/*
* uia_lresult_from_node is expected to release the node here upon
* failure.
*/
lr = lr2 = 0;
if (!(lr = uia_lresult_from_node(event->u.serverside.node)))
{
UiaNodeRelease(event->u.serverside.nav_start_node);
return E_FAIL;
}
if (event->u.serverside.nav_start_node && !(lr2 = uia_lresult_from_node(event->u.serverside.nav_start_node)))
{
uia_node_lresult_release(lr);
return E_FAIL;
}
VariantInit(&v2);
variant_init_i4(&v, lr);
if (lr2)
variant_init_i4(&v2, lr2);
hr = IWineUiaEvent_raise_event(event->event->u.serverside.event_iface, v, v2);
if (FAILED(hr))
{
uia_node_lresult_release(lr);
uia_node_lresult_release(lr2);
}
return hr;
}
/* Check the parent chain of HWNDs, excluding the desktop. */
static BOOL uia_win_event_hwnd_map_contains_ancestors(struct rb_tree *hwnd_map, HWND hwnd)
{
HWND parent = GetAncestor(hwnd, GA_PARENT);
const HWND desktop = GetDesktopWindow();
while (parent && (parent != desktop))
{
if (uia_hwnd_map_check_hwnd(hwnd_map, parent))
return TRUE;
parent = GetAncestor(parent, GA_PARENT);
}
return FALSE;
}
HRESULT create_msaa_provider_from_hwnd(HWND hwnd, int in_child_id, IRawElementProviderSimple **ret_elprov)
{
IRawElementProviderSimple *elprov;
IAccessible *acc;
int child_id;
HRESULT hr;
*ret_elprov = NULL;
hr = AccessibleObjectFromWindow(hwnd, OBJID_CLIENT, &IID_IAccessible, (void **)&acc);
if (FAILED(hr))
return hr;
child_id = in_child_id;
if (in_child_id != CHILDID_SELF)
{
IDispatch *disp;
VARIANT cid;
disp = NULL;
variant_init_i4(&cid, in_child_id);
hr = IAccessible_get_accChild(acc, cid, &disp);
if (FAILED(hr))
TRACE("get_accChild failed with %#lx!\n", hr);
if (SUCCEEDED(hr) && disp)
{
IAccessible_Release(acc);
hr = IDispatch_QueryInterface(disp, &IID_IAccessible, (void **)&acc);
IDispatch_Release(disp);
if (FAILED(hr))
return hr;
child_id = CHILDID_SELF;
}
}
hr = create_msaa_provider(acc, child_id, hwnd, TRUE, in_child_id == CHILDID_SELF, &elprov);
IAccessible_Release(acc);
if (FAILED(hr))
return hr;
*ret_elprov = elprov;
return S_OK;
}
struct uia_elprov_event_data
{
IRawElementProviderSimple *elprov;
struct uia_event_args *args;
BOOL clientside_only;
SAFEARRAY *rt_id;
HUIANODE node;
};
static HRESULT uia_raise_elprov_event_callback(struct uia_event *event, void *data);
static HRESULT uia_win_event_for_each_callback(struct uia_event *event, void *data)
{
struct uia_queue_win_event *win_event = (struct uia_queue_win_event *)data;
struct event_sink_event *sink_event, *sink_event2;
struct uia_proxy_win_event_sink *sink;
IRawElementProviderSimple *elprov;
struct uia_node *node_data;
HUIANODE node;
HRESULT hr;
int i;
/*
* Check if this HWND, or any of it's ancestors (excluding the desktop)
* are in our scope.
*/
if (!uia_hwnd_map_check_hwnd(&event->u.clientside.win_event_hwnd_map, win_event->hwnd) &&
!uia_win_event_hwnd_map_contains_ancestors(&event->u.clientside.win_event_hwnd_map, win_event->hwnd))
return S_OK;
/* Has a native serverside provider, no need to do WinEvent translation. */
if (UiaHasServerSideProvider(win_event->hwnd))
return S_OK;
/*
* Regardless of the object ID of the WinEvent, OBJID_CLIENT is queried
* for the HWND with the same child ID as the WinEvent.
*/
hr = create_msaa_provider_from_hwnd(win_event->hwnd, win_event->child_id, &elprov);
if (FAILED(hr))
return hr;
hr = create_uia_node_from_elprov(elprov, &node, TRUE, NODE_FLAG_IGNORE_COM_THREADING);
IRawElementProviderSimple_Release(elprov);
if (FAILED(hr))
return hr;
hr = create_proxy_win_event_sink(&sink, event->event_id);
if (SUCCEEDED(hr))
{
node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
for (i = 0; i < node_data->prov_count; i++)
{
hr = respond_to_win_event_on_node_provider((IWineUiaNode *)node, i, win_event->event_id, win_event->hwnd, win_event->obj_id,
win_event->child_id, &sink->IProxyProviderWinEventSink_iface);
if (FAILED(hr) || !list_empty(&sink->sink_events))
break;
}
InterlockedIncrement(&sink->sink_defunct);
LIST_FOR_EACH_ENTRY_SAFE(sink_event, sink_event2, &sink->sink_events, struct event_sink_event, event_sink_list_entry)
{
struct uia_elprov_event_data event_data = { sink_event->elprov, sink_event->args, TRUE };
list_remove(&sink_event->event_sink_list_entry);
hr = uia_raise_elprov_event_callback(event, (void *)&event_data);
if (FAILED(hr))
WARN("uia_raise_elprov_event_callback failed with hr %#lx\n", hr);
UiaNodeRelease(event_data.node);
SafeArrayDestroy(event_data.rt_id);
IRawElementProviderSimple_Release(sink_event->elprov);
uia_event_args_release(sink_event->args);
free(sink_event);
}
IProxyProviderWinEventSink_Release(&sink->IProxyProviderWinEventSink_iface);
}
UiaNodeRelease(node);
return hr;
}
static void uia_event_thread_process_queue(struct list *event_queue)
{
while (1)
{
struct uia_queue_event *event;
HRESULT hr = S_OK;
if (!(event = uia_event_queue_pop(event_queue)))
break;
switch (event->queue_event_type)
{
case QUEUE_EVENT_TYPE_SERVERSIDE:
case QUEUE_EVENT_TYPE_CLIENTSIDE:
{
struct uia_queue_uia_event *uia_event = (struct uia_queue_uia_event *)event;
if (event->queue_event_type == QUEUE_EVENT_TYPE_SERVERSIDE)
hr = uia_raise_serverside_event(uia_event);
else
hr = uia_raise_clientside_event(uia_event);
uia_event_args_release(uia_event->args);
IWineUiaEvent_Release(&uia_event->event->IWineUiaEvent_iface);
break;
}
case QUEUE_EVENT_TYPE_WIN_EVENT:
{
struct uia_queue_win_event *win_event = (struct uia_queue_win_event *)event;
hr = uia_com_win_event_callback(win_event->event_id, win_event->hwnd, win_event->obj_id, win_event->child_id,
win_event->thread_id, win_event->event_time);
if (FAILED(hr))
WARN("uia_com_win_event_callback failed with hr %#lx\n", hr);
hr = uia_event_for_each(win_event_to_uia_event_id(win_event->event_id), uia_win_event_for_each_callback,
(void *)win_event, TRUE);
break;
}
default:
break;
}
if (FAILED(hr))
WARN("Failed to raise event type %d with hr %#lx\n", event->queue_event_type, hr);
free(event);
}
}
static void CALLBACK uia_event_thread_win_event_proc(HWINEVENTHOOK hook, DWORD event_id, HWND hwnd, LONG obj_id,
LONG child_id, DWORD thread_id, DWORD event_time)
{
struct uia_queue_win_event *win_event;
TRACE("%p, %ld, %p, %ld, %ld, %ld, %ld\n", hook, event_id, hwnd, obj_id, child_id, thread_id, event_time);
if (!win_event_to_uia_event_id(event_id))
return;
if (!(win_event = calloc(1, sizeof(*win_event))))
{
ERR("Failed to allocate uia_queue_win_event structure\n");
return;
}
win_event->hook = hook;
win_event->event_id = event_id;
win_event->hwnd = hwnd;
win_event->obj_id = obj_id;
win_event->child_id = child_id;
win_event->thread_id = thread_id;
win_event->event_time = event_time;
uia_event_queue_push(&win_event->queue_entry, QUEUE_EVENT_TYPE_WIN_EVENT);
}
static DWORD WINAPI uia_event_thread_proc(void *arg)
{
HANDLE initialized_event = arg;
struct list event_queue;
HWINEVENTHOOK hook;
HWND hwnd;
MSG msg;
list_init(&event_queue);
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);
}
event_thread.hwnd = hwnd;
event_thread.event_queue = &event_queue;
event_thread.hook = hook = SetWinEventHook(EVENT_MIN, EVENT_MAX, 0, uia_event_thread_win_event_proc, 0, 0,
WINEVENT_OUTOFCONTEXT);
/* Initialization complete, thread can now process window messages. */
SetEvent(initialized_event);
TRACE("Event thread started.\n");
while (GetMessageW(&msg, NULL, 0, 0))
{
if ((msg.hwnd == hwnd) && ((msg.message == WM_UIA_EVENT_THREAD_STOP) ||
(msg.message == WM_UIA_EVENT_THREAD_PROCESS_QUEUE)))
{
uia_event_thread_process_queue(&event_queue);
if (msg.message == WM_UIA_EVENT_THREAD_STOP)
break;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
TRACE("Shutting down UI Automation event thread.\n");
UnhookWinEvent(hook);
DestroyWindow(hwnd);
CoUninitialize();
FreeLibraryAndExitThread(huia_module, 0);
}
static BOOL uia_start_event_thread(void)
{
BOOL started = TRUE;
EnterCriticalSection(&event_thread_cs);
if (++event_thread.ref == 1)
{
HANDLE ready_event = NULL;
HANDLE events[2];
HMODULE hmodule;
DWORD wait_obj;
/* Increment DLL reference count. */
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
(const WCHAR *)uia_start_event_thread, &hmodule);
events[0] = ready_event = CreateEventW(NULL, FALSE, FALSE, NULL);
if (!(event_thread.hthread = CreateThread(NULL, 0, uia_event_thread_proc,
ready_event, 0, NULL)))
{
FreeLibrary(hmodule);
started = FALSE;
goto exit;
}
events[1] = event_thread.hthread;
wait_obj = WaitForMultipleObjects(2, events, FALSE, INFINITE);
if (wait_obj != WAIT_OBJECT_0)
{
CloseHandle(event_thread.hthread);
started = FALSE;
}
exit:
if (ready_event)
CloseHandle(ready_event);
if (!started)
memset(&event_thread, 0, sizeof(event_thread));
}
LeaveCriticalSection(&event_thread_cs);
return started;
}
static void uia_stop_event_thread(void)
{
EnterCriticalSection(&event_thread_cs);
if (!--event_thread.ref)
{
PostMessageW(event_thread.hwnd, WM_UIA_EVENT_THREAD_STOP, 0, 0);
CloseHandle(event_thread.hthread);
memset(&event_thread, 0, sizeof(event_thread));
}
LeaveCriticalSection(&event_thread_cs);
}
BOOL uia_clientside_event_start_event_thread(struct uia_event *event)
{
if (!event->u.clientside.event_thread_started)
event->u.clientside.event_thread_started = uia_start_event_thread();
return event->u.clientside.event_thread_started;
}
/*
* IWineUiaEvent interface.
*/
static inline struct uia_event *impl_from_IWineUiaEvent(IWineUiaEvent *iface)
{
return CONTAINING_RECORD(iface, struct uia_event, IWineUiaEvent_iface);
}
static HRESULT WINAPI uia_event_QueryInterface(IWineUiaEvent *iface, REFIID riid, void **ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, &IID_IWineUiaEvent) || IsEqualIID(riid, &IID_IUnknown))
*ppv = iface;
else
return E_NOINTERFACE;
IWineUiaEvent_AddRef(iface);
return S_OK;
}
static ULONG WINAPI uia_event_AddRef(IWineUiaEvent *iface)
{
struct uia_event *event = impl_from_IWineUiaEvent(iface);
ULONG ref = InterlockedIncrement(&event->ref);
TRACE("%p, refcount %ld\n", event, ref);
return ref;
}
static ULONG WINAPI uia_event_Release(IWineUiaEvent *iface)
{
struct uia_event *event = impl_from_IWineUiaEvent(iface);
ULONG ref = InterlockedDecrement(&event->ref);
TRACE("%p, refcount %ld\n", event, ref);
if (!ref)
{
int i;
/*
* If this event has an event_map_entry, it should've been released
* before hitting a reference count of 0.
*/
assert(!event->event_map_entry);
SafeArrayDestroy(event->runtime_id);
if (event->event_type == EVENT_TYPE_CLIENTSIDE)
{
uia_cache_request_destroy(&event->u.clientside.cache_req);
if (event->u.clientside.event_thread_started)
uia_stop_event_thread();
uia_hwnd_map_destroy(&event->u.clientside.win_event_hwnd_map);
}
else
{
EnterCriticalSection(&event_map_cs);
rb_remove(&uia_event_map.serverside_event_map, &event->u.serverside.serverside_event_entry);
uia_event_map.serverside_event_count--;
LeaveCriticalSection(&event_map_cs);
if (event->u.serverside.event_iface)
IWineUiaEvent_Release(event->u.serverside.event_iface);
uia_stop_event_thread();
}
for (i = 0; i < event->event_advisers_count; i++)
IWineUiaEventAdviser_Release(event->event_advisers[i]);
free(event->event_advisers);
free(event);
}
return ref;
}
static HRESULT WINAPI uia_event_advise_events(IWineUiaEvent *iface, BOOL advise_added, LONG adviser_start_idx)
{
struct uia_event *event = impl_from_IWineUiaEvent(iface);
HRESULT hr;
int i;
TRACE("%p, %d, %ld\n", event, advise_added, adviser_start_idx);
for (i = adviser_start_idx; i < event->event_advisers_count; i++)
{
hr = IWineUiaEventAdviser_advise(event->event_advisers[i], advise_added, (UINT_PTR)event);
if (FAILED(hr))
return hr;
}
/*
* First call to advise events on a serverside provider, add it to the
* events list so it can be raised.
*/
if (!adviser_start_idx && advise_added && event->event_type == EVENT_TYPE_SERVERSIDE)
{
hr = uia_event_map_add_event(event);
if (FAILED(hr))
WARN("Failed to add event to event map, hr %#lx\n", hr);
}
/*
* Once we've advised of removal, no need to keep the advisers around.
* We can also release our reference to the event map.
*/
if (!advise_added)
{
InterlockedIncrement(&event->event_defunct);
uia_event_map_entry_release(event->event_map_entry);
event->event_map_entry = NULL;
for (i = 0; i < event->event_advisers_count; i++)
IWineUiaEventAdviser_Release(event->event_advisers[i]);
free(event->event_advisers);
event->event_advisers_count = event->event_advisers_arr_size = 0;
}
return S_OK;
}
static HRESULT WINAPI uia_event_set_event_data(IWineUiaEvent *iface, const GUID *event_guid, LONG scope,
VARIANT runtime_id, IWineUiaEvent *event_iface)
{
struct uia_event *event = impl_from_IWineUiaEvent(iface);
TRACE("%p, %s, %ld, %s, %p\n", event, debugstr_guid(event_guid), scope, debugstr_variant(&runtime_id), event_iface);
assert(event->event_type == EVENT_TYPE_SERVERSIDE);
event->event_id = UiaLookupId(AutomationIdentifierType_Event, event_guid);
event->scope = scope;
if (V_VT(&runtime_id) == (VT_I4 | VT_ARRAY))
{
HRESULT hr;
hr = SafeArrayCopy(V_ARRAY(&runtime_id), &event->runtime_id);
if (FAILED(hr))
{
WARN("Failed to copy runtime id, hr %#lx\n", hr);
return hr;
}
}
event->u.serverside.event_iface = event_iface;
IWineUiaEvent_AddRef(event_iface);
return S_OK;
}
static HRESULT WINAPI uia_event_raise_event(IWineUiaEvent *iface, VARIANT in_node, VARIANT in_nav_start_node)
{
struct uia_event *event = impl_from_IWineUiaEvent(iface);
struct uia_queue_uia_event *queue_event;
struct uia_event_args *args;
TRACE("%p, %s, %s\n", iface, debugstr_variant(&in_node), debugstr_variant(&in_nav_start_node));
assert(event->event_type != EVENT_TYPE_SERVERSIDE);
if (!(queue_event = calloc(1, sizeof(*queue_event))))
return E_OUTOFMEMORY;
if (!(args = create_uia_event_args(uia_event_info_from_id(event->event_id))))
{
free(queue_event);
return E_OUTOFMEMORY;
}
queue_event->args = args;
queue_event->event = event;
queue_event->u.clientside.node = V_I4(&in_node);
if (V_VT(&in_nav_start_node) == VT_I4)
queue_event->u.clientside.nav_start_node = V_I4(&in_nav_start_node);
IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface);
uia_event_queue_push(&queue_event->queue_entry, QUEUE_EVENT_TYPE_CLIENTSIDE);
return S_OK;
}
static const IWineUiaEventVtbl uia_event_vtbl = {
uia_event_QueryInterface,
uia_event_AddRef,
uia_event_Release,
uia_event_advise_events,
uia_event_set_event_data,
uia_event_raise_event,
};
static struct uia_event *unsafe_impl_from_IWineUiaEvent(IWineUiaEvent *iface)
{
if (!iface || (iface->lpVtbl != &uia_event_vtbl))
return NULL;
return CONTAINING_RECORD(iface, struct uia_event, IWineUiaEvent_iface);
}
static HRESULT create_uia_event(struct uia_event **out_event, LONG event_cookie, int event_type)
{
struct uia_event *event = calloc(1, sizeof(*event));
*out_event = NULL;
if (!event)
return E_OUTOFMEMORY;
event->IWineUiaEvent_iface.lpVtbl = &uia_event_vtbl;
event->ref = 1;
event->event_cookie = event_cookie;
event->event_type = event_type;
*out_event = event;
return S_OK;
}
static HRESULT create_clientside_uia_event(struct uia_event **out_event, int event_id, int scope,
UiaWineEventCallback *cback, void *cback_data, SAFEARRAY *runtime_id)
{
struct uia_event *event = NULL;
static LONG next_event_cookie;
HRESULT hr;
*out_event = NULL;
hr = create_uia_event(&event, InterlockedIncrement(&next_event_cookie), EVENT_TYPE_CLIENTSIDE);
if (FAILED(hr))
return hr;
event->runtime_id = runtime_id;
event->event_id = event_id;
event->scope = scope;
event->u.clientside.event_callback = cback;
event->u.clientside.callback_data = cback_data;
uia_hwnd_map_init(&event->u.clientside.win_event_hwnd_map);
*out_event = event;
return S_OK;
}
HRESULT create_serverside_uia_event(struct uia_event **out_event, LONG process_id, LONG event_cookie)
{
struct uia_event_identifier event_identifier = { event_cookie, process_id };
struct rb_entry *rb_entry;
struct uia_event *event;
HRESULT hr = S_OK;
/*
* Attempt to lookup an existing event for this PID/event_cookie. If there
* is one, return S_FALSE.
*/
*out_event = NULL;
EnterCriticalSection(&event_map_cs);
if (uia_event_map.serverside_event_count && (rb_entry = rb_get(&uia_event_map.serverside_event_map, &event_identifier)))
{
*out_event = RB_ENTRY_VALUE(rb_entry, struct uia_event, u.serverside.serverside_event_entry);
hr = S_FALSE;
goto exit;
}
hr = create_uia_event(&event, event_cookie, EVENT_TYPE_SERVERSIDE);
if (FAILED(hr))
goto exit;
if (!uia_start_event_thread())
{
free(event);
hr = E_FAIL;
goto exit;
}
event->u.serverside.proc_id = process_id;
uia_event_map.serverside_event_count++;
if (uia_event_map.serverside_event_count == 1)
rb_init(&uia_event_map.serverside_event_map, uia_serverside_event_id_compare);
rb_put(&uia_event_map.serverside_event_map, &event_identifier, &event->u.serverside.serverside_event_entry);
*out_event = event;
exit:
LeaveCriticalSection(&event_map_cs);
return hr;
}
static HRESULT uia_event_add_event_adviser(IWineUiaEventAdviser *adviser, struct uia_event *event)
{
if (!uia_array_reserve((void **)&event->event_advisers, &event->event_advisers_arr_size,
event->event_advisers_count + 1, sizeof(*event->event_advisers)))
return E_OUTOFMEMORY;
event->event_advisers[event->event_advisers_count] = adviser;
IWineUiaEventAdviser_AddRef(adviser);
event->event_advisers_count++;
return S_OK;
}
/*
* IWineUiaEventAdviser interface.
*/
struct uia_event_adviser {
IWineUiaEventAdviser IWineUiaEventAdviser_iface;
LONG ref;
IRawElementProviderAdviseEvents *advise_events;
DWORD git_cookie;
};
static inline struct uia_event_adviser *impl_from_IWineUiaEventAdviser(IWineUiaEventAdviser *iface)
{
return CONTAINING_RECORD(iface, struct uia_event_adviser, IWineUiaEventAdviser_iface);
}
static HRESULT WINAPI uia_event_adviser_QueryInterface(IWineUiaEventAdviser *iface, REFIID riid, void **ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, &IID_IWineUiaEventAdviser) || IsEqualIID(riid, &IID_IUnknown))
*ppv = iface;
else
return E_NOINTERFACE;
IWineUiaEventAdviser_AddRef(iface);
return S_OK;
}
static ULONG WINAPI uia_event_adviser_AddRef(IWineUiaEventAdviser *iface)
{
struct uia_event_adviser *adv_events = impl_from_IWineUiaEventAdviser(iface);
ULONG ref = InterlockedIncrement(&adv_events->ref);
TRACE("%p, refcount %ld\n", adv_events, ref);
return ref;
}
static ULONG WINAPI uia_event_adviser_Release(IWineUiaEventAdviser *iface)
{
struct uia_event_adviser *adv_events = impl_from_IWineUiaEventAdviser(iface);
ULONG ref = InterlockedDecrement(&adv_events->ref);
TRACE("%p, refcount %ld\n", adv_events, ref);
if (!ref)
{
if (adv_events->git_cookie)
{
if (FAILED(unregister_interface_in_git(adv_events->git_cookie)))
WARN("Failed to revoke advise events interface from GIT\n");
}
IRawElementProviderAdviseEvents_Release(adv_events->advise_events);
free(adv_events);
}
return ref;
}
static HRESULT WINAPI uia_event_adviser_advise(IWineUiaEventAdviser *iface, BOOL advise_added, LONG_PTR huiaevent)
{
struct uia_event_adviser *adv_events = impl_from_IWineUiaEventAdviser(iface);
struct uia_event *event_data = (struct uia_event *)huiaevent;
IRawElementProviderAdviseEvents *advise_events;
HRESULT hr;
TRACE("%p, %d, %#Ix\n", adv_events, advise_added, huiaevent);
if (adv_events->git_cookie)
{
hr = get_interface_in_git(&IID_IRawElementProviderAdviseEvents, adv_events->git_cookie,
(IUnknown **)&advise_events);
if (FAILED(hr))
return hr;
}
else
{
advise_events = adv_events->advise_events;
IRawElementProviderAdviseEvents_AddRef(advise_events);
}
if (advise_added)
hr = IRawElementProviderAdviseEvents_AdviseEventAdded(advise_events, event_data->event_id, NULL);
else
hr = IRawElementProviderAdviseEvents_AdviseEventRemoved(advise_events, event_data->event_id, NULL);
IRawElementProviderAdviseEvents_Release(advise_events);
return hr;
}
static const IWineUiaEventAdviserVtbl uia_event_adviser_vtbl = {
uia_event_adviser_QueryInterface,
uia_event_adviser_AddRef,
uia_event_adviser_Release,
uia_event_adviser_advise,
};
HRESULT uia_event_add_provider_event_adviser(IRawElementProviderAdviseEvents *advise_events, struct uia_event *event)
{
struct uia_event_adviser *adv_events;
IRawElementProviderSimple *elprov;
enum ProviderOptions prov_opts;
HRESULT hr;
hr = IRawElementProviderAdviseEvents_QueryInterface(advise_events, &IID_IRawElementProviderSimple,
(void **)&elprov);
if (FAILED(hr))
{
ERR("Failed to get IRawElementProviderSimple from advise events\n");
return E_FAIL;
}
hr = IRawElementProviderSimple_get_ProviderOptions(elprov, &prov_opts);
IRawElementProviderSimple_Release(elprov);
if (FAILED(hr))
return hr;
if (!(adv_events = calloc(1, sizeof(*adv_events))))
return E_OUTOFMEMORY;
if (prov_opts & ProviderOptions_UseComThreading)
{
hr = register_interface_in_git((IUnknown *)advise_events, &IID_IRawElementProviderAdviseEvents,
&adv_events->git_cookie);
if (FAILED(hr))
{
free(adv_events);
return hr;
}
}
adv_events->IWineUiaEventAdviser_iface.lpVtbl = &uia_event_adviser_vtbl;
adv_events->ref = 1;
adv_events->advise_events = advise_events;
IRawElementProviderAdviseEvents_AddRef(advise_events);
hr = uia_event_add_event_adviser(&adv_events->IWineUiaEventAdviser_iface, event);
IWineUiaEventAdviser_Release(&adv_events->IWineUiaEventAdviser_iface);
return hr;
}
/*
* IWineUiaEventAdviser interface for serverside events.
*/
struct uia_serverside_event_adviser {
IWineUiaEventAdviser IWineUiaEventAdviser_iface;
LONG ref;
IWineUiaEvent *event_iface;
};
static inline struct uia_serverside_event_adviser *impl_from_serverside_IWineUiaEventAdviser(IWineUiaEventAdviser *iface)
{
return CONTAINING_RECORD(iface, struct uia_serverside_event_adviser, IWineUiaEventAdviser_iface);
}
static HRESULT WINAPI uia_serverside_event_adviser_QueryInterface(IWineUiaEventAdviser *iface, REFIID riid, void **ppv)
{
*ppv = NULL;
if (IsEqualIID(riid, &IID_IWineUiaEventAdviser) || IsEqualIID(riid, &IID_IUnknown))
*ppv = iface;
else
return E_NOINTERFACE;
IWineUiaEventAdviser_AddRef(iface);
return S_OK;
}
static ULONG WINAPI uia_serverside_event_adviser_AddRef(IWineUiaEventAdviser *iface)
{
struct uia_serverside_event_adviser *adv_events = impl_from_serverside_IWineUiaEventAdviser(iface);
ULONG ref = InterlockedIncrement(&adv_events->ref);
TRACE("%p, refcount %ld\n", adv_events, ref);
return ref;
}
static ULONG WINAPI uia_serverside_event_adviser_Release(IWineUiaEventAdviser *iface)
{
struct uia_serverside_event_adviser *adv_events = impl_from_serverside_IWineUiaEventAdviser(iface);
ULONG ref = InterlockedDecrement(&adv_events->ref);
TRACE("%p, refcount %ld\n", adv_events, ref);
if (!ref)
{
IWineUiaEvent_Release(adv_events->event_iface);
free(adv_events);
}
return ref;
}
static HRESULT WINAPI uia_serverside_event_adviser_advise(IWineUiaEventAdviser *iface, BOOL advise_added, LONG_PTR huiaevent)
{
struct uia_serverside_event_adviser *adv_events = impl_from_serverside_IWineUiaEventAdviser(iface);
struct uia_event *event_data = (struct uia_event *)huiaevent;
HRESULT hr;
TRACE("%p, %d, %#Ix\n", adv_events, advise_added, huiaevent);
if (advise_added)
{
const struct uia_event_info *event_info = uia_event_info_from_id(event_data->event_id);
VARIANT v;
VariantInit(&v);
if (event_data->runtime_id)
{
V_VT(&v) = VT_I4 | VT_ARRAY;
V_ARRAY(&v) = event_data->runtime_id;
}
hr = IWineUiaEvent_set_event_data(adv_events->event_iface, event_info->guid, event_data->scope, v,
&event_data->IWineUiaEvent_iface);
if (FAILED(hr))
{
WARN("Failed to set event data on serverside event, hr %#lx\n", hr);
return hr;
}
}
return IWineUiaEvent_advise_events(adv_events->event_iface, advise_added, 0);
}
static const IWineUiaEventAdviserVtbl uia_serverside_event_adviser_vtbl = {
uia_serverside_event_adviser_QueryInterface,
uia_serverside_event_adviser_AddRef,
uia_serverside_event_adviser_Release,
uia_serverside_event_adviser_advise,
};
HRESULT uia_event_add_serverside_event_adviser(IWineUiaEvent *serverside_event, struct uia_event *event)
{
struct uia_serverside_event_adviser *adv_events;
HRESULT hr;
/*
* Need to create a proxy IWineUiaEvent for our clientside event to use
* this serverside IWineUiaEvent proxy from the appropriate apartment.
*/
if (!event->u.clientside.git_cookie)
{
if (!uia_clientside_event_start_event_thread(event))
return E_FAIL;
hr = register_interface_in_git((IUnknown *)&event->IWineUiaEvent_iface, &IID_IWineUiaEvent,
&event->u.clientside.git_cookie);
if (FAILED(hr))
return hr;
}
if (!(adv_events = calloc(1, sizeof(*adv_events))))
return E_OUTOFMEMORY;
adv_events->IWineUiaEventAdviser_iface.lpVtbl = &uia_serverside_event_adviser_vtbl;
adv_events->ref = 1;
adv_events->event_iface = serverside_event;
IWineUiaEvent_AddRef(serverside_event);
hr = uia_event_add_event_adviser(&adv_events->IWineUiaEventAdviser_iface, event);
IWineUiaEventAdviser_Release(&adv_events->IWineUiaEventAdviser_iface);
return hr;
}
static HRESULT uia_event_advise(struct uia_event *event, BOOL advise_added, LONG start_idx)
{
IWineUiaEvent *event_iface;
HRESULT hr;
if (event->u.clientside.git_cookie)
{
hr = get_interface_in_git(&IID_IWineUiaEvent, event->u.clientside.git_cookie,
(IUnknown **)&event_iface);
if (FAILED(hr))
return hr;
}
else
{
event_iface = &event->IWineUiaEvent_iface;
IWineUiaEvent_AddRef(event_iface);
}
hr = IWineUiaEvent_advise_events(event_iface, advise_added, start_idx);
IWineUiaEvent_Release(event_iface);
return hr;
}
HRESULT uia_event_advise_node(struct uia_event *event, HUIANODE node)
{
int old_event_advisers_count = event->event_advisers_count;
HRESULT hr;
hr = attach_event_to_uia_node(node, event);
if (FAILED(hr))
return hr;
if (event->event_advisers_count != old_event_advisers_count)
hr = uia_event_advise(event, TRUE, old_event_advisers_count);
return hr;
}
/***********************************************************************
* UiaEventAddWindow (uiautomationcore.@)
*/
HRESULT WINAPI UiaEventAddWindow(HUIAEVENT huiaevent, HWND hwnd)
{
struct uia_event *event = unsafe_impl_from_IWineUiaEvent((IWineUiaEvent *)huiaevent);
HUIANODE node;
HRESULT hr;
TRACE("(%p, %p)\n", huiaevent, hwnd);
if (!event)
return E_INVALIDARG;
assert(event->event_type == EVENT_TYPE_CLIENTSIDE);
hr = UiaNodeFromHandle(hwnd, &node);
if (FAILED(hr))
return hr;
hr = uia_event_advise_node(event, node);
UiaNodeRelease(node);
return hr;
}
static HRESULT uia_clientside_event_callback(struct uia_event *event, struct uia_event_args *args,
SAFEARRAY *cache_req, BSTR tree_struct)
{
UiaEventCallback *event_callback = (UiaEventCallback *)event->u.clientside.callback_data;
event_callback(&args->simple_args, cache_req, tree_struct);
return S_OK;
}
HRESULT uia_add_clientside_event(HUIANODE huianode, EVENTID event_id, enum TreeScope scope, PROPERTYID *prop_ids,
int prop_ids_count, struct UiaCacheRequest *cache_req, SAFEARRAY *rt_id, UiaWineEventCallback *cback,
void *cback_data, HUIAEVENT *huiaevent)
{
struct uia_event *event;
SAFEARRAY *sa;
HRESULT hr;
hr = SafeArrayCopy(rt_id, &sa);
if (FAILED(hr))
return hr;
hr = create_clientside_uia_event(&event, event_id, scope, cback, cback_data, sa);
if (FAILED(hr))
{
SafeArrayDestroy(sa);
return hr;
}
hr = uia_cache_request_clone(&event->u.clientside.cache_req, cache_req);
if (FAILED(hr))
goto exit;
hr = attach_event_to_uia_node(huianode, event);
if (FAILED(hr))
goto exit;
hr = uia_event_advise(event, TRUE, 0);
if (FAILED(hr))
goto exit;
hr = uia_event_map_add_event(event);
if (FAILED(hr))
goto exit;
*huiaevent = (HUIAEVENT)event;
exit:
if (FAILED(hr))
IWineUiaEvent_Release(&event->IWineUiaEvent_iface);
return hr;
}
/***********************************************************************
* UiaAddEvent (uiautomationcore.@)
*/
HRESULT WINAPI UiaAddEvent(HUIANODE huianode, EVENTID event_id, UiaEventCallback *callback, enum TreeScope scope,
PROPERTYID *prop_ids, int prop_ids_count, struct UiaCacheRequest *cache_req, HUIAEVENT *huiaevent)
{
const struct uia_event_info *event_info = uia_event_info_from_id(event_id);
SAFEARRAY *sa;
HRESULT hr;
TRACE("(%p, %d, %p, %#x, %p, %d, %p, %p)\n", huianode, event_id, callback, scope, prop_ids, prop_ids_count,
cache_req, huiaevent);
if (!huianode || !callback || !cache_req || !huiaevent)
return E_INVALIDARG;
if (!event_info)
WARN("No event information for event ID %d\n", event_id);
*huiaevent = NULL;
if (event_info && (event_info->event_arg_type == EventArgsType_PropertyChanged))
{
FIXME("Property changed event registration currently unimplemented\n");
return E_NOTIMPL;
}
hr = UiaGetRuntimeId(huianode, &sa);
if (FAILED(hr))
return hr;
hr = uia_add_clientside_event(huianode, event_id, scope, prop_ids, prop_ids_count, cache_req, sa,
uia_clientside_event_callback, (void *)callback, huiaevent);
SafeArrayDestroy(sa);
return hr;
}
/***********************************************************************
* UiaRemoveEvent (uiautomationcore.@)
*/
HRESULT WINAPI UiaRemoveEvent(HUIAEVENT huiaevent)
{
struct uia_event *event = unsafe_impl_from_IWineUiaEvent((IWineUiaEvent *)huiaevent);
HRESULT hr;
TRACE("(%p)\n", event);
if (!event)
return E_INVALIDARG;
assert(event->event_type == EVENT_TYPE_CLIENTSIDE);
hr = uia_event_advise(event, FALSE, 0);
if (FAILED(hr))
return hr;
if (event->u.clientside.git_cookie)
{
hr = unregister_interface_in_git(event->u.clientside.git_cookie);
if (FAILED(hr))
return hr;
}
IWineUiaEvent_Release(&event->IWineUiaEvent_iface);
return S_OK;
}
HRESULT uia_event_invoke(HUIANODE node, HUIANODE nav_start_node, struct uia_event_args *args, struct uia_event *event)
{
HRESULT hr = S_OK;
if (event->event_type == EVENT_TYPE_CLIENTSIDE)
{
SAFEARRAY *out_req;
BSTR tree_struct;
if (nav_start_node && (hr = uia_event_check_node_within_event_scope(event, nav_start_node, NULL, NULL)) != S_OK)
return hr;
hr = UiaGetUpdatedCache(node, &event->u.clientside.cache_req, NormalizeState_View, NULL, &out_req,
&tree_struct);
if (SUCCEEDED(hr))
{
hr = event->u.clientside.event_callback(event, args, out_req, tree_struct);
if (FAILED(hr))
WARN("Event callback failed with hr %#lx\n", hr);
SafeArrayDestroy(out_req);
SysFreeString(tree_struct);
}
}
else
{
struct uia_queue_uia_event *queue_event;
HUIANODE node2, nav_start_node2;
if (!(queue_event = calloc(1, sizeof(*queue_event))))
return E_OUTOFMEMORY;
node2 = nav_start_node2 = NULL;
hr = clone_uia_node(node, &node2);
if (FAILED(hr))
{
free(queue_event);
return hr;
}
if (nav_start_node)
{
hr = clone_uia_node(nav_start_node, &nav_start_node2);
if (FAILED(hr))
{
free(queue_event);
UiaNodeRelease(node2);
return hr;
}
}
queue_event->args = args;
queue_event->event = event;
queue_event->u.serverside.node = node2;
queue_event->u.serverside.nav_start_node = nav_start_node2;
InterlockedIncrement(&args->ref);
IWineUiaEvent_AddRef(&event->IWineUiaEvent_iface);
uia_event_queue_push(&queue_event->queue_entry, QUEUE_EVENT_TYPE_SERVERSIDE);
}
return hr;
}
static void set_refuse_hwnd_providers(struct uia_node *node, BOOL refuse_hwnd_providers)
{
struct uia_provider *prov_data = impl_from_IWineUiaProvider(node->prov[get_node_provider_type_at_idx(node, 0)]);
prov_data->refuse_hwnd_node_providers = refuse_hwnd_providers;
}
/*
* Check if a node is within the scope of a registered event.
* If it is, return S_OK.
* If it isn't, return S_FALSE.
* Upon failure, return a failure HR.
*/
HRESULT uia_event_check_node_within_event_scope(struct uia_event *event, HUIANODE node, SAFEARRAY *rt_id,
HUIANODE *clientside_nav_node_out)
{
struct UiaPropertyCondition prop_cond = { ConditionType_Property, UIA_RuntimeIdPropertyId };
struct uia_node *node_data = impl_from_IWineUiaNode((IWineUiaNode *)node);
BOOL in_scope = FALSE;
HRESULT hr = S_FALSE;
if (clientside_nav_node_out)
*clientside_nav_node_out = NULL;
if (event->event_type == EVENT_TYPE_SERVERSIDE)
assert(clientside_nav_node_out);
/* Event is no longer valid. */
if (InterlockedCompareExchange(&event->event_defunct, 0, 0) != 0)
return S_FALSE;
/* Can't match an event that doesn't have a runtime ID, early out. */
if (!event->runtime_id)
return S_FALSE;
if (event->desktop_subtree_event)
return S_OK;
if (rt_id && !uia_compare_safearrays(rt_id, event->runtime_id, UIAutomationType_IntArray))
return (event->scope & TreeScope_Element) ? S_OK : S_FALSE;
if (!(event->scope & (TreeScope_Descendants | TreeScope_Children)))
return S_FALSE;
V_VT(&prop_cond.Value) = VT_I4 | VT_ARRAY;
V_ARRAY(&prop_cond.Value) = event->runtime_id;
IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface);
while (1)
{
HUIANODE node2 = NULL;
/*
* When trying to match serverside events through navigation, we
* don't want any clientside providers added in the server process.
* Once we encounter a provider with an HWND, we pass it off to the
* client for any further navigation.
*/
if (event->event_type == EVENT_TYPE_SERVERSIDE)
{
if (node_data->hwnd)
{
*clientside_nav_node_out = (HUIANODE)&node_data->IWineUiaNode_iface;
IWineUiaNode_AddRef(&node_data->IWineUiaNode_iface);
in_scope = TRUE;
break;
}
set_refuse_hwnd_providers(node_data, TRUE);
}
hr = navigate_uia_node(node_data, NavigateDirection_Parent, &node2);
if (FAILED(hr) || !node2)
break;
IWineUiaNode_Release(&node_data->IWineUiaNode_iface);
node_data = impl_from_IWineUiaNode((IWineUiaNode *)node2);
hr = uia_condition_check(node2, (struct UiaCondition *)&prop_cond);
if (FAILED(hr))
break;
if (uia_condition_matched(hr))
{
in_scope = TRUE;
break;
}
if (!(event->scope & TreeScope_Descendants))
break;
}
IWineUiaNode_Release(&node_data->IWineUiaNode_iface);
if (FAILED(hr))
return hr;
return in_scope ? S_OK : S_FALSE;
}
static HRESULT uia_raise_elprov_event_callback(struct uia_event *event, void *data)
{
struct uia_elprov_event_data *event_data = (struct uia_elprov_event_data *)data;
HUIANODE nav_node = NULL;
HRESULT hr = S_OK;
if (!event_data->node)
{
/*
* For events raised on server-side providers, we don't want to add any
* clientside HWND providers.
*/
hr = create_uia_node_from_elprov(event_data->elprov, &event_data->node, event_data->clientside_only, 0);
if (FAILED(hr))
return hr;
hr = UiaGetRuntimeId(event_data->node, &event_data->rt_id);
if (FAILED(hr))
return hr;
}
hr = uia_event_check_node_within_event_scope(event, event_data->node, event_data->rt_id, &nav_node);
if (hr == S_OK)
hr = uia_event_invoke(event_data->node, nav_node, event_data->args, event);
UiaNodeRelease(nav_node);
return hr;
}
static HRESULT uia_raise_elprov_event(IRawElementProviderSimple *elprov, struct uia_event_args *args)
{
struct uia_elprov_event_data event_data = { elprov, args };
enum ProviderOptions prov_opts = 0;
HRESULT hr;
hr = IRawElementProviderSimple_get_ProviderOptions(elprov, &prov_opts);
if (FAILED(hr))
return hr;
event_data.clientside_only = !(prov_opts & ProviderOptions_ServerSideProvider);
hr = uia_event_for_each(args->simple_args.EventId, uia_raise_elprov_event_callback, (void *)&event_data,
event_data.clientside_only);
if (FAILED(hr))
WARN("uia_event_for_each failed with hr %#lx\n", hr);
UiaNodeRelease(event_data.node);
SafeArrayDestroy(event_data.rt_id);
return hr;
}
/***********************************************************************
* UiaRaiseAutomationEvent (uiautomationcore.@)
*/
HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *elprov, EVENTID id)
{
const struct uia_event_info *event_info = uia_event_info_from_id(id);
struct uia_event_args *args;
HRESULT hr;
TRACE("(%p, %d)\n", elprov, id);
if (!elprov)
return E_INVALIDARG;
if (!event_info || event_info->event_arg_type != EventArgsType_Simple)
{
if (!event_info)
FIXME("No event info structure for event id %d\n", id);
else
WARN("Wrong event raising function for event args type %d\n", event_info->event_arg_type);
return S_OK;
}
args = create_uia_event_args(event_info);
if (!args)
return E_OUTOFMEMORY;
hr = uia_raise_elprov_event(elprov, args);
uia_event_args_release(args);
if (FAILED(hr))
return hr;
return S_OK;
}