Most control characters sent via some CTRL+key combination should not autocomplete at all. ^C is one example, where just copying some text should not show the auto-suggestion box (if not visible). ^V is another example, where it is already handled in WM_PASTE, so it has to be a no-op here, else auto-append from WM_PASTE would complete the text and then the ^V autocompletion would remove every other suggestion in the listbox.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
v3: Use iscntrlW
dlls/shell32/autocomplete.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index ef835b9..048a47f 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -359,6 +359,10 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, return ACEditSubclassProc_KeyDown(This, hwnd, uMsg, wParam, lParam); case WM_CHAR: case WM_UNICHAR: + /* Don't autocomplete at all on most control characters */ + if (iscntrlW(wParam) && !(wParam >= '\b' && wParam <= '\r')) + break; + ret = CallWindowProcW(This->wpOrigEditProc, hwnd, uMsg, wParam, lParam); autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' ' ? autoappend_flag_yes : autoappend_flag_no);
Send some of the messages directly to the edit control's window procedure to match Windows behavior and to be able to process WM_SETTEXT.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
v3: Keep SendMessage when retrieving the text.
dlls/shell32/autocomplete.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index 048a47f..2f63d88 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -100,6 +100,14 @@ static inline IAutoCompleteImpl *impl_from_IAutoCompleteDropDown(IAutoCompleteDr return CONTAINING_RECORD(iface, IAutoCompleteImpl, IAutoCompleteDropDown_iface); }
+static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text, WPARAM start, LPARAM end) +{ + /* Send it directly to the edit control to match Windows behavior */ + WNDPROC proc = ac->wpOrigEditProc; + if (CallWindowProcW(proc, hwnd, WM_SETTEXT, 0, (LPARAM)text)) + CallWindowProcW(proc, hwnd, EM_SETSEL, start, end); +} + static size_t format_quick_complete(WCHAR *dst, const WCHAR *qc, const WCHAR *str, size_t str_len) { /* Replace the first %s directly without using snprintf, to avoid @@ -142,8 +150,7 @@ static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR * } else tmp = str;
- SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)tmp); - SendMessageW(hwnd, EM_SETSEL, len, size - 1); + set_text_and_selection(ac, hwnd, tmp, len, size - 1); if (tmp != str) heap_free(tmp); } @@ -256,8 +263,7 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT if ((buf = heap_alloc(sz * sizeof(WCHAR)))) { len = format_quick_complete(buf, ac->quickComplete, text, len); - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)buf); - SendMessageW(hwnd, EM_SETSEL, 0, len); + set_text_and_selection(ac, hwnd, buf, 0, len); heap_free(buf); }
@@ -309,16 +315,13 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR)))) return 0; len = SendMessageW(ac->hwndListBox, LB_GETTEXT, sel, (LPARAM)msg); - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)msg); - SendMessageW(hwnd, EM_SETSEL, len, len); + set_text_and_selection(ac, hwnd, msg, len, len); heap_free(msg); } else { - UINT len; - SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)ac->txtbackup); - len = strlenW(ac->txtbackup); - SendMessageW(hwnd, EM_SETSEL, len, len); + UINT len = strlenW(ac->txtbackup); + set_text_and_selection(ac, hwnd, ac->txtbackup, len, len); } return 0; } @@ -409,8 +412,7 @@ static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR)))) break; len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg); - SendMessageW(This->hwndEdit, WM_SETTEXT, 0, (LPARAM)msg); - SendMessageW(This->hwndEdit, EM_SETSEL, 0, len); + set_text_and_selection(This, This->hwndEdit, msg, 0, len); ShowWindow(hwnd, SW_HIDE); heap_free(msg); break;
Signed-off-by: Huw Davies huw@codeweavers.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
This should work now that we send it directly to the edit control's procedure.
dlls/shell32/autocomplete.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index 2f63d88..1751401 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -370,6 +370,7 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, autocomplete_text(This, hwnd, (This->options & ACO_AUTOAPPEND) && wParam >= ' ' ? autoappend_flag_yes : autoappend_flag_no); return ret; + case WM_SETTEXT: case WM_CUT: case WM_CLEAR: case WM_UNDO:
Signed-off-by: Huw Davies huw@codeweavers.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/shell32/autocomplete.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index 1751401..2153968 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -409,7 +409,7 @@ static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0); if (sel < 0) break; - len = SendMessageW(This->hwndListBox, LB_GETTEXTLEN, sel, 0); + len = SendMessageW(hwnd, LB_GETTEXTLEN, sel, 0); if (!(msg = heap_alloc((len + 1) * sizeof(WCHAR)))) break; len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
Signed-off-by: Huw Davies huw@codeweavers.com
Windows' AutoComplete seems to bypass hijacking in some cases. However, WM_GETTEXT seems to be able to get hijacked.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
This still works now with Wine, so I think it's nice to add these tests just in case for the future.
Also reduced the Sleep amount to 1/3 of its original, which will be especially useful in future tests (to be added) to prevent them from taking way too long.
dlls/shell32/tests/autocomplete.c | 82 +++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 7 deletions(-)
diff --git a/dlls/shell32/tests/autocomplete.c b/dlls/shell32/tests/autocomplete.c index b9c6374..e85e30c 100644 --- a/dlls/shell32/tests/autocomplete.c +++ b/dlls/shell32/tests/autocomplete.c @@ -236,6 +236,36 @@ static void createMainWnd(void) CW_USEDEFAULT, CW_USEDEFAULT, 130, 105, NULL, NULL, GetModuleHandleA(NULL), 0); }
+static WNDPROC HijackerWndProc_prev; +static const WCHAR HijackerWndProc_txt[] = {'H','i','j','a','c','k','e','d',0}; +static LRESULT CALLBACK HijackerWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_GETTEXT: + { + size_t len = min(wParam, ARRAY_SIZE(HijackerWndProc_txt)); + memcpy((void*)lParam, HijackerWndProc_txt, len * sizeof(WCHAR)); + return len; + } + case WM_GETTEXTLENGTH: + return ARRAY_SIZE(HijackerWndProc_txt) - 1; + } + return CallWindowProcW(HijackerWndProc_prev, hWnd, msg, wParam, lParam); +} + +static LRESULT CALLBACK HijackerWndProc2(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case EM_SETSEL: + lParam = wParam; + break; + case WM_SETTEXT: + lParam = (LPARAM)HijackerWndProc_txt; + break; + } + return CallWindowProcW(HijackerWndProc_prev, hWnd, msg, wParam, lParam); +} + struct string_enumerator { IEnumString IEnumString_iface; @@ -358,18 +388,29 @@ static HRESULT string_enumerator_create(void **ppv, WCHAR **suggestions, int cou return S_OK; }
+static void dispatch_messages(void) +{ + MSG msg; + Sleep(33); + while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } +} + static void test_custom_source(void) { static WCHAR str_alpha[] = {'t','e','s','t','1',0}; static WCHAR str_alpha2[] = {'t','e','s','t','2',0}; static WCHAR str_beta[] = {'a','u','t','o',' ','c','o','m','p','l','e','t','e',0}; + static WCHAR str_au[] = {'a','u',0}; static WCHAR *suggestions[] = { str_alpha, str_alpha2, str_beta }; IUnknown *enumerator; IAutoComplete2 *autocomplete; HWND hwnd_edit; WCHAR buffer[20]; HRESULT hr; - MSG msg;
ShowWindow(hMainWnd, SW_SHOW);
@@ -385,16 +426,43 @@ static void test_custom_source(void) hr = IAutoComplete2_Init(autocomplete, hwnd_edit, enumerator, NULL, NULL); ok(hr == S_OK, "IAutoComplete_Init failed: %x\n", hr);
+ SetFocus(hwnd_edit); SendMessageW(hwnd_edit, WM_CHAR, 'a', 1); SendMessageW(hwnd_edit, WM_CHAR, 'u', 1); - Sleep(100); - while (PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) - { - TranslateMessage(&msg); - DispatchMessageA(&msg); - } + dispatch_messages(); + SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); + ok(lstrcmpW(str_beta, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str_beta), wine_dbgstr_w(buffer)); + SendMessageW(hwnd_edit, EM_SETSEL, 0, -1); + SendMessageW(hwnd_edit, WM_CHAR, '\b', 1); + dispatch_messages(); + SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); + ok(buffer[0] == '\0', "Expected empty string, got %s\n", wine_dbgstr_w(buffer)); + + /* hijack the window procedure */ + HijackerWndProc_prev = (WNDPROC)SetWindowLongPtrW(hwnd_edit, GWLP_WNDPROC, (LONG_PTR)HijackerWndProc); + SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); + ok(lstrcmpW(HijackerWndProc_txt, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(HijackerWndProc_txt), wine_dbgstr_w(buffer)); + + SendMessageW(hwnd_edit, WM_CHAR, 'a', 1); + SendMessageW(hwnd_edit, WM_CHAR, 'u', 1); + SetWindowLongPtrW(hwnd_edit, GWLP_WNDPROC, (LONG_PTR)HijackerWndProc_prev); + dispatch_messages(); + SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); + ok(lstrcmpW(str_au, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str_au), wine_dbgstr_w(buffer)); + SendMessageW(hwnd_edit, EM_SETSEL, 0, -1); + SendMessageW(hwnd_edit, WM_CHAR, '\b', 1); + dispatch_messages(); + SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); + ok(buffer[0] == '\0', "Expected empty string, got %s\n", wine_dbgstr_w(buffer)); + + HijackerWndProc_prev = (WNDPROC)SetWindowLongPtrW(hwnd_edit, GWLP_WNDPROC, (LONG_PTR)HijackerWndProc2); + SendMessageW(hwnd_edit, WM_CHAR, 'a', 1); + SendMessageW(hwnd_edit, WM_CHAR, 'u', 1); + SetWindowLongPtrW(hwnd_edit, GWLP_WNDPROC, (LONG_PTR)HijackerWndProc_prev); + dispatch_messages(); SendMessageW(hwnd_edit, WM_GETTEXT, ARRAY_SIZE(buffer), (LPARAM)buffer); ok(lstrcmpW(str_beta, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str_beta), wine_dbgstr_w(buffer)); + /* end of hijacks */
ShowWindow(hMainWnd, SW_HIDE); DestroyWindow(hwnd_edit);
Signed-off-by: Huw Davies huw@codeweavers.com
Gabriel Ivăncescu gabrielopcode@gmail.com writes:
Why do you need all these Sleeps and dispatch calls? As far as I can see, everything you are testing should be synchronous.
On Mon, Sep 24, 2018 at 3:02 PM, Alexandre Julliard julliard@winehq.org wrote:
Well, at first I also wanted to remove them (they were there before, but only one test existed). It looks like they are actually needed on Windows, though, otherwise the tests fail.
I think Windows uses a separate thread to do the autocomplete and some other weird stuff like that.
It's not the SendMessage that's the problem, but I think the edit control doesn't receive the autocompleted stuff immediately on Windows.