-- v2: comctl32/tests: Test MSAA events for SysLink. comctl32/tests: Test SetWindowText and LM_GETITEM for SysLink. comctl32/tests: Test accLocation values on SysLink control. comctl32: Implement EVENT_OBJECT_NAMECHANGE for SysLink controls. comctl32: Implement accDoDefaultAction for SysLink controls. comctl32/tests: Add test for SysLink accDoDefaultAction.
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/syslink.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+)
diff --git a/dlls/comctl32/tests/syslink.c b/dlls/comctl32/tests/syslink.c index c25263f6f97..fa906e9c100 100644 --- a/dlls/comctl32/tests/syslink.c +++ b/dlls/comctl32/tests/syslink.c @@ -279,6 +279,21 @@ static void test_link_id(void) DestroyWindow(hwnd); }
+static void wait_link_click(DWORD timeout) +{ + DWORD start_time = GetTickCount(); + DWORD time_waited; + + if (g_link_id == -1) + flush_events(); + + while (g_link_id == -1 && (time_waited = GetTickCount() - start_time) < timeout) + { + MsgWaitForMultipleObjects(0, NULL, FALSE, timeout - time_waited, QS_ALLEVENTS); + flush_events(); + } +} + static void test_msaa(void) { HWND hwnd, ret_hwnd; @@ -335,6 +350,9 @@ static void test_msaa(void) if (SUCCEEDED(hr)) ok(!name, "unexpected default action %s\n", debugstr_w(name));
+ hr = IAccessible_accDoDefaultAction(acc, varChild); + todo_wine ok(hr == E_INVALIDARG, "accDoDefaultAction should fail, hr=%lx\n", hr); + hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr);
@@ -383,6 +401,12 @@ static void test_msaa(void) SysFreeString(name); }
+ g_link_id = -1; + hr = IAccessible_accDoDefaultAction(acc, varChild); + todo_wine ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); + wait_link_click(500); + todo_wine ok(g_link_id == 0, "Got unexpected link id %d.\n", g_link_id); + hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr);
@@ -427,6 +451,12 @@ static void test_msaa(void) SysFreeString(name); }
+ g_link_id = -1; + hr = IAccessible_accDoDefaultAction(acc, varChild); + todo_wine ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); + wait_link_click(500); + todo_wine ok(g_link_id == 1, "Got unexpected link id %d.\n", g_link_id); + hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr);
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/syslink.c | 21 +++++++++++++++++++-- dlls/comctl32/tests/syslink.c | 10 +++++----- 2 files changed, 24 insertions(+), 7 deletions(-)
diff --git a/dlls/comctl32/syslink.c b/dlls/comctl32/syslink.c index d0bfaf164fc..0880c0da527 100644 --- a/dlls/comctl32/syslink.c +++ b/dlls/comctl32/syslink.c @@ -529,10 +529,27 @@ static HRESULT WINAPI Accessible_accHitTest(IAccessible *iface, LONG left, LONG return E_NOTIMPL; }
+static LRESULT SYSLINK_SendParentNotify (const SYSLINK_INFO *infoPtr, UINT code, const DOC_ITEM *Link, int iLink); + static HRESULT WINAPI Accessible_accDoDefaultAction(IAccessible *iface, VARIANT childid) { - FIXME("%p\n", iface); - return E_NOTIMPL; + SYSLINK_ACC *This = impl_from_IAccessible(iface); + HRESULT hr; + DOC_ITEM* item; + + TRACE("%p, %s\n", iface, debugstr_variant(&childid)); + + hr = Accessible_FindChild(This, childid, &item); + if (FAILED(hr)) + return hr; + + if (!item) + /* Not supported for whole control. */ + return E_INVALIDARG; + + SYSLINK_SendParentNotify(This->infoPtr, NM_CLICK, item, V_I4(&childid) - 1); + + return S_OK; }
static HRESULT WINAPI Accessible_put_accName(IAccessible *iface, VARIANT childid, BSTR name) diff --git a/dlls/comctl32/tests/syslink.c b/dlls/comctl32/tests/syslink.c index fa906e9c100..cf9d47c87e7 100644 --- a/dlls/comctl32/tests/syslink.c +++ b/dlls/comctl32/tests/syslink.c @@ -351,7 +351,7 @@ static void test_msaa(void) ok(!name, "unexpected default action %s\n", debugstr_w(name));
hr = IAccessible_accDoDefaultAction(acc, varChild); - todo_wine ok(hr == E_INVALIDARG, "accDoDefaultAction should fail, hr=%lx\n", hr); + ok(hr == E_INVALIDARG, "accDoDefaultAction should fail, hr=%lx\n", hr);
hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr); @@ -403,9 +403,9 @@ static void test_msaa(void)
g_link_id = -1; hr = IAccessible_accDoDefaultAction(acc, varChild); - todo_wine ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); + ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); wait_link_click(500); - todo_wine ok(g_link_id == 0, "Got unexpected link id %d.\n", g_link_id); + ok(g_link_id == 0, "Got unexpected link id %d.\n", g_link_id);
hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr); @@ -453,9 +453,9 @@ static void test_msaa(void)
g_link_id = -1; hr = IAccessible_accDoDefaultAction(acc, varChild); - todo_wine ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); + ok(hr == S_OK, "accDoDefaultAction failed, hr=%lx\n", hr); wait_link_click(500); - todo_wine ok(g_link_id == 1, "Got unexpected link id %d.\n", g_link_id); + ok(g_link_id == 1, "Got unexpected link id %d.\n", g_link_id);
hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr);
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/syslink.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/comctl32/syslink.c b/dlls/comctl32/syslink.c index 0880c0da527..b3bc114186e 100644 --- a/dlls/comctl32/syslink.c +++ b/dlls/comctl32/syslink.c @@ -2132,6 +2132,7 @@ static LRESULT WINAPI SysLinkWindowProc(HWND hwnd, UINT message,
case WM_SETTEXT: SYSLINK_SetText(infoPtr, (LPWSTR)lParam); + NotifyWinEvent(EVENT_OBJECT_NAMECHANGE, infoPtr->Self, OBJID_CLIENT, 0); return DefWindowProcW(hwnd, message, wParam, lParam);
case WM_LBUTTONDOWN:
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/syslink.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/dlls/comctl32/tests/syslink.c b/dlls/comctl32/tests/syslink.c index cf9d47c87e7..ce46099d8ed 100644 --- a/dlls/comctl32/tests/syslink.c +++ b/dlls/comctl32/tests/syslink.c @@ -302,7 +302,7 @@ static void test_msaa(void) IAccessible *acc; VARIANT varChild, varResult; BSTR name; - LONG left, top, width, height, count=0; + LONG left, top, width, height, hwnd_left, hwnd_top, count=0; IDispatch *child; IOleWindow *ole_window;
@@ -355,6 +355,8 @@ static void test_msaa(void)
hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr); + hwnd_left = left; + hwnd_top = top;
hr = IAccessible_get_accChildCount(acc, &count); ok(hr == S_OK, "accChildCount failed, hr=%lx\n", hr); @@ -407,8 +409,12 @@ static void test_msaa(void) wait_link_click(500); ok(g_link_id == 0, "Got unexpected link id %d.\n", g_link_id);
+ g_link_id = -1; hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr); + SendMessageA(hwnd, WM_LBUTTONDOWN, 1, MAKELPARAM(left - hwnd_left + width / 2, top - hwnd_top + height / 2)); + SendMessageA(hwnd, WM_LBUTTONUP, 0, MAKELPARAM(left - hwnd_left + width / 2, top - hwnd_top + height / 2)); + ok(g_link_id == 0, "Got unexpected link id %d.\n", g_link_id);
/* child 2 */ V_I4(&varChild) = 2; @@ -457,8 +463,12 @@ static void test_msaa(void) wait_link_click(500); ok(g_link_id == 1, "Got unexpected link id %d.\n", g_link_id);
+ g_link_id = -1; hr = IAccessible_accLocation(acc, &left, &top, &width, &height, varChild); ok(hr == S_OK, "accLocation failed, hr=%lx\n", hr); + SendMessageA(hwnd, WM_LBUTTONDOWN, 1, MAKELPARAM(left - hwnd_left + width / 2, top - hwnd_top + height / 2)); + SendMessageA(hwnd, WM_LBUTTONUP, 0, MAKELPARAM(left - hwnd_left + width / 2, top - hwnd_top + height / 2)); + ok(g_link_id == 1, "Got unexpected link id %d.\n", g_link_id);
hr = IAccessible_QueryInterface(acc, &IID_IOleWindow, (void**)&ole_window); ok(hr == S_OK, "QueryInterface failed, hr=%lx\n", hr);
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/syslink.c | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+)
diff --git a/dlls/comctl32/tests/syslink.c b/dlls/comctl32/tests/syslink.c index ce46099d8ed..1467c6579c1 100644 --- a/dlls/comctl32/tests/syslink.c +++ b/dlls/comctl32/tests/syslink.c @@ -64,6 +64,19 @@ static const struct message parent_visible_syslink_wnd_seq[] = { {0} };
+static const struct message settext_syslink_wnd_seq[] = { + { WM_SETTEXT, sent }, + { WM_PAINT, sent }, + { WM_ERASEBKGND, sent|defwinproc|optional }, /* Wine only */ + {0} +}; + +static const struct message parent_settext_syslink_wnd_seq[] = { + { WM_CTLCOLORSTATIC, sent }, + { WM_NOTIFY, sent|wparam|lparam|optional, 0, NM_CUSTOMDRAW }, /* FIXME: Not sent on Wine */ + {0} +}; + /* Try to make sure pending X events have been processed before continuing */ static void flush_events(void) { @@ -101,6 +114,8 @@ static LRESULT WINAPI parent_wnd_proc(HWND hwnd, UINT message, WPARAM wParam, LP if (defwndproc_counter) msg.flags |= defwinproc; msg.wParam = wParam; msg.lParam = lParam; + if (message == WM_NOTIFY && lParam) + msg.lParam = ((NMHDR*)lParam)->code; add_message(sequences, PARENT_SEQ_INDEX, &msg); }
@@ -199,6 +214,8 @@ static void test_create_syslink(void) { HWND hWndSysLink; LONG oldstyle; + LRESULT ret; + LITEM item;
/* Create an invisible SysLink control */ flush_sequences(sequences, NUM_MSG_SEQUENCE); @@ -208,6 +225,13 @@ static void test_create_syslink(void) ok_sequence(sequences, SYSLINK_SEQ_INDEX, empty_wnd_seq, "create SysLink", FALSE); ok_sequence(sequences, PARENT_SEQ_INDEX, parent_create_syslink_wnd_seq, "create SysLink (parent)", TRUE);
+ /* Get first item */ + item.mask = LIF_ITEMINDEX|LIF_ITEMID|LIF_URL; + item.iLink = 0; + ret = SendMessageW(hWndSysLink, LM_GETITEM, 0, (LPARAM)&item); + ok(ret == 1, "LM_GETITEM failed\n"); + ok(!wcscmp(item.szUrl, L"link1"), "unexpected url %s\n", debugstr_w(item.szUrl)); + /* Make the SysLink control visible */ flush_sequences(sequences, NUM_MSG_SEQUENCE); oldstyle = GetWindowLongA(hWndSysLink, GWL_STYLE); @@ -217,6 +241,20 @@ static void test_create_syslink(void) ok_sequence(sequences, SYSLINK_SEQ_INDEX, visible_syslink_wnd_seq, "visible SysLink", TRUE); ok_sequence(sequences, PARENT_SEQ_INDEX, parent_visible_syslink_wnd_seq, "visible SysLink (parent)", TRUE);
+ /* Change contents */ + flush_sequences(sequences, NUM_MSG_SEQUENCE); + SetWindowTextW(hWndSysLink, L"Head <a href="link">link</a> Tail"); + flush_events(); + ok_sequence(sequences, SYSLINK_SEQ_INDEX, settext_syslink_wnd_seq, "SetWindowText", FALSE); + ok_sequence(sequences, PARENT_SEQ_INDEX, parent_settext_syslink_wnd_seq, "SetWindowText (parent)", FALSE); + + /* Get first item */ + item.mask = LIF_ITEMINDEX|LIF_ITEMID|LIF_URL; + item.iLink = 0; + ret = SendMessageW(hWndSysLink, LM_GETITEM, 0, (LPARAM)&item); + ok(ret == 1, "LM_GETITEM failed\n"); + ok(!wcscmp(item.szUrl, L"link"), "unexpected url %s\n", debugstr_w(item.szUrl)); + DestroyWindow(hWndSysLink); }
From: Esme Povirk esme@codeweavers.com
--- dlls/comctl32/tests/syslink.c | 48 +++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/dlls/comctl32/tests/syslink.c b/dlls/comctl32/tests/syslink.c index 1467c6579c1..71b87e7b6b5 100644 --- a/dlls/comctl32/tests/syslink.c +++ b/dlls/comctl32/tests/syslink.c @@ -38,7 +38,46 @@ static int g_link_id;
static struct msg_sequence *sequences[NUM_MSG_SEQUENCE];
-static const struct message empty_wnd_seq[] = { +static void CALLBACK msg_winevent_proc(HWINEVENTHOOK hevent, + DWORD event, + HWND hwnd, + LONG object_id, + LONG child_id, + DWORD thread_id, + DWORD event_time) +{ + struct message msg = {0}; + WCHAR class_name[256]; + + /* ignore events not from a syslink control */ + if (!GetClassNameW(hwnd, class_name, ARRAY_SIZE(class_name)) || + wcscmp(class_name, WC_LINK) != 0) + return; + + msg.message = event; + msg.flags = winevent_hook|wparam|lparam; + msg.wParam = object_id; + msg.lParam = child_id; + add_message(sequences, SYSLINK_SEQ_INDEX, &msg); +} + +static void init_winevent_hook(void) { + hwineventhook = SetWinEventHook(EVENT_MIN, EVENT_MAX, GetModuleHandleA(0), msg_winevent_proc, + 0, GetCurrentThreadId(), WINEVENT_INCONTEXT); + if (!hwineventhook) + win_skip( "no win event hook support\n" ); +} + +static void uninit_winevent_hook(void) { + if (!hwineventhook) + return; + + UnhookWinEvent(hwineventhook); + hwineventhook = 0; +} + +static const struct message create_syslink_wnd_seq[] = { + { EVENT_OBJECT_CREATE, winevent_hook|wparam|lparam, OBJID_WINDOW, CHILDID_SELF }, {0} };
@@ -66,6 +105,7 @@ static const struct message parent_visible_syslink_wnd_seq[] = {
static const struct message settext_syslink_wnd_seq[] = { { WM_SETTEXT, sent }, + { EVENT_OBJECT_NAMECHANGE, winevent_hook|wparam|lparam, OBJID_WINDOW, CHILDID_SELF }, { WM_PAINT, sent }, { WM_ERASEBKGND, sent|defwinproc|optional }, /* Wine only */ {0} @@ -222,7 +262,7 @@ static void test_create_syslink(void) hWndSysLink = create_syslink(WS_CHILD | WS_TABSTOP, hWndParent); ok(hWndSysLink != NULL, "Expected non NULL value (le %lu)\n", GetLastError()); flush_events(); - ok_sequence(sequences, SYSLINK_SEQ_INDEX, empty_wnd_seq, "create SysLink", FALSE); + ok_sequence(sequences, SYSLINK_SEQ_INDEX, create_syslink_wnd_seq, "create SysLink", FALSE); ok_sequence(sequences, PARENT_SEQ_INDEX, parent_create_syslink_wnd_seq, "create SysLink (parent)", TRUE);
/* Get first item */ @@ -544,6 +584,8 @@ START_TEST(syslink)
init_msg_sequences(sequences, NUM_MSG_SEQUENCE);
+ init_winevent_hook(); + /* Create parent window */ hWndParent = create_parent_window(); ok(hWndParent != NULL, "Failed to create parent Window!\n"); @@ -555,6 +597,8 @@ START_TEST(syslink) test_link_id(); test_msaa();
+ uninit_winevent_hook(); + DestroyWindow(hWndParent); unload_v6_module(ctx_cookie, hCtx); SetCursorPos(orig_pos.x, orig_pos.y);
On Wed Mar 26 19:20:20 2025 +0000, Esme Povirk wrote:
Yep, it's E_INVALIDARG on Windows. Also, the NAMECHANGE event is for OBJID_WINDOW on Windows, so it may be sent by Win32 and not the SysLink control itself.
A modification to user32:msg and manual testing with ControlSpyv6 and accEvent both indicate that WM_SETTEXT on a ComboBox does not send a NAMECHANGE. However, calling DefWindowProc on a ComboBox directly does send it, so probably ComboBox doesn't call DefWindowProc for WM_SETTEXT.
I'll work on adding that to DefWindowProc in a separate MR first.
On Wed Mar 26 19:56:59 2025 +0000, Esme Povirk wrote:
A modification to user32:msg and manual testing with ControlSpyv6 and accEvent both indicate that WM_SETTEXT on a ComboBox does not send a NAMECHANGE. However, calling DefWindowProc on a ComboBox directly does send it, so probably ComboBox doesn't call DefWindowProc for WM_SETTEXT. I'll work on adding that to DefWindowProc in a separate MR first.
Awesome! I'm glad my speculation on that return value wasn't bogus and a waste of time to investigate. :)