[PATCH 0/5] MR11193: Imm32: Send WM_IME_ENDCOMPOSITION / reset composition string more consistently.
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/imm32/tests/imm32.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index c4d1404f3ea..bf3984a7188 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -6498,7 +6498,7 @@ static void test_ImmSetCompositionWindow(void) ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); - process_messages(); + flush_events(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11193
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/imm32/tests/imm32.c | 90 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index bf3984a7188..7abc51b5f7b 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -1083,28 +1083,108 @@ static void test_SCS_SETSTR(void) LONG alen,wlen; BOOL ret; DWORD prop; + imm_msgs *msg; + BOOL ends_comp_in_set; imc = ImmGetContext(hwnd); + flush_events(); + msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); if (!ret) { win_skip("Composition isn't supported\n"); ImmReleaseContext(hwnd, imc); return; } + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); + /* Before Win10 1909 WM_IME_ENDCOMPOSITION arrives after ImmSetCompositionStringW with non-empty string. On the + * newer Windows version WM_IME_ENDCOMPOSITION is only sent when the composition string is cleared or context + * is deactivated. */ + ends_comp_in_set = !!msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, NULL, 128, NULL, 128); ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); ok(!alen, "got %ld.\n", alen); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); ok(!wlen, "got %ld.\n", alen); + msg_spy_flush_msgs(); + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); + ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); + + msg_spy_flush_msgs(); + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, L"12", 6, NULL, 0); + ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + ok(!msg || broken(ends_comp_in_set && msg), "found WM_IME_ENDCOMPOSITION.\n"); + + alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); + todo_wine ok(alen == 2 || broken(ends_comp_in_set && !alen), "got %ld.\n", alen); + wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); + todo_wine ok(wlen == 4 || broken(ends_comp_in_set && !wlen), "got %ld.\n", wlen); + + msg_spy_flush_msgs(); + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, L"", 2, NULL, 0); + ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); + + alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); + todo_wine ok(!alen, "got %ld.\n", alen); + wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); + todo_wine ok(!wlen, "got %ld.\n", alen); + + msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + ok(!msg || broken(ends_comp_in_set && msg), "found WM_IME_ENDCOMPOSITION.\n"); + + msg_spy_flush_msgs(); + ImmSetActiveContext( hwnd, imc, FALSE ); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); + alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); + todo_wine ok(!alen, "got %ld.\n", alen); + wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); + todo_wine ok(!wlen, "got %ld.\n", alen); + + msg_spy_flush_msgs(); + ImmSetActiveContext( hwnd, imc, TRUE ); + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + ok(!msg, "found WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!msg, "did not find WM_IME_COMPOSITION.\n"); msg_spy_flush_msgs(); + ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); + ok(ret, "got error %lu.\n", GetLastError()); + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); + ok(!msg || broken(ends_comp_in_set && msg), "found WM_IME_ENDCOMPOSITION.\n"); + + process_messages(); + msg_spy_flush_msgs(); + alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); /* windows machines without any IME installed just return 0 above */ @@ -1170,9 +1250,19 @@ static void test_SCS_SETSTR(void) imc = ImmGetContext(hwnd); msg_spy_flush_msgs(); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!msg, "found WM_IME_COMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + ok(!msg, "found WM_IME_STARTCOMPOSITION.\n"); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL,0); ok(ret, "ImmSetCompositionStringW failed\n"); + + msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); + ok(!msg, "found WM_IME_STARTCOMPOSITION.\n"); + msg = msg_spy_find_msg(WM_IME_COMPOSITION); + ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); + wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, sizeof(wstring)); if (wlen > 0) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11193
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/imm32/ime.c | 5 ++++- dlls/imm32/tests/imm32.c | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/dlls/imm32/ime.c b/dlls/imm32/ime.c index 3cf7c1ed00a..253ce2c3610 100644 --- a/dlls/imm32/ime.c +++ b/dlls/imm32/ime.c @@ -683,7 +683,10 @@ BOOL WINAPI ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, D { UINT msg, flags = GCS_COMPSTR | GCS_COMPCLAUSE | GCS_COMPATTR | GCS_DELTASTART | GCS_CURSORPOS; WCHAR wparam = comp && comp_len >= sizeof(WCHAR) ? *(WCHAR *)comp : 0; - input_context_set_comp_str( ctx, comp, comp_len / sizeof(WCHAR) ); + DWORD len = comp_len / sizeof(WCHAR); + + if (len && comp && !((WCHAR *)comp)[len - 1]) --len; + input_context_set_comp_str( ctx, comp, len ); if ((msg = ime_set_composition_status( himc, TRUE ))) ime_send_message( himc, msg, 0, 0 ); ime_send_message( himc, WM_IME_COMPOSITION, wparam, flags ); } diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 7abc51b5f7b..8cd5aba4e61 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -1130,9 +1130,9 @@ static void test_SCS_SETSTR(void) ok(!msg || broken(ends_comp_in_set && msg), "found WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); - todo_wine ok(alen == 2 || broken(ends_comp_in_set && !alen), "got %ld.\n", alen); + ok(alen == 2 || broken(ends_comp_in_set && !alen), "got %ld.\n", alen); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); - todo_wine ok(wlen == 4 || broken(ends_comp_in_set && !wlen), "got %ld.\n", wlen); + ok(wlen == 4 || broken(ends_comp_in_set && !wlen), "got %ld.\n", wlen); msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, L"", 2, NULL, 0); @@ -1141,9 +1141,9 @@ static void test_SCS_SETSTR(void) todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); - todo_wine ok(!alen, "got %ld.\n", alen); + ok(!alen, "got %ld.\n", alen); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); - todo_wine ok(!wlen, "got %ld.\n", alen); + ok(!wlen, "got %ld.\n", alen); msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11193
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/imm32/ime.c | 2 +- dlls/imm32/tests/imm32.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dlls/imm32/ime.c b/dlls/imm32/ime.c index 253ce2c3610..96be851f5a7 100644 --- a/dlls/imm32/ime.c +++ b/dlls/imm32/ime.c @@ -687,7 +687,7 @@ BOOL WINAPI ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, D if (len && comp && !((WCHAR *)comp)[len - 1]) --len; input_context_set_comp_str( ctx, comp, len ); - if ((msg = ime_set_composition_status( himc, TRUE ))) ime_send_message( himc, msg, 0, 0 ); + if ((msg = ime_set_composition_status( himc, !!len ))) ime_send_message( himc, msg, 0, 0 ); ime_send_message( himc, WM_IME_COMPOSITION, wparam, flags ); } diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 8cd5aba4e61..92f687e52e6 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -1108,7 +1108,7 @@ static void test_SCS_SETSTR(void) ret = ImmSetCompositionStringW(imc, SCS_SETSTR, NULL, 128, NULL, 128); ok(ret, "got error %lu.\n", GetLastError()); msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); - todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); + ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); ok(!alen, "got %ld.\n", alen); @@ -1119,7 +1119,7 @@ static void test_SCS_SETSTR(void) ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); ok(ret, "got error %lu.\n", GetLastError()); msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); - todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); msg = msg_spy_find_msg(WM_IME_COMPOSITION); ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); @@ -1138,7 +1138,7 @@ static void test_SCS_SETSTR(void) ret = ImmSetCompositionStringW(imc, SCS_SETSTR, L"", 2, NULL, 0); ok(ret, "got error %lu.\n", GetLastError()); msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); - todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); + ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); ok(!alen, "got %ld.\n", alen); @@ -1149,7 +1149,7 @@ static void test_SCS_SETSTR(void) ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); ok(ret, "got error %lu.\n", GetLastError()); msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); - todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); msg = msg_spy_find_msg(WM_IME_COMPOSITION); ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11193
From: Paul Gofman <pgofman@codeweavers.com> --- dlls/imm32/ime.c | 12 ++++++++++++ dlls/imm32/tests/imm32.c | 8 ++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/dlls/imm32/ime.c b/dlls/imm32/ime.c index 96be851f5a7..67ee18067d2 100644 --- a/dlls/imm32/ime.c +++ b/dlls/imm32/ime.c @@ -534,7 +534,19 @@ BOOL WINAPI ImeSelect( HIMC himc, BOOL select ) BOOL WINAPI ImeSetActiveContext( HIMC himc, BOOL flag ) { + INPUTCONTEXT *ctx; + UINT msg; + TRACE( "himc %p, flag %#x stub!\n", himc, flag ); + if (!flag && (msg = ime_set_composition_status( himc, FALSE ))) + { + if ((ctx = ImmLockIMC( himc ))) + { + input_context_set_comp_str( ctx, NULL, 0 ); + ImmUnlockIMC( himc ); + } + ime_send_message( himc, msg, 0, 0 ); + } return TRUE; } diff --git a/dlls/imm32/tests/imm32.c b/dlls/imm32/tests/imm32.c index 92f687e52e6..920cca5808d 100644 --- a/dlls/imm32/tests/imm32.c +++ b/dlls/imm32/tests/imm32.c @@ -1158,11 +1158,11 @@ static void test_SCS_SETSTR(void) msg_spy_flush_msgs(); ImmSetActiveContext( hwnd, imc, FALSE ); msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); - todo_wine ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); + ok(!!msg || broken(ends_comp_in_set && !msg), "did not find WM_IME_ENDCOMPOSITION.\n"); alen = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 20); - todo_wine ok(!alen, "got %ld.\n", alen); + ok(!alen, "got %ld.\n", alen); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 20); - todo_wine ok(!wlen, "got %ld.\n", alen); + ok(!wlen, "got %ld.\n", alen); msg_spy_flush_msgs(); ImmSetActiveContext( hwnd, imc, TRUE ); @@ -1176,7 +1176,7 @@ static void test_SCS_SETSTR(void) ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); ok(ret, "got error %lu.\n", GetLastError()); msg = msg_spy_find_msg(WM_IME_STARTCOMPOSITION); - todo_wine ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); + ok(!!msg, "did not find WM_IME_STARTCOMPOSITION.\n"); msg = msg_spy_find_msg(WM_IME_COMPOSITION); ok(!!msg, "did not find WM_IME_COMPOSITION.\n"); msg = msg_spy_find_msg(WM_IME_ENDCOMPOSITION); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/11193
That helps "Call of Duty: Black Ops Cold War" which otherwise stops processing keyboard input after popping up chat window once (because it tracks WM_IME_STARTCOMPOSITION / WM_IME_ENDCOMPOSITION and ignores keys while in composition states; while itself does SetCompositionString when popping up chat window, even though with L"" string). The first patch ("imm32/tests: Flush events in test_ImmSetCompositionWindow().") fixes test_ImmSetCompositionWindow() on my WM here which randomly fails because focus transition events (which influence IME context set / clear) are arriving late. The same flush helps added tests to avoid the same problem. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11193#note_143566
When running with the added tests on Testbot machines there are various keycode test failures and a hang on some Windows machines, that is not introduced by the changes in test or implementation, here is the run of the current tests with only a newline added to test file: https://testbot.winehq.org/JobDetails.pl?Key=163462#k317 -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11193#note_143567
Rémi Bernon (@rbernon) commented about dlls/imm32/tests/imm32.c:
ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); - process_messages(); + flush_events(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0;
```suggestion:-12+0 hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu ", GetLastError() ); flush_events(); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ``` I'm not sure why this is needed but it's likely only because the window becomes visible, and may need to wait a bit longer for the X11 driver messages. The rest shouldn't depend on any host messages, so lets keep it separated. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11193#note_143574
On Fri Jun 19 06:48:09 2026 +0000, Rémi Bernon wrote:
```suggestion:-12+0 hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu ", GetLastError() ); flush_events(); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ``` I'm not sure why this is needed but it's likely only because the window becomes visible, and may need to wait a bit longer for the X11 driver messages. The rest shouldn't depend on any host messages, so lets keep it separated.
Yes, those extra activation / deactivation (which I am reproducing on Gnome at least) are coming from initial show window or maybe some activation changes in the previous test. In test_ImmSetCompositionWindow() that results in spurious WM_IME_ context messages coming from win32u switching active windows. In the added test it completely garbles the test intermittently because now with the changes deactivating context on de-focusing also resets composition string. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/11193#note_143642
participants (3)
-
Paul Gofman -
Paul Gofman (@gofman) -
Rémi Bernon (@rbernon)