/* * 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; }