[PATCH v2 0/2] MR11157: wshom.ocx: Implemented the AppActivate method
The `SendKeys` method used by scripted UI automation is almost pointless without window activation support. -- v2: Fixed var decl https://gitlab.winehq.org/wine/wine/-/merge_requests/11157
From: Anders Kjersem <andersdev@proton.me> --- dlls/wshom.ocx/shell.c | 77 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/dlls/wshom.ocx/shell.c b/dlls/wshom.ocx/shell.c index 4056201a6b7..525dec7f932 100644 --- a/dlls/wshom.ocx/shell.c +++ b/dlls/wshom.ocx/shell.c @@ -1820,8 +1820,81 @@ static HRESULT WINAPI WshShell3_LogEvent(IWshShell3 *iface, VARIANT *Type, BSTR static HRESULT WINAPI WshShell3_AppActivate(IWshShell3 *iface, VARIANT *App, VARIANT *Wait, VARIANT_BOOL *out_Success) { - FIXME("(%s %s %p): stub\n", debugstr_variant(App), debugstr_variant(Wait), out_Success); - return E_NOTIMPL; + HRESULT hr = S_FALSE; + HWND hWnd = GetWindow(GetDesktopWindow(), GW_CHILD); + HWND hWndTarget = NULL; + VARIANT vTmp; + if (!App) + return E_POINTER; + + V_VT(&vTmp) = VT_EMPTY; + if (V_VT(App) != VT_BSTR && SUCCEEDED(VariantChangeType(&vTmp, App, 0, VT_I4)) && V_I4(&vTmp)) + { + DWORD pidFind = V_I4(&vTmp), pid; + for (; hWnd; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) + { + if (!GetWindowThreadProcessId(hWnd, &pid) || pid != pidFind || !IsWindowVisible(hWnd)) + continue; + if (IsWindowEnabled(hWnd)) + { + hWndTarget = hWnd; + break; // Enabled and visible, we are done + } + else if (!hWndTarget) + { + hWndTarget = hWnd; // We will accept a disabled window if we have to + } + } + } + else if (SUCCEEDED(VariantChangeType(&vTmp, App, 0, VT_BSTR))) + { + BSTR bsFind = V_BSTR(&vTmp); + UINT lenFind = lstrlenW(bsFind); + for (; hWnd && !hWndTarget && lenFind; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) + { + UINT cch = GetWindowTextLengthW(hWnd); + if (!cch) + continue; + BSTR bsBuf = SysAllocStringLen(NULL, ++cch); + if (!bsBuf) + { + hr = E_OUTOFMEMORY; + break; + } + cch = GetWindowTextW(hWnd, bsBuf, cch); + if (cch >= lenFind && CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE, bsBuf, + lenFind, bsFind, lenFind) == CSTR_EQUAL) + { + hWndTarget = hWnd; + } + SysFreeString(bsBuf); + } + VariantClear(&vTmp); + } + + if (hWndTarget) + { + DWORD mytid = GetCurrentThreadId(), pid; + DWORD othertid = GetWindowThreadProcessId(hWnd, &pid); + BOOL attached = AttachThreadInput(mytid, othertid, TRUE); + SetForegroundWindow(hWndTarget); + V_VT(&vTmp) = VT_EMPTY; + if (Wait && !is_optional_argument(Wait) && + SUCCEEDED(VariantChangeType(&vTmp, Wait, 0, VT_BOOL)) && V_BOOL(&vTmp)) + { + UINT wait = 2000, interval = 100; + for (; wait && GetForegroundWindow() != hWndTarget; wait -= interval) + Sleep(interval); + } + SetLastError(0); + hr = (SetFocus(hWndTarget) || !GetLastError()) ? S_OK : S_FALSE; + if (attached) + AttachThreadInput(mytid, othertid, FALSE); + } + + if (out_Success) + *out_Success = (hr == S_OK) ? VARIANT_TRUE : VARIANT_FALSE; + return hr; } static HRESULT WINAPI WshShell3_SendKeys(IWshShell3 *iface, BSTR Keys, VARIANT *Wait) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11157
From: Anders Kjersem <andersdev@proton.me> --- dlls/wshom.ocx/shell.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/wshom.ocx/shell.c b/dlls/wshom.ocx/shell.c index 525dec7f932..c9192bf5d60 100644 --- a/dlls/wshom.ocx/shell.c +++ b/dlls/wshom.ocx/shell.c @@ -1852,10 +1852,11 @@ static HRESULT WINAPI WshShell3_AppActivate(IWshShell3 *iface, VARIANT *App, VAR UINT lenFind = lstrlenW(bsFind); for (; hWnd && !hWndTarget && lenFind; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) { + BSTR bsBuf; UINT cch = GetWindowTextLengthW(hWnd); if (!cch) continue; - BSTR bsBuf = SysAllocStringLen(NULL, ++cch); + bsBuf = SysAllocStringLen(NULL, ++cch); if (!bsBuf) { hr = E_OUTOFMEMORY; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11157
On Mon Jun 15 17:22:40 2026 +0000, Nikolay Sivov wrote:
This looks very specific. Where do those checks come from? Which checks? The conversion of the Wait argument?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11157#note_143150
On Mon Jun 15 18:07:21 2026 +0000, Anders Kjersem wrote:
Which checks? The conversion of the Wait argument? Or SetFocus? (SetFocus can return NULL but we don't know if that means failure or just no window with focus) Timeout values, last error checks, case-insensitive window text check. Is AttachThreadInput needed with SetForegroundWindow? Things like that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/11157#note_143151
On Mon Jun 15 18:11:03 2026 +0000, Nikolay Sivov wrote: > Timeout values, last error checks, case-insensitive window text check. > Is AttachThreadInput needed with SetForegroundWindow? Things like that. - AttachThreadInput is [a well-known](https://stackoverflow.com/a/3772400) hack to make SetForegroundWindow work better (stealing focus, which is actually what we legitimately want in this case because the user/script is probably going to follow up by sending keystrokes to this window). - Case-insensitive window text prefix check is easy to just verify on Windows. - The timeout values I made up, seemed about right. - `SetLastError(0)` is needed so we can tell if `SetFocus` failed (the function returns NULL) or if it succeeded but there was no previously focused window (the function returns NULL). This is documented on MSDN... -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11157#note_143152
participants (3)
-
Anders Kjersem -
Anders Kjersem (@anders) -
Nikolay Sivov (@nsivov)