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:
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.
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:
Why do you need all these Sleeps and dispatch calls? As far as I can see, everything you are testing should be synchronous.
-- Alexandre Julliard julliard@winehq.org
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.