/* * Unit tests for imm32 * * Copyright (c) 2008 Michael Jung * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "winbase.h" #include "wine/test.h" #include "objbase.h" #include "winuser.h" #include "wingdi.h" #include "imm.h" #include "immdev.h" #include "ime_test.h" static const char *debugstr_wm_ime( UINT msg ) { switch (msg) { case WM_IME_STARTCOMPOSITION: return "WM_IME_STARTCOMPOSITION"; case WM_IME_ENDCOMPOSITION: return "WM_IME_ENDCOMPOSITION"; case WM_IME_COMPOSITION: return "WM_IME_COMPOSITION"; case WM_IME_SETCONTEXT: return "WM_IME_SETCONTEXT"; case WM_IME_NOTIFY: return "WM_IME_NOTIFY"; case WM_IME_CONTROL: return "WM_IME_CONTROL"; case WM_IME_COMPOSITIONFULL: return "WM_IME_COMPOSITIONFULL"; case WM_IME_SELECT: return "WM_IME_SELECT"; case WM_IME_CHAR: return "WM_IME_CHAR"; case WM_IME_REQUEST: return "WM_IME_REQUEST"; case WM_IME_KEYDOWN: return "WM_IME_KEYDOWN"; case WM_IME_KEYUP: return "WM_IME_KEYUP"; default: return wine_dbg_sprintf( "%#x", msg ); } } static const char *debugstr_ok( const char *cond ) { int c, n = 0; /* skip possible casts */ while ((c = *cond++)) { if (c == '(') n++; if (!n) break; if (c == ')') n--; } if (!strchr( cond - 1, '(' )) return wine_dbg_sprintf( "got %s", cond - 1 ); return wine_dbg_sprintf( "%.*s returned", (int)strcspn( cond - 1, "( " ), cond - 1 ); } #define ok_eq( e, r, t, f, ... ) \ do \ { \ t v = (r); \ ok( v == (e), "%s " f "\n", debugstr_ok( #r ), v, ##__VA_ARGS__ ); \ } while (0) #define ok_ne( e, r, t, f, ... ) \ do \ { \ t v = (r); \ ok( v != (e), "%s " f "\n", debugstr_ok( #r ), v, ##__VA_ARGS__ ); \ } while (0) #define ok_wcs( e, r ) \ do \ { \ const WCHAR *v = (r); \ ok( !wcscmp( v, (e) ), "%s %s\n", debugstr_ok(#r), debugstr_w(v) ); \ } while (0) #define ok_str( e, r ) \ do \ { \ const char *v = (r); \ ok( !strcmp( v, (e) ), "%s %s\n", debugstr_ok(#r), debugstr_a(v) ); \ } while (0) #define ok_ret( e, r ) ok_eq( e, r, UINT_PTR, "%Iu, error %ld", GetLastError() ) BOOL WINAPI ImmSetActiveContext(HWND, HIMC, BOOL); static BOOL (WINAPI *pImmAssociateContextEx)(HWND,HIMC,DWORD); static UINT (WINAPI *pNtUserAssociateInputContext)(HWND,HIMC,ULONG); static BOOL (WINAPI *pImmIsUIMessageA)(HWND,UINT,WPARAM,LPARAM); static UINT (WINAPI *pSendInput) (UINT, INPUT*, size_t); extern BOOL WINAPI ImmFreeLayout(HKL); extern BOOL WINAPI ImmLoadIME(HKL); extern BOOL WINAPI ImmActivateLayout(HKL); #define check_member_( file, line, val, exp, fmt, member ) \ ok_(file, line)( (val).member == (exp).member, "got " #member " " fmt "\n", (val).member ) #define check_member( val, exp, fmt, member ) \ check_member_( __FILE__, __LINE__, val, exp, fmt, member ) #define check_member_wstr_( file, line, val, exp, member ) \ ok_(file, line)( !wcscmp( (val).member, (exp).member ), "got " #member " %s\n", \ debugstr_w((val).member) ) #define check_member_wstr( val, exp, member ) \ check_member_wstr_( __FILE__, __LINE__, val, exp, member ) #define check_member_str_( file, line, val, exp, member ) \ ok_(file, line)( !strcmp( (val).member, (exp).member ), "got " #member " %s\n", \ debugstr_a((val).member) ) #define check_member_str( val, exp, member ) \ check_member_str_( __FILE__, __LINE__, val, exp, member ) #define check_member_point_( file, line, val, exp, member ) \ ok_(file, line)( !memcmp( &(val).member, &(exp).member, sizeof(POINT) ), \ "got " #member " %s\n", wine_dbgstr_point( &(val).member ) ) #define check_member_point( val, exp, member ) \ check_member_point_( __FILE__, __LINE__, val, exp, member ) #define check_member_rect_( file, line, val, exp, member ) \ ok_(file, line)( !memcmp( &(val).member, &(exp).member, sizeof(RECT) ), \ "got " #member " %s\n", wine_dbgstr_rect( &(val).member ) ) #define check_member_rect( val, exp, member ) \ check_member_rect_( __FILE__, __LINE__, val, exp, member ) #define check_composition_string( a, b ) check_composition_string_( __LINE__, a, b ) static void check_composition_string_( int line, COMPOSITIONSTRING *string, const COMPOSITIONSTRING *expect ) { check_member_( __FILE__, line, *string, *expect, "%lu", dwSize ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadAttrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadAttrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadClauseLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadClauseOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadStrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompReadStrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompAttrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompAttrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompClauseLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompClauseOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompStrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCompStrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwCursorPos ); check_member_( __FILE__, line, *string, *expect, "%lu", dwDeltaStart ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadClauseLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadClauseOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadStrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultReadStrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultClauseLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultClauseOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultStrLen ); check_member_( __FILE__, line, *string, *expect, "%lu", dwResultStrOffset ); check_member_( __FILE__, line, *string, *expect, "%lu", dwPrivateSize ); check_member_( __FILE__, line, *string, *expect, "%lu", dwPrivateOffset ); } #define check_candidate_list( a, b ) check_candidate_list_( __LINE__, a, b, TRUE ) static void check_candidate_list_( int line, CANDIDATELIST *list, const CANDIDATELIST *expect, BOOL unicode ) { UINT i; check_member_( __FILE__, line, *list, *expect, "%lu", dwSize ); check_member_( __FILE__, line, *list, *expect, "%lu", dwStyle ); check_member_( __FILE__, line, *list, *expect, "%lu", dwCount ); check_member_( __FILE__, line, *list, *expect, "%lu", dwSelection ); check_member_( __FILE__, line, *list, *expect, "%lu", dwPageStart ); check_member_( __FILE__, line, *list, *expect, "%lu", dwPageSize ); for (i = 0; i < list->dwCount && i < expect->dwCount; ++i) { void *list_str = (BYTE *)list + list->dwOffset[i], *expect_str = (BYTE *)expect + expect->dwOffset[i]; check_member_( __FILE__, line, *list, *expect, "%lu", dwOffset[i] ); if (unicode) ok_( __FILE__, line )( !wcscmp( list_str, expect_str ), "got %s\n", debugstr_w(list_str) ); else ok_( __FILE__, line )( !strcmp( list_str, expect_str ), "got %s\n", debugstr_a(list_str) ); } } #define check_candidate_form( a, b ) check_candidate_form_( __LINE__, a, b ) static void check_candidate_form_( int line, CANDIDATEFORM *form, const CANDIDATEFORM *expect ) { check_member_( __FILE__, line, *form, *expect, "%#lx", dwIndex ); check_member_( __FILE__, line, *form, *expect, "%#lx", dwStyle ); check_member_point_( __FILE__, line, *form, *expect, ptCurrentPos ); check_member_rect_( __FILE__, line, *form, *expect, rcArea ); } #define check_composition_form( a, b ) check_composition_form_( __LINE__, a, b ) static void check_composition_form_( int line, COMPOSITIONFORM *form, const COMPOSITIONFORM *expect ) { check_member_( __FILE__, line, *form, *expect, "%#lx", dwStyle ); check_member_point_( __FILE__, line, *form, *expect, ptCurrentPos ); check_member_rect_( __FILE__, line, *form, *expect, rcArea ); } #define check_logfont_w( a, b ) check_logfont_w_( __LINE__, a, b ) static void check_logfont_w_( int line, LOGFONTW *font, const LOGFONTW *expect ) { check_member_( __FILE__, line, *font, *expect, "%lu", lfHeight ); check_member_( __FILE__, line, *font, *expect, "%lu", lfWidth ); check_member_( __FILE__, line, *font, *expect, "%lu", lfEscapement ); check_member_( __FILE__, line, *font, *expect, "%lu", lfOrientation ); check_member_( __FILE__, line, *font, *expect, "%lu", lfWeight ); check_member_( __FILE__, line, *font, *expect, "%u", lfItalic ); check_member_( __FILE__, line, *font, *expect, "%u", lfUnderline ); check_member_( __FILE__, line, *font, *expect, "%u", lfStrikeOut ); check_member_( __FILE__, line, *font, *expect, "%u", lfCharSet ); check_member_( __FILE__, line, *font, *expect, "%u", lfOutPrecision ); check_member_( __FILE__, line, *font, *expect, "%u", lfClipPrecision ); check_member_( __FILE__, line, *font, *expect, "%u", lfQuality ); check_member_( __FILE__, line, *font, *expect, "%u", lfPitchAndFamily ); check_member_wstr_( __FILE__, line, *font, *expect, lfFaceName ); } #define check_logfont_a( a, b ) check_logfont_a_( __LINE__, a, b ) static void check_logfont_a_( int line, LOGFONTA *font, const LOGFONTA *expect ) { check_member_( __FILE__, line, *font, *expect, "%lu", lfHeight ); check_member_( __FILE__, line, *font, *expect, "%lu", lfWidth ); check_member_( __FILE__, line, *font, *expect, "%lu", lfEscapement ); check_member_( __FILE__, line, *font, *expect, "%lu", lfOrientation ); check_member_( __FILE__, line, *font, *expect, "%lu", lfWeight ); check_member_( __FILE__, line, *font, *expect, "%u", lfItalic ); check_member_( __FILE__, line, *font, *expect, "%u", lfUnderline ); check_member_( __FILE__, line, *font, *expect, "%u", lfStrikeOut ); check_member_( __FILE__, line, *font, *expect, "%u", lfCharSet ); check_member_( __FILE__, line, *font, *expect, "%u", lfOutPrecision ); check_member_( __FILE__, line, *font, *expect, "%u", lfClipPrecision ); check_member_( __FILE__, line, *font, *expect, "%u", lfQuality ); check_member_( __FILE__, line, *font, *expect, "%u", lfPitchAndFamily ); check_member_str_( __FILE__, line, *font, *expect, lfFaceName ); } #define DEFINE_EXPECT(func) \ static BOOL expect_ ## func = FALSE, called_ ## func = FALSE, enabled_ ## func = FALSE #define SET_EXPECT(func) \ expect_ ## func = TRUE #define CHECK_EXPECT2(func) \ do { \ if (enabled_ ## func) {\ ok(expect_ ##func, "unexpected call " #func "\n"); \ called_ ## func = TRUE; \ } \ }while(0) #define CHECK_EXPECT(func) \ do { \ CHECK_EXPECT2(func); \ expect_ ## func = FALSE; \ }while(0) #define CHECK_CALLED(func) \ do { \ ok(called_ ## func, "expected " #func "\n"); \ expect_ ## func = called_ ## func = FALSE; \ }while(0) #define SET_ENABLE(func, val) \ enabled_ ## func = (val) DEFINE_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); DEFINE_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); #define process_messages() process_messages_(0) static void process_messages_(HWND hwnd) { MSG msg; while (PeekMessageA( &msg, hwnd, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); DispatchMessageA( &msg ); } } /* try to make sure pending X events have been processed before continuing */ #define flush_events() flush_events_( 100, 200 ) static void flush_events_( int min_timeout, int max_timeout ) { DWORD time = GetTickCount() + max_timeout; MSG msg; while (max_timeout > 0) { if (MsgWaitForMultipleObjects( 0, NULL, FALSE, min_timeout, QS_ALLINPUT ) == WAIT_TIMEOUT) break; while (PeekMessageA( &msg, 0, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); DispatchMessageA( &msg ); } max_timeout = time - GetTickCount(); } } #define ime_trace( msg, ... ) if (winetest_debug > 1) trace( "%04lx:%s " msg, GetCurrentThreadId(), __func__, ## __VA_ARGS__ ) static BOOL ImeSelect_init_status; static BOOL todo_ImeInquire; DEFINE_EXPECT( ImeInquire ); static BOOL todo_ImeDestroy; DEFINE_EXPECT( ImeDestroy ); DEFINE_EXPECT( ImeEscape ); DEFINE_EXPECT( ImeEnumRegisterWord ); DEFINE_EXPECT( ImeRegisterWord ); DEFINE_EXPECT( ImeGetRegisterWordStyle ); DEFINE_EXPECT( ImeUnregisterWord ); static BOOL todo_ImeSetCompositionString; DEFINE_EXPECT( ImeSetCompositionString ); static BOOL todo_IME_DLL_PROCESS_ATTACH; DEFINE_EXPECT( IME_DLL_PROCESS_ATTACH ); static BOOL todo_IME_DLL_PROCESS_DETACH; DEFINE_EXPECT( IME_DLL_PROCESS_DETACH ); static IMEINFO ime_info; static UINT ime_count; static WCHAR ime_path[MAX_PATH]; static HIMC default_himc; static HKL default_hkl, wineime_hkl; static HKL expect_ime = (HKL)(int)0xe020047f; enum ime_function { IME_SELECT = 1, IME_NOTIFY, IME_PROCESS_KEY, IME_TO_ASCII_EX, IME_SET_ACTIVE_CONTEXT, MSG_IME_UI, MSG_TEST_WIN, }; struct ime_call { HKL hkl; HIMC himc; enum ime_function func; WCHAR comp[16]; WCHAR result[16]; union { int select; struct { int action; int index; int value; } notify; struct { WORD vkey; LPARAM lparam; } process_key; struct { UINT vkey; UINT vsc; UINT flags; } to_ascii_ex; struct { int flag; } set_active_context; struct { UINT msg; WPARAM wparam; LPARAM lparam; } message; }; BOOL todo; BOOL broken; BOOL flaky_himc; BOOL todo_value; BOOL todo_himc; }; struct ime_call empty_sequence[] = {{0}}; static struct ime_call ime_calls[1024]; static ULONG ime_call_count; #define ok_call( a, b ) ok_call_( __FILE__, __LINE__, a, b ) static int ok_call_( const char *file, int line, const struct ime_call *expected, const struct ime_call *received ) { int ret; if ((ret = expected->func - received->func)) goto done; /* Wine doesn't allocate HIMC in a deterministic order, ignore them when they are enumerated */ if (expected->flaky_himc && (ret = !!(UINT_PTR)expected->himc - !!(UINT_PTR)received->himc)) goto done; if (!expected->flaky_himc && (ret = (UINT_PTR)expected->himc - (UINT_PTR)received->himc)) { /* on some Wine configurations the IME UI doesn't get an HIMC */ if (!winetest_platform_is_wine || !expected->todo_himc) goto done; else todo_wine ok( 0, "got himc %p\n", received->himc ); } if ((ret = (UINT)(UINT_PTR)expected->hkl - (UINT)(UINT_PTR)received->hkl)) goto done; switch (expected->func) { case IME_SELECT: if ((ret = expected->select - received->select)) goto done; break; case IME_NOTIFY: if ((ret = expected->notify.action - received->notify.action)) goto done; if ((ret = expected->notify.index - received->notify.index)) goto done; if ((ret = expected->notify.value - received->notify.value)) goto done; break; case IME_PROCESS_KEY: if ((ret = expected->process_key.vkey - received->process_key.vkey)) goto done; if ((ret = expected->process_key.lparam - received->process_key.lparam)) goto done; break; case IME_TO_ASCII_EX: if ((ret = expected->to_ascii_ex.vkey - received->to_ascii_ex.vkey)) goto done; if ((ret = expected->to_ascii_ex.vsc - received->to_ascii_ex.vsc)) goto done; if ((ret = expected->to_ascii_ex.flags - received->to_ascii_ex.flags)) goto done; break; case IME_SET_ACTIVE_CONTEXT: if ((ret = expected->set_active_context.flag - received->set_active_context.flag)) goto done; break; case MSG_IME_UI: case MSG_TEST_WIN: if ((ret = expected->message.msg - received->message.msg)) goto done; if ((ret = (expected->message.wparam - received->message.wparam))) goto done; if ((ret = (expected->message.lparam - received->message.lparam))) goto done; if ((ret = wcscmp( expected->comp, received->comp ))) goto done; if ((ret = wcscmp( expected->result, received->result ))) goto done; break; } done: if (ret && broken( expected->broken )) return ret; switch (received->func) { case IME_SELECT: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, IME_SELECT select %u\n", received->hkl, received->himc, received->select ); return ret; case IME_NOTIFY: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, IME_NOTIFY action %#x, index %#x, value %#x\n", received->hkl, received->himc, received->notify.action, received->notify.index, received->notify.value ); return ret; case IME_PROCESS_KEY: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, IME_PROCESS_KEY vkey %#x, lparam %#Ix\n", received->hkl, received->himc, received->process_key.vkey, received->process_key.lparam ); return ret; case IME_TO_ASCII_EX: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, IME_TO_ASCII_EX vkey %#x, vsc %#x, flags %#x\n", received->hkl, received->himc, received->to_ascii_ex.vkey, received->to_ascii_ex.vsc, received->to_ascii_ex.flags ); return ret; case IME_SET_ACTIVE_CONTEXT: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, IME_SET_ACTIVE_CONTEXT flag %u\n", received->hkl, received->himc, received->set_active_context.flag ); return ret; case MSG_IME_UI: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, MSG_IME_UI msg %s, wparam %#Ix, lparam %#Ix\n", received->hkl, received->himc, debugstr_wm_ime(received->message.msg), received->message.wparam, received->message.lparam ); return ret; case MSG_TEST_WIN: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "got hkl %p, himc %p, MSG_TEST_WIN msg %s, wparam %#Ix, lparam %#Ix, comp %s, result %s\n", received->hkl, received->himc, debugstr_wm_ime(received->message.msg), received->message.wparam, received->message.lparam, debugstr_w(received->comp), debugstr_w(received->result) ); return ret; } switch (expected->func) { case IME_SELECT: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, IME_SELECT select %u\n", expected->hkl, expected->himc, expected->select ); break; case IME_NOTIFY: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, IME_NOTIFY action %#x, index %#x, value %#x\n", expected->hkl, expected->himc, expected->notify.action, expected->notify.index, expected->notify.value ); break; case IME_PROCESS_KEY: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, IME_PROCESS_KEY vkey %#x, lparam %#Ix\n", expected->hkl, expected->himc, expected->process_key.vkey, expected->process_key.lparam ); break; case IME_TO_ASCII_EX: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, IME_TO_ASCII_EX vkey %#x, vsc %#x, flags %#x\n", expected->hkl, expected->himc, expected->to_ascii_ex.vkey, expected->to_ascii_ex.vsc, expected->to_ascii_ex.flags ); break; case IME_SET_ACTIVE_CONTEXT: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, IME_SET_ACTIVE_CONTEXT flag %u\n", expected->hkl, expected->himc, expected->set_active_context.flag ); break; case MSG_IME_UI: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, MSG_IME_UI msg %s, wparam %#Ix, lparam %#Ix\n", expected->hkl, expected->himc, debugstr_wm_ime(expected->message.msg), expected->message.wparam, expected->message.lparam ); break; case MSG_TEST_WIN: todo_wine_if( expected->todo || expected->todo_value ) ok_(file, line)( !ret, "hkl %p, himc %p, MSG_TEST_WIN msg %s, wparam %#Ix, lparam %#Ix, comp %s, result %s\n", expected->hkl, expected->himc, debugstr_wm_ime(expected->message.msg), expected->message.wparam, expected->message.lparam, debugstr_w(expected->comp), debugstr_w(expected->result) ); break; } return 0; } #define ok_seq( a ) ok_seq_( __FILE__, __LINE__, a, #a ) static void ok_seq_( const char *file, int line, const struct ime_call *expected, const char *context ) { const struct ime_call *received = ime_calls; UINT i = 0, ret; while (expected->func || received->func) { winetest_push_context( "%u%s%s", i++, !expected->func ? " (spurious)" : "", !received->func ? " (missing)" : "" ); ret = ok_call_( file, line, expected, received ); if (ret && expected->todo && expected->func && !strcmp( winetest_platform, "wine" )) expected++; else if (ret && broken(expected->broken)) expected++; else { if (expected->func) expected++; if (received->func) received++; } winetest_pop_context(); } memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static BOOL check_WM_SHOWWINDOW; static BOOL ignore_IME_NOTIFY; static BOOL ignore_WM_IME_NOTIFY; static BOOL ignore_WM_IME_REQUEST; static BOOL ignore_message( UINT msg, WPARAM wparam ) { switch (msg) { case WM_IME_NOTIFY: if (ignore_WM_IME_NOTIFY) return TRUE; return wparam > IMN_PRIVATE; case WM_IME_REQUEST: if (ignore_WM_IME_REQUEST) return TRUE; return FALSE; case WM_IME_STARTCOMPOSITION: case WM_IME_ENDCOMPOSITION: case WM_IME_COMPOSITION: case WM_IME_SETCONTEXT: case WM_IME_CONTROL: case WM_IME_COMPOSITIONFULL: case WM_IME_SELECT: case WM_IME_CHAR: case 0x287: case WM_IME_KEYDOWN: case WM_IME_KEYUP: return FALSE; case WM_SHOWWINDOW: return !check_WM_SHOWWINDOW; default: return TRUE; } } static LRESULT CALLBACK ime_ui_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = (HIMC)GetWindowLongPtrW( hwnd, IMMGWL_IMC ), .func = MSG_IME_UI, .message = {.msg = msg, .wparam = wparam, .lparam = lparam} }; LONG_PTR ptr; ime_trace( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam ); if (ignore_message( msg, wparam )) return DefWindowProcW( hwnd, msg, wparam, lparam ); ptr = GetWindowLongPtrW( hwnd, IMMGWL_PRIVATE ); ok( !ptr, "got IMMGWL_PRIVATE %#Ix\n", ptr ); ime_calls[ime_call_count++] = call; return DefWindowProcW( hwnd, msg, wparam, lparam ); } static LRESULT CALLBACK test_window_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = ImmGetContext( hwnd ), .func = MSG_TEST_WIN, .message = {.msg = msg, .wparam = wparam, .lparam = lparam} }; ime_trace( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam ); if (ignore_message( msg, wparam )) return DefWindowProcW( hwnd, msg, wparam, lparam ); if (msg == WM_IME_COMPOSITION) { ImmGetCompositionStringW( call.himc, GCS_COMPSTR, call.comp, sizeof(call.comp) ); ImmGetCompositionStringW( call.himc, GCS_RESULTSTR, call.result, sizeof(call.result) ); } ime_calls[ime_call_count++] = call; return DefWindowProcW( hwnd, msg, wparam, lparam ); } static WNDCLASSEXW ime_ui_class = { .cbSize = sizeof(WNDCLASSEXW), .style = CS_IME, .lpfnWndProc = ime_ui_window_proc, .cbWndExtra = 2 * sizeof(LONG_PTR), .lpszClassName = L"WineTestIME", }; static WNDCLASSEXW test_class = { .cbSize = sizeof(WNDCLASSEXW), .lpfnWndProc = test_window_proc, .lpszClassName = L"WineTest", }; /* * msgspy - record and analyse message traces sent to a certain window */ typedef struct _msgs { CWPSTRUCT msg; BOOL post; } imm_msgs; static struct _msg_spy { HWND hwnd; HHOOK get_msg_hook; HHOOK call_wnd_proc_hook; imm_msgs msgs[64]; unsigned int i_msg; } msg_spy; typedef struct { DWORD type; union { MOUSEINPUT mi; KEYBDINPUT ki; HARDWAREINPUT hi; } u; } TEST_INPUT; static UINT (WINAPI *pSendInput) (UINT, INPUT*, size_t); static LRESULT CALLBACK get_msg_filter(int nCode, WPARAM wParam, LPARAM lParam) { if (HC_ACTION == nCode) { MSG *msg = (MSG*)lParam; if ((msg->hwnd == msg_spy.hwnd || msg_spy.hwnd == NULL) && (msg_spy.i_msg < ARRAY_SIZE(msg_spy.msgs))) { msg_spy.msgs[msg_spy.i_msg].msg.hwnd = msg->hwnd; msg_spy.msgs[msg_spy.i_msg].msg.message = msg->message; msg_spy.msgs[msg_spy.i_msg].msg.wParam = msg->wParam; msg_spy.msgs[msg_spy.i_msg].msg.lParam = msg->lParam; msg_spy.msgs[msg_spy.i_msg].post = TRUE; msg_spy.i_msg++; } } return CallNextHookEx(msg_spy.get_msg_hook, nCode, wParam, lParam); } static LRESULT CALLBACK call_wnd_proc_filter(int nCode, WPARAM wParam, LPARAM lParam) { if (HC_ACTION == nCode) { CWPSTRUCT *cwp = (CWPSTRUCT*)lParam; if (((cwp->hwnd == msg_spy.hwnd || msg_spy.hwnd == NULL)) && (msg_spy.i_msg < ARRAY_SIZE(msg_spy.msgs))) { memcpy(&msg_spy.msgs[msg_spy.i_msg].msg, cwp, sizeof(msg_spy.msgs[0].msg)); msg_spy.msgs[msg_spy.i_msg].post = FALSE; msg_spy.i_msg++; } } return CallNextHookEx(msg_spy.call_wnd_proc_hook, nCode, wParam, lParam); } static void msg_spy_pump_msg_queue(void) { MSG msg; while(PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return; } static void msg_spy_flush_msgs(void) { msg_spy_pump_msg_queue(); msg_spy.i_msg = 0; } static imm_msgs* msg_spy_find_next_msg(UINT message, UINT *start) { UINT i; msg_spy_pump_msg_queue(); if (msg_spy.i_msg >= ARRAY_SIZE(msg_spy.msgs)) fprintf(stdout, "%s:%d: msg_spy: message buffer overflow!\n", __FILE__, __LINE__); for (i = *start; i < msg_spy.i_msg; i++) if (msg_spy.msgs[i].msg.message == message) { *start = i+1; return &msg_spy.msgs[i]; } return NULL; } static imm_msgs* msg_spy_find_msg(UINT message) { UINT i = 0; return msg_spy_find_next_msg(message, &i); } static void msg_spy_init(HWND hwnd) { msg_spy.hwnd = hwnd; msg_spy.get_msg_hook = SetWindowsHookExW(WH_GETMESSAGE, get_msg_filter, GetModuleHandleW(NULL), GetCurrentThreadId()); msg_spy.call_wnd_proc_hook = SetWindowsHookExW(WH_CALLWNDPROC, call_wnd_proc_filter, GetModuleHandleW(NULL), GetCurrentThreadId()); msg_spy.i_msg = 0; msg_spy_flush_msgs(); } static void msg_spy_cleanup(void) { if (msg_spy.get_msg_hook) UnhookWindowsHookEx(msg_spy.get_msg_hook); if (msg_spy.call_wnd_proc_hook) UnhookWindowsHookEx(msg_spy.call_wnd_proc_hook); memset(&msg_spy, 0, sizeof(msg_spy)); } /* * imm32 test cases - Issue some IMM commands on a dummy window and analyse the * messages being sent to this window in response. */ static const char wndcls[] = "winetest_imm32_wndcls"; static enum { PHASE_UNKNOWN, FIRST_WINDOW, SECOND_WINDOW, CREATE_CANCEL, NCCREATE_CANCEL, IME_DISABLED } test_phase; static HWND hwnd, child; static HWND get_ime_window(void); static void load_resource( const WCHAR *name, WCHAR *filename ) { static WCHAR path[MAX_PATH]; DWORD written; HANDLE file; HRSRC res; void *ptr; GetTempPathW( ARRAY_SIZE(path), path ); GetTempFileNameW( path, name, 0, filename ); file = CreateFileW( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, 0 ); ok( file != INVALID_HANDLE_VALUE, "failed to create %s, error %lu\n", debugstr_w(filename), GetLastError() ); res = FindResourceW( NULL, name, L"TESTDLL" ); ok( res != 0, "couldn't find resource\n" ); ptr = LockResource( LoadResource( GetModuleHandleW( NULL ), res ) ); WriteFile( file, ptr, SizeofResource( GetModuleHandleW( NULL ), res ), &written, NULL ); ok( written == SizeofResource( GetModuleHandleW( NULL ), res ), "couldn't write resource\n" ); CloseHandle( file ); } static LRESULT WINAPI wndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { HWND default_ime_wnd; switch (msg) { case WM_IME_SETCONTEXT: if (wParam) CHECK_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); else CHECK_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); ok(lParam == ISC_SHOWUIALL || !lParam, "lParam = %Ix\n", lParam); return TRUE; case WM_NCCREATE: default_ime_wnd = get_ime_window(); switch(test_phase) { case FIRST_WINDOW: case IME_DISABLED: ok(!default_ime_wnd, "expected no IME windows\n"); break; case SECOND_WINDOW: ok(default_ime_wnd != NULL, "expected IME window existence\n"); break; default: break; /* do nothing */ } if (test_phase == NCCREATE_CANCEL) return FALSE; return TRUE; case WM_NCCALCSIZE: default_ime_wnd = get_ime_window(); switch(test_phase) { case FIRST_WINDOW: case SECOND_WINDOW: case CREATE_CANCEL: ok(default_ime_wnd != NULL, "expected IME window existence\n"); break; case IME_DISABLED: ok(!default_ime_wnd, "expected no IME windows\n"); break; default: break; /* do nothing */ } break; case WM_CREATE: default_ime_wnd = get_ime_window(); switch(test_phase) { case FIRST_WINDOW: case SECOND_WINDOW: case CREATE_CANCEL: ok(default_ime_wnd != NULL, "expected IME window existence\n"); break; case IME_DISABLED: ok(!default_ime_wnd, "expected no IME windows\n"); break; default: break; /* do nothing */ } if (test_phase == CREATE_CANCEL) return -1; return TRUE; } return DefWindowProcA(hWnd,msg,wParam,lParam); } static BOOL is_ime_enabled(void) { HIMC himc; HWND wnd; wnd = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); himc = ImmGetContext(wnd); if (!himc) { DestroyWindow(wnd); return FALSE; } ImmReleaseContext(wnd, himc); DestroyWindow(wnd); return TRUE; } static BOOL init(void) { WNDCLASSEXA wc; HMODULE hmod,huser; hmod = GetModuleHandleA("imm32.dll"); huser = GetModuleHandleA("user32"); pImmAssociateContextEx = (void*)GetProcAddress(hmod, "ImmAssociateContextEx"); pImmIsUIMessageA = (void*)GetProcAddress(hmod, "ImmIsUIMessageA"); pSendInput = (void*)GetProcAddress(huser, "SendInput"); pNtUserAssociateInputContext = (void*)GetProcAddress(GetModuleHandleW(L"win32u.dll"), "NtUserAssociateInputContext"); wc.cbSize = sizeof(WNDCLASSEXA); wc.style = 0; wc.lpfnWndProc = wndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = GetModuleHandleA(NULL); wc.hIcon = LoadIconA(NULL, (LPCSTR)IDI_APPLICATION); wc.hCursor = LoadCursorA(NULL, (LPCSTR)IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = wndcls; wc.hIconSm = LoadIconA(NULL, (LPCSTR)IDI_APPLICATION); if (!RegisterClassExA(&wc)) return FALSE; hwnd = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); if (!hwnd) return FALSE; child = CreateWindowA("edit", "edit", WS_CHILD | WS_VISIBLE, 0, 0, 50, 50, hwnd, 0, 0, 0); if (!child) return FALSE; ShowWindow(hwnd, SW_SHOWNORMAL); UpdateWindow(hwnd); msg_spy_init(hwnd); return TRUE; } static void cleanup(void) { msg_spy_cleanup(); if (hwnd) DestroyWindow(hwnd); UnregisterClassA(wndcls, GetModuleHandleW(NULL)); } static void test_ImmNotifyIME(void) { static const char string[] = "wine"; char resstr[16] = ""; HIMC imc; BOOL ret; imc = ImmGetContext(hwnd); msg_spy_flush_msgs(); ret = ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); ok(broken(!ret) || ret, /* Vista+ */ "Canceling an empty composition string should succeed.\n"); ok(!msg_spy_find_msg(WM_IME_COMPOSITION), "Windows does not post " "WM_IME_COMPOSITION in response to NI_COMPOSITIONSTR / CPS_CANCEL, if " "the composition string being canceled is empty.\n"); ImmSetCompositionStringA(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); msg_spy_flush_msgs(); ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); msg_spy_flush_msgs(); /* behavior differs between win9x and NT */ ret = ImmGetCompositionStringA(imc, GCS_COMPSTR, resstr, sizeof(resstr)); ok(!ret, "After being cancelled the composition string is empty.\n"); msg_spy_flush_msgs(); ret = ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); ok(broken(!ret) || ret, /* Vista+ */ "Canceling an empty composition string should succeed.\n"); ok(!msg_spy_find_msg(WM_IME_COMPOSITION), "Windows does not post " "WM_IME_COMPOSITION in response to NI_COMPOSITIONSTR / CPS_CANCEL, if " "the composition string being canceled is empty.\n"); msg_spy_flush_msgs(); ImmReleaseContext(hwnd, imc); imc = ImmCreateContext(); ImmDestroyContext(imc); SetLastError(0xdeadbeef); ret = ImmNotifyIME((HIMC)0xdeadcafe, NI_COMPOSITIONSTR, CPS_CANCEL, 0); ok (ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08x!\n", ret); SetLastError(0xdeadbeef); ret = ImmNotifyIME(0x00000000, NI_COMPOSITIONSTR, CPS_CANCEL, 0); ok (ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_SUCCESS, "wrong last error %08x!\n", ret); SetLastError(0xdeadbeef); ret = ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_CANCEL, 0); ok (ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08x!\n", ret); } static struct { WNDPROC old_wnd_proc; BOOL catch_result_str; BOOL catch_ime_char; DWORD start; DWORD timer_id; } ime_composition_test; static LRESULT WINAPI test_ime_wnd_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_IME_COMPOSITION: if ((lParam & GCS_RESULTSTR) && !ime_composition_test.catch_result_str) { HWND hwndIme; WCHAR wstring[20]; HIMC imc; LONG size; LRESULT ret; hwndIme = ImmGetDefaultIMEWnd(hWnd); ok(hwndIme != NULL, "expected IME window existence\n"); ok(!ime_composition_test.catch_ime_char, "WM_IME_CHAR is sent\n"); ret = CallWindowProcA(ime_composition_test.old_wnd_proc, hWnd, msg, wParam, lParam); ok(ime_composition_test.catch_ime_char, "WM_IME_CHAR isn't sent\n"); ime_composition_test.catch_ime_char = FALSE; SendMessageA(hwndIme, msg, wParam, lParam); ok(!ime_composition_test.catch_ime_char, "WM_IME_CHAR is sent\n"); imc = ImmGetContext(hWnd); size = ImmGetCompositionStringW(imc, GCS_RESULTSTR, wstring, sizeof(wstring)); ok(size > 0, "ImmGetCompositionString(GCS_RESULTSTR) is %ld\n", size); ImmReleaseContext(hwnd, imc); ime_composition_test.catch_result_str = TRUE; return ret; } break; case WM_IME_CHAR: if (!ime_composition_test.catch_result_str) ime_composition_test.catch_ime_char = TRUE; break; case WM_TIMER: if (wParam == ime_composition_test.timer_id) { HWND parent = GetParent(hWnd); char title[64]; int left = 20 - (GetTickCount() - ime_composition_test.start) / 1000; wsprintfA(title, "%sLeft %d sec. - IME composition test", ime_composition_test.catch_result_str ? "[*] " : "", left); SetWindowTextA(parent, title); if (left <= 0) DestroyWindow(parent); else SetTimer(hWnd, wParam, 100, NULL); return TRUE; } break; } return CallWindowProcA(ime_composition_test.old_wnd_proc, hWnd, msg, wParam, lParam); } static void test_SCS_SETSTR(void) { HIMC imc; static const WCHAR string[] = {'w','i','n','e',0x65e5,0x672c,0x8a9e}; char cstring[20]; WCHAR wstring[20]; LONG len; LONG alen,wlen; BOOL ret; DWORD prop; imc = ImmGetContext(hwnd); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 0); if (!ret) { win_skip("Composition isn't supported\n"); ImmReleaseContext(hwnd, imc); return; } ret = ImmSetCompositionStringW(imc, SCS_SETSTR, NULL, 128, NULL, 128); ok(ret, "got error %lu.\n", GetLastError()); 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); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL, 2); ok(ret, "got error %lu.\n", GetLastError()); 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 */ if( alen && wlen) { len = ImmGetCompositionStringW(imc, GCS_COMPATTR, NULL, 0); ok(len*sizeof(WCHAR)==wlen,"GCS_COMPATTR(W) not returning correct count\n"); len = ImmGetCompositionStringA(imc, GCS_COMPATTR, NULL, 0); ok(len==alen,"GCS_COMPATTR(A) not returning correct count\n"); /* Get strings with exactly matching buffer sizes. */ memset(wstring, 0x1a, sizeof(wstring)); memset(cstring, 0x1a, sizeof(cstring)); len = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, alen); ok(len == alen, "Unexpected length %ld.\n", len); ok(cstring[alen] == 0x1a, "Unexpected buffer contents.\n"); len = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, wlen); ok(len == wlen, "Unexpected length %ld.\n", len); ok(wstring[wlen/sizeof(WCHAR)] == 0x1a1a, "Unexpected buffer contents.\n"); /* Get strings with exactly smaller buffer sizes. */ memset(wstring, 0x1a, sizeof(wstring)); memset(cstring, 0x1a, sizeof(cstring)); /* Returns 0 but still fills buffer. */ len = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, alen - 1); ok(!len, "Unexpected length %ld.\n", len); ok(cstring[0] == 'w', "Unexpected buffer contents %s.\n", cstring); len = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, wlen - 1); ok(len == wlen - 1, "Unexpected length %ld.\n", len); ok(!memcmp(wstring, string, wlen - 1), "Unexpected buffer contents.\n"); /* Get the size of the required output buffer. */ memset(wstring, 0x1a, sizeof(wstring)); memset(cstring, 0x1a, sizeof(cstring)); len = ImmGetCompositionStringA(imc, GCS_COMPSTR, cstring, 0); ok(len == alen, "Unexpected length %ld.\n", len); ok(cstring[0] == 0x1a, "Unexpected buffer contents %s.\n", cstring); len = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, 0); ok(len == wlen, "Unexpected length %ld.\n", len); ok(wstring[0] == 0x1a1a, "Unexpected buffer contents.\n"); } else win_skip("Composition string isn't available\n"); ImmReleaseContext(hwnd, imc); /* Test composition results input by IMM API */ prop = ImmGetProperty(GetKeyboardLayout(0), IGP_SETCOMPSTR); if (!(prop & SCS_CAP_COMPSTR)) { /* Wine's IME doesn't support SCS_SETSTR in ImmSetCompositionString */ skip("This IME doesn't support SCS_SETSTR\n"); } else { ime_composition_test.old_wnd_proc = (WNDPROC)SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)test_ime_wnd_proc); imc = ImmGetContext(hwnd); msg_spy_flush_msgs(); ret = ImmSetCompositionStringW(imc, SCS_SETSTR, string, sizeof(string), NULL,0); ok(ret, "ImmSetCompositionStringW failed\n"); wlen = ImmGetCompositionStringW(imc, GCS_COMPSTR, wstring, sizeof(wstring)); if (wlen > 0) { ret = ImmNotifyIME(imc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); ok(ret, "ImmNotifyIME(CPS_COMPLETE) failed\n"); msg_spy_flush_msgs(); ok(ime_composition_test.catch_result_str, "WM_IME_COMPOSITION(GCS_RESULTSTR) isn't sent\n"); } else win_skip("Composition string isn't available\n"); ImmReleaseContext(hwnd, imc); SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (LONG_PTR)ime_composition_test.old_wnd_proc); msg_spy_flush_msgs(); } /* Test composition results input by hand */ memset(&ime_composition_test, 0, sizeof(ime_composition_test)); if (winetest_interactive) { HWND hwndMain, hwndChild; MSG msg; const DWORD MY_TIMER = 0xcaffe; hwndMain = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "IME composition test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 320, 160, NULL, NULL, GetModuleHandleA(NULL), NULL); hwndChild = CreateWindowExA(0, "static", "Input a DBCS character here using IME.", WS_CHILD | WS_VISIBLE, 0, 0, 320, 100, hwndMain, NULL, GetModuleHandleA(NULL), NULL); ime_composition_test.old_wnd_proc = (WNDPROC)SetWindowLongPtrA(hwndChild, GWLP_WNDPROC, (LONG_PTR)test_ime_wnd_proc); SetFocus(hwndChild); ime_composition_test.timer_id = MY_TIMER; ime_composition_test.start = GetTickCount(); SetTimer(hwndChild, ime_composition_test.timer_id, 100, NULL); while (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); if (!IsWindow(hwndMain)) break; } if (!ime_composition_test.catch_result_str) skip("WM_IME_COMPOSITION(GCS_RESULTSTR) isn't tested\n"); msg_spy_flush_msgs(); } SetLastError(0xdeadbeef); imc = ImmGetContext(hwnd); ok(imc != 0, "ImmGetContext() failed. Last error: %lu\n", GetLastError()); if (!imc) return; ret = ImmSetCompositionStringW(imc, SCS_SETSTR, NULL, 0, NULL, 0); ok(broken(!ret) || ret, /* Vista+ */ "ImmSetCompositionStringW() failed.\n"); ret = ImmSetCompositionStringW(imc, SCS_SETSTR | SCS_CHANGEATTR, NULL, 0, NULL, 0); ok(!ret, "ImmSetCompositionStringW() succeeded.\n"); ret = ImmSetCompositionStringW(imc, SCS_SETSTR | SCS_CHANGECLAUSE, NULL, 0, NULL, 0); ok(!ret, "ImmSetCompositionStringW() succeeded.\n"); ret = ImmSetCompositionStringW(imc, SCS_CHANGEATTR | SCS_CHANGECLAUSE, NULL, 0, NULL, 0); ok(!ret, "ImmSetCompositionStringW() succeeded.\n"); ret = ImmSetCompositionStringW(imc, SCS_SETSTR | SCS_CHANGEATTR | SCS_CHANGECLAUSE, NULL, 0, NULL, 0); ok(!ret, "ImmSetCompositionStringW() succeeded.\n"); ImmReleaseContext(hwnd, imc); } static void test_ImmIME(void) { HIMC imc; imc = ImmGetContext(hwnd); if (imc) { BOOL rc; rc = ImmConfigureIMEA(imc, NULL, IME_CONFIG_REGISTERWORD, NULL); ok (rc == 0, "ImmConfigureIMEA did not fail\n"); rc = ImmConfigureIMEW(imc, NULL, IME_CONFIG_REGISTERWORD, NULL); ok (rc == 0, "ImmConfigureIMEW did not fail\n"); } ImmReleaseContext(hwnd,imc); } static void test_ImmAssociateContextEx(void) { HIMC imc; BOOL rc; if (!pImmAssociateContextEx) return; imc = ImmGetContext(hwnd); if (imc) { HIMC retimc, newimc; HWND focus; SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, TRUE); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, TRUE); ok(GetActiveWindow() == hwnd, "hwnd is not active\n"); newimc = ImmCreateContext(); ok(newimc != imc, "handles should not be the same\n"); rc = pImmAssociateContextEx(NULL, NULL, 0); ok(!rc, "ImmAssociateContextEx succeeded\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); rc = pImmAssociateContextEx(hwnd, NULL, 0); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); ok(rc, "ImmAssociateContextEx failed\n"); rc = pImmAssociateContextEx(NULL, imc, 0); ok(!rc, "ImmAssociateContextEx succeeded\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); rc = pImmAssociateContextEx(hwnd, imc, 0); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); ok(rc, "ImmAssociateContextEx failed\n"); retimc = ImmGetContext(hwnd); ok(retimc == imc, "handles should be the same\n"); ImmReleaseContext(hwnd,retimc); rc = pImmAssociateContextEx(hwnd, imc, 0); ok(rc, "ImmAssociateContextEx failed\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); rc = pImmAssociateContextEx(hwnd, newimc, 0); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); ok(rc, "ImmAssociateContextEx failed\n"); retimc = ImmGetContext(hwnd); ok(retimc == newimc, "handles should be the same\n"); ImmReleaseContext(hwnd,retimc); focus = CreateWindowA("button", "button", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(focus != NULL, "CreateWindow failed\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SetFocus(focus); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); rc = pImmAssociateContextEx(hwnd, imc, 0); ok(rc, "ImmAssociateContextEx failed\n"); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); DestroyWindow(focus); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SetFocus(child); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); rc = pImmAssociateContextEx(hwnd, newimc, 0); ok(rc, "ImmAssociateContextEx failed\n"); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); SetFocus(hwnd); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); rc = pImmAssociateContextEx(hwnd, NULL, IACE_DEFAULT); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); ok(rc, "ImmAssociateContextEx failed\n"); SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, FALSE); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, FALSE); } ImmReleaseContext(hwnd,imc); } /* similar to above, but using NtUserAssociateInputContext */ static void test_NtUserAssociateInputContext(void) { HIMC imc; UINT rc; if (!pNtUserAssociateInputContext) { win_skip("NtUserAssociateInputContext not available\n"); return; } imc = ImmGetContext(hwnd); if (imc) { HIMC retimc, newimc; HWND focus; SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, TRUE); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, TRUE); ok(GetActiveWindow() == hwnd, "hwnd is not active\n"); newimc = ImmCreateContext(); ok(newimc != imc, "handles should not be the same\n"); rc = pNtUserAssociateInputContext(NULL, NULL, 0); ok(rc == 2, "NtUserAssociateInputContext returned %x\n", rc); rc = pNtUserAssociateInputContext(hwnd, NULL, 0); ok(rc == 1, "NtUserAssociateInputContext returned %x\n", rc); rc = pNtUserAssociateInputContext(NULL, imc, 0); ok(rc == 2, "NtUserAssociateInputContext returned %x\n", rc); rc = pNtUserAssociateInputContext(hwnd, imc, 0); ok(rc == 1, "NtUserAssociateInputContext returned %x\n", rc); retimc = ImmGetContext(hwnd); ok(retimc == imc, "handles should be the same\n"); ImmReleaseContext(hwnd,retimc); rc = pNtUserAssociateInputContext(hwnd, imc, 0); ok(rc == 0, "NtUserAssociateInputContext returned %x\n", rc); rc = pNtUserAssociateInputContext(hwnd, newimc, 0); ok(rc == 1, "NtUserAssociateInputContext returned %x\n", rc); retimc = ImmGetContext(hwnd); ok(retimc == newimc, "handles should be the same\n"); ImmReleaseContext(hwnd,retimc); focus = CreateWindowA("button", "button", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(focus != NULL, "CreateWindow failed\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SetFocus(focus); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); rc = pNtUserAssociateInputContext(hwnd, imc, 0); ok(rc == 0, "NtUserAssociateInputContext returned %x\n", rc); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); DestroyWindow(focus); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); SetFocus(child); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); rc = pNtUserAssociateInputContext(hwnd, newimc, 0); ok(rc == 0, "NtUserAssociateInputContext returned %x\n", rc); SET_EXPECT(WM_IME_SETCONTEXT_ACTIVATE); SetFocus(hwnd); CHECK_CALLED(WM_IME_SETCONTEXT_ACTIVATE); rc = pNtUserAssociateInputContext(hwnd, NULL, IACE_DEFAULT); ok(rc == 1, "NtUserAssociateInputContext returned %x\n", rc); SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, FALSE); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, FALSE); } ImmReleaseContext(hwnd,imc); } struct test_cross_thread_himc_params { HWND hwnd; HANDLE event; HIMC himc[2]; INPUTCONTEXT *contexts[2]; }; static DWORD WINAPI test_cross_thread_himc_thread( void *arg ) { CANDIDATEFORM candidate = {.dwIndex = 1, .dwStyle = CFS_CANDIDATEPOS}; struct test_cross_thread_himc_params *params = arg; COMPOSITIONFORM composition = {0}; INPUTCONTEXT *contexts[2]; HIMC himc[2], tmp_himc; LOGFONTW fontW = {0}; HWND hwnd, tmp_hwnd; POINT pos = {0}; MSG msg; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok_ne( NULL, hwnd, HWND, "%p" ); himc[0] = ImmGetContext( hwnd ); ok_ne( NULL, himc[0], HIMC, "%p" ); contexts[0] = ImmLockIMC( himc[0] ); ok_ne( NULL, contexts[0], INPUTCONTEXT *, "%p" ); contexts[0]->hWnd = hwnd; tmp_hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); tmp_himc = ImmGetContext( tmp_hwnd ); ok_eq( himc[0], tmp_himc, HIMC, "%p" ); ok_ret( 1, ImmReleaseContext( tmp_hwnd, tmp_himc ) ); ok_ret( 1, DestroyWindow( tmp_hwnd ) ); himc[1] = ImmCreateContext(); ok_ne( NULL, himc[1], HIMC, "%p" ); contexts[1] = ImmLockIMC( himc[1] ); ok_ne( NULL, contexts[1], INPUTCONTEXT *, "%p" ); contexts[1]->hWnd = hwnd; ok_ret( 1, ImmSetOpenStatus( himc[0], 0xdeadbeef ) ); ok_ret( 1, ImmSetOpenStatus( himc[1], 0xfeedcafe ) ); ok_ret( 1, ImmSetCompositionWindow( himc[0], &composition ) ); ok_ret( 1, ImmSetCompositionWindow( himc[1], &composition ) ); ok_ret( 1, ImmSetCandidateWindow( himc[0], &candidate ) ); ok_ret( 1, ImmSetCandidateWindow( himc[1], &candidate ) ); ok_ret( 1, ImmSetStatusWindowPos( himc[0], &pos ) ); ok_ret( 1, ImmSetStatusWindowPos( himc[1], &pos ) ); ok_ret( 1, ImmSetCompositionFontW( himc[0], &fontW ) ); ok_ret( 1, ImmSetCompositionFontW( himc[1], &fontW ) ); params->hwnd = hwnd; params->himc[0] = himc[0]; params->himc[1] = himc[1]; params->contexts[0] = contexts[0]; params->contexts[1] = contexts[1]; SetEvent( params->event ); while (GetMessageW( &msg, 0, 0, 0 )) { TranslateMessage( &msg ); DispatchMessageW( &msg ); } ok_ret( 1, ImmUnlockIMC( himc[0] ) ); ok_ret( 1, ImmUnlockIMC( himc[1] ) ); ok_ret( 1, ImmDestroyContext( himc[1] ) ); ok_ret( 1, ImmReleaseContext( hwnd, himc[0] ) ); ok_ret( 0, DestroyWindow( hwnd ) ); return 1; } static void test_cross_thread_himc(void) { static const WCHAR comp_string[] = L"CompString"; RECONVERTSTRING reconv = {.dwSize = sizeof(RECONVERTSTRING)}; struct test_cross_thread_himc_params params; COMPOSITIONFORM composition = {0}; DWORD tid, conversion, sentence; IMECHARPOSITION char_pos = {0}; CANDIDATEFORM candidate = {0}; COMPOSITIONSTRING *string; HIMC himc[2], tmp_himc; INPUTCONTEXT *tmp_ctx; LOGFONTW fontW = {0}; LOGFONTA fontA = {0}; char buffer[512]; POINT pos = {0}; HANDLE thread; BYTE *dst; UINT ret; himc[0] = ImmGetContext( hwnd ); ok_ne( NULL, himc[0], HIMC, "%p" ); ok_ne( NULL, ImmLockIMC( himc[0] ), INPUTCONTEXT *, "%p" ); ok_ret( 1, ImmUnlockIMC( himc[0] ) ); params.event = CreateEventW(NULL, TRUE, FALSE, NULL); ok_ne( NULL, params.event, HANDLE, "%p" ); thread = CreateThread( NULL, 0, test_cross_thread_himc_thread, ¶ms, 0, &tid ); ok_ne( NULL, thread, HANDLE, "%p" ); WaitForSingleObject( params.event, INFINITE ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; tmp_himc = ImmGetContext( params.hwnd ); ok_ne( himc[0], tmp_himc, HIMC, "%p" ); ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); ok_ret( 1, ImmReleaseContext( params.hwnd, tmp_himc ) ); himc[1] = ImmCreateContext(); ok_ne( NULL, himc[1], HIMC, "%p" ); tmp_ctx = ImmLockIMC( himc[1] ); ok_ne( NULL, tmp_ctx, INPUTCONTEXT *, "%p" ); tmp_ctx->hCompStr = ImmReSizeIMCC( tmp_ctx->hCompStr, 512 ); ok_ne( NULL, tmp_ctx->hCompStr, HIMCC, "%p" ); string = ImmLockIMCC( tmp_ctx->hCompStr ); ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); string->dwSize = sizeof(COMPOSITIONSTRING); string->dwCompStrLen = wcslen( comp_string ); string->dwCompStrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompStrOffset; memcpy( dst, comp_string, string->dwCompStrLen * sizeof(WCHAR) ); string->dwSize += string->dwCompStrLen * sizeof(WCHAR); string->dwCompClauseLen = 2 * sizeof(DWORD); string->dwCompClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwCompClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = string->dwCompStrLen; string->dwSize += 2 * sizeof(DWORD); string->dwCompAttrLen = string->dwCompStrLen; string->dwCompAttrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompAttrOffset; memset( dst, ATTR_INPUT, string->dwCompStrLen ); string->dwSize += string->dwCompStrLen; ok_ret( 0, ImmUnlockIMCC( tmp_ctx->hCompStr ) ); ok_ret( 1, ImmUnlockIMC( himc[1] ) ); /* ImmLockIMC should succeed with cross thread HIMC */ tmp_ctx = ImmLockIMC( params.himc[0] ); ok_eq( params.contexts[0], tmp_ctx, INPUTCONTEXT *, "%p" ); ret = ImmGetIMCLockCount( params.himc[0] ); ok( ret >= 2, "got ret %u\n", ret ); tmp_ctx->hCompStr = ImmReSizeIMCC( tmp_ctx->hCompStr, 512 ); ok_ne( NULL, tmp_ctx->hCompStr, HIMCC, "%p" ); string = ImmLockIMCC( tmp_ctx->hCompStr ); ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); string->dwSize = sizeof(COMPOSITIONSTRING); string->dwCompStrLen = wcslen( comp_string ); string->dwCompStrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompStrOffset; memcpy( dst, comp_string, string->dwCompStrLen * sizeof(WCHAR) ); string->dwSize += string->dwCompStrLen * sizeof(WCHAR); string->dwCompClauseLen = 2 * sizeof(DWORD); string->dwCompClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwCompClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = string->dwCompStrLen; string->dwSize += 2 * sizeof(DWORD); string->dwCompAttrLen = string->dwCompStrLen; string->dwCompAttrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompAttrOffset; memset( dst, ATTR_INPUT, string->dwCompStrLen ); string->dwSize += string->dwCompStrLen; ok_ret( 0, ImmUnlockIMCC( tmp_ctx->hCompStr ) ); ok_ret( 1, ImmUnlockIMC( params.himc[0] ) ); tmp_ctx = ImmLockIMC( params.himc[1] ); ok_eq( params.contexts[1], tmp_ctx, INPUTCONTEXT *, "%p" ); ret = ImmGetIMCLockCount( params.himc[1] ); ok( ret >= 2, "got ret %u\n", ret ); ok_ret( 1, ImmUnlockIMC( params.himc[1] ) ); /* ImmSetActiveContext should succeed with cross thread HIMC */ SET_ENABLE( WM_IME_SETCONTEXT_DEACTIVATE, TRUE ); SET_ENABLE( WM_IME_SETCONTEXT_ACTIVATE, TRUE ); SET_EXPECT( WM_IME_SETCONTEXT_ACTIVATE ); ok_ret( 1, ImmSetActiveContext( hwnd, params.himc[0], TRUE ) ); CHECK_CALLED( WM_IME_SETCONTEXT_ACTIVATE ); SET_EXPECT( WM_IME_SETCONTEXT_DEACTIVATE ); ok_ret( 1, ImmSetActiveContext( hwnd, params.himc[0], FALSE ) ); CHECK_CALLED( WM_IME_SETCONTEXT_DEACTIVATE ); SET_ENABLE( WM_IME_SETCONTEXT_DEACTIVATE, FALSE ); SET_ENABLE( WM_IME_SETCONTEXT_ACTIVATE, FALSE ); /* ImmSetOpenStatus should fail with cross thread HIMC */ ok_ret( 1, ImmSetOpenStatus( himc[1], 0xdeadbeef ) ); ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( himc[1] ) ); ok_ret( 0, ImmSetOpenStatus( params.himc[0], TRUE ) ); ok_ret( 0, ImmSetOpenStatus( params.himc[1], TRUE ) ); ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( params.himc[0] ) ); ok_ret( (int)0xfeedcafe, ImmGetOpenStatus( params.himc[1] ) ); ok_ret( 0, ImmSetOpenStatus( params.himc[0], FALSE ) ); ok_ret( (int)0xdeadbeef, ImmGetOpenStatus( params.himc[0] ) ); /* ImmSetConversionStatus should fail with cross thread HIMC */ ok_ret( 1, ImmGetConversionStatus( himc[1], &conversion, &sentence ) ); ok_ret( 1, ImmSetConversionStatus( himc[1], conversion, sentence ) ); ok_ret( 1, ImmGetConversionStatus( params.himc[0], &conversion, &sentence ) ); ok_ret( 1, ImmGetConversionStatus( params.himc[1], &conversion, &sentence ) ); ok_ret( 0, ImmSetConversionStatus( params.himc[0], conversion, sentence ) ); ok_ret( 0, ImmSetConversionStatus( params.himc[1], conversion, sentence ) ); /* ImmSetCompositionFont(W|A) should fail with cross thread HIMC */ ok_ret( 1, ImmSetCompositionFontA( himc[1], &fontA ) ); ok_ret( 1, ImmGetCompositionFontA( himc[1], &fontA ) ); ok_ret( 1, ImmSetCompositionFontW( himc[1], &fontW ) ); ok_ret( 1, ImmGetCompositionFontW( himc[1], &fontW ) ); ok_ret( 0, ImmSetCompositionFontA( params.himc[0], &fontA ) ); ok_ret( 0, ImmSetCompositionFontA( params.himc[1], &fontA ) ); ok_ret( 1, ImmGetCompositionFontA( params.himc[0], &fontA ) ); ok_ret( 1, ImmGetCompositionFontA( params.himc[1], &fontA ) ); ok_ret( 0, ImmSetCompositionFontW( params.himc[0], &fontW ) ); ok_ret( 0, ImmSetCompositionFontW( params.himc[1], &fontW ) ); ok_ret( 1, ImmGetCompositionFontW( params.himc[0], &fontW ) ); ok_ret( 1, ImmGetCompositionFontW( params.himc[1], &fontW ) ); /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ ok_ret( 0, ImmRequestMessageW( himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontW ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_COMPOSITIONFONT, (LPARAM)&fontA ) ); ok_seq( empty_sequence ); /* ImmSetCompositionString(W|A) should fail with cross thread HIMC */ ok_ret( 10, ImmGetCompositionStringA( himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); ok_ret( 20, ImmGetCompositionStringW( himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); ok_ret( 1, ImmSetCompositionStringA( himc[1], SCS_SETSTR, "a", 2, NULL, 0 ) ); ok_ret( 1, ImmSetCompositionStringW( himc[1], SCS_SETSTR, L"a", 4, NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringA( params.himc[0], SCS_SETSTR, "a", 2, NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringA( params.himc[1], SCS_SETSTR, "a", 2, NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringW( params.himc[0], SCS_SETSTR, L"a", 4, NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringW( params.himc[1], SCS_SETSTR, L"a", 4, NULL, 0 ) ); ok_ret( 10, ImmGetCompositionStringA( params.himc[0], GCS_COMPSTR, buffer, sizeof(buffer) ) ); ok_ret( 0, ImmGetCompositionStringA( params.himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); ok_ret( 20, ImmGetCompositionStringW( params.himc[0], GCS_COMPSTR, buffer, sizeof(buffer) ) ); ok_ret( 0, ImmGetCompositionStringW( params.himc[1], GCS_COMPSTR, buffer, sizeof(buffer) ) ); /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ ok_ret( 0, ImmRequestMessageW( himc[1], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageW( himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_RECONVERTSTRING, 0 ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( himc[1], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageW( himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_DOCUMENTFEED, 0 ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); ok_seq( empty_sequence ); /* ImmSetCompositionWindow should fail with cross thread HIMC */ ok_ret( 1, ImmSetCompositionWindow( himc[1], &composition ) ); ok_ret( 1, ImmGetCompositionWindow( himc[1], &composition ) ); ok_ret( 0, ImmSetCompositionWindow( params.himc[0], &composition ) ); ok_ret( 0, ImmSetCompositionWindow( params.himc[1], &composition ) ); ok_ret( 1, ImmGetCompositionWindow( params.himc[0], &composition ) ); ok_ret( 1, ImmGetCompositionWindow( params.himc[1], &composition ) ); /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ ok_ret( 0, ImmRequestMessageW( himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_COMPOSITIONWINDOW, (LPARAM)&composition ) ); ok_seq( empty_sequence ); /* ImmSetCandidateWindow should fail with cross thread HIMC */ ok_ret( 1, ImmSetCandidateWindow( himc[1], &candidate ) ); ok_ret( 1, ImmGetCandidateWindow( himc[1], 0, &candidate ) ); ok_ret( 1, ImmGetCandidateWindow( params.himc[0], 1, &candidate ) ); ok_ret( 1, ImmGetCandidateWindow( params.himc[1], 1, &candidate ) ); ok_ret( 0, ImmSetCandidateWindow( params.himc[0], &candidate ) ); ok_ret( 0, ImmSetCandidateWindow( params.himc[1], &candidate ) ); /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ candidate.dwIndex = -1; ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); candidate.dwIndex = 0; ok_ret( 0, ImmRequestMessageW( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_CANDIDATEWINDOW, (LPARAM)&candidate ) ); ok_seq( empty_sequence ); /* ImmSetStatusWindowPos should fail with cross thread HIMC */ ok_ret( 1, ImmSetStatusWindowPos( himc[1], &pos ) ); ok_ret( 1, ImmGetStatusWindowPos( himc[1], &pos ) ); ok_ret( 0, ImmSetStatusWindowPos( params.himc[0], &pos ) ); ok_ret( 0, ImmSetStatusWindowPos( params.himc[1], &pos ) ); ok_ret( 1, ImmGetStatusWindowPos( params.himc[0], &pos ) ); ok_ret( 1, ImmGetStatusWindowPos( params.himc[1], &pos ) ); /* ImmRequestMessage(W|A) should fail with cross thread HIMC */ ok_ret( 0, ImmRequestMessageW( himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_ret( 0, ImmRequestMessageA( himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_ret( 0, ImmRequestMessageW( params.himc[0], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_ret( 0, ImmRequestMessageA( params.himc[0], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_ret( 0, ImmRequestMessageW( params.himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_ret( 0, ImmRequestMessageA( params.himc[1], IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); ok_seq( empty_sequence ); /* ImmGenerateMessage should fail with cross thread HIMC */ ok_ret( 1, ImmGenerateMessage( himc[1] ) ); ok_ret( 0, ImmGenerateMessage( params.himc[0] ) ); ok_ret( 0, ImmGenerateMessage( params.himc[1] ) ); /* ImmAssociateContext should fail with cross thread HWND or HIMC */ tmp_himc = ImmAssociateContext( hwnd, params.himc[0] ); ok_eq( NULL, tmp_himc, HIMC, "%p" ); tmp_himc = ImmGetContext( hwnd ); ok_eq( himc[0], tmp_himc, HIMC, "%p" ); ok_ret( 1, ImmReleaseContext( hwnd, tmp_himc ) ); tmp_himc = ImmAssociateContext( hwnd, params.himc[1] ); ok_eq( NULL, tmp_himc, HIMC, "%p" ); tmp_himc = ImmGetContext( hwnd ); ok_eq( himc[0], tmp_himc, HIMC, "%p" ); ok_ret( 1, ImmReleaseContext( hwnd, tmp_himc ) ); tmp_himc = ImmAssociateContext( params.hwnd, params.himc[1] ); ok_eq( NULL, tmp_himc, HIMC, "%p" ); tmp_himc = ImmGetContext( params.hwnd ); ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); ok_ret( 1, ImmReleaseContext( params.hwnd, tmp_himc ) ); /* ImmAssociateContext should succeed with cross thread HWND and NULL HIMC */ tmp_himc = ImmAssociateContext( params.hwnd, NULL ); ok_eq( params.himc[0], tmp_himc, HIMC, "%p" ); tmp_himc = ImmGetContext( params.hwnd ); ok_eq( NULL, tmp_himc, HIMC, "%p" ); /* ImmReleaseContext / ImmDestroyContext should fail with cross thread HIMC */ ok_ret( 1, ImmReleaseContext( params.hwnd, params.himc[0] ) ); ok_ret( 0, ImmDestroyContext( params.himc[1] ) ); /* ImmGetContext should fail with another process HWND */ tmp_himc = ImmGetContext( GetDesktopWindow() ); ok_eq( NULL, tmp_himc, HIMC, "%p" ); ok_ret( 0, SendMessageW( params.hwnd, WM_CLOSE, 0, 0 ) ); ok_ret( 1, PostThreadMessageW( tid, WM_QUIT, 1, 0 ) ); ok_ret( 0, WaitForSingleObject( thread, 5000 ) ); ok_ret( 1, CloseHandle( thread ) ); ok_ret( 1, CloseHandle( params.event ) ); ok_ret( 1, ImmReleaseContext( hwnd, himc[0] ) ); ok_ret( 1, ImmDestroyContext( himc[1] ) ); } static void test_ImmIsUIMessage(void) { struct test { UINT msg; BOOL ret; }; static const struct test tests[] = { { WM_MOUSEMOVE, FALSE }, { WM_IME_STARTCOMPOSITION, TRUE }, { WM_IME_ENDCOMPOSITION, TRUE }, { WM_IME_COMPOSITION, TRUE }, { WM_IME_SETCONTEXT, TRUE }, { WM_IME_NOTIFY, TRUE }, { WM_IME_CONTROL, FALSE }, { WM_IME_COMPOSITIONFULL, TRUE }, { WM_IME_SELECT, TRUE }, { WM_IME_CHAR, FALSE }, { 0x287 /* FIXME */, TRUE }, { WM_IME_REQUEST, FALSE }, { WM_IME_KEYDOWN, FALSE }, { WM_IME_KEYUP, FALSE }, { 0, FALSE } /* mark the end */ }; UINT WM_MSIME_SERVICE = RegisterWindowMessageA("MSIMEService"); UINT WM_MSIME_RECONVERTOPTIONS = RegisterWindowMessageA("MSIMEReconvertOptions"); UINT WM_MSIME_MOUSE = RegisterWindowMessageA("MSIMEMouseOperation"); UINT WM_MSIME_RECONVERTREQUEST = RegisterWindowMessageA("MSIMEReconvertRequest"); UINT WM_MSIME_RECONVERT = RegisterWindowMessageA("MSIMEReconvert"); UINT WM_MSIME_QUERYPOSITION = RegisterWindowMessageA("MSIMEQueryPosition"); UINT WM_MSIME_DOCUMENTFEED = RegisterWindowMessageA("MSIMEDocumentFeed"); const struct test *test; BOOL ret; if (!pImmIsUIMessageA) return; for (test = tests; test->msg; test++) { msg_spy_flush_msgs(); ret = pImmIsUIMessageA(NULL, test->msg, 0, 0); ok(ret == test->ret, "ImmIsUIMessageA returned %x for %x\n", ret, test->msg); ok(!msg_spy_find_msg(test->msg), "Windows does not send 0x%x for NULL hwnd\n", test->msg); ret = pImmIsUIMessageA(hwnd, test->msg, 0, 0); ok(ret == test->ret, "ImmIsUIMessageA returned %x for %x\n", ret, test->msg); if (ret) ok(msg_spy_find_msg(test->msg) != NULL, "Windows does send 0x%x\n", test->msg); else ok(!msg_spy_find_msg(test->msg), "Windows does not send 0x%x\n", test->msg); } ret = pImmIsUIMessageA(NULL, WM_MSIME_SERVICE, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_SERVICE\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_RECONVERTOPTIONS, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_RECONVERTOPTIONS\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_MOUSE, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_MOUSE\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_RECONVERTREQUEST, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_RECONVERTREQUEST\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_RECONVERT, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_RECONVERT\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_QUERYPOSITION, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_QUERYPOSITION\n"); ret = pImmIsUIMessageA(NULL, WM_MSIME_DOCUMENTFEED, 0, 0); ok(!ret, "ImmIsUIMessageA returned TRUE for WM_MSIME_DOCUMENTFEED\n"); } static void test_ImmGetContext(void) { HIMC himc; DWORD err; SetLastError(0xdeadbeef); himc = ImmGetContext((HWND)0xffffffff); err = GetLastError(); ok(himc == NULL, "ImmGetContext succeeded\n"); ok(err == ERROR_INVALID_WINDOW_HANDLE, "got %lu\n", err); himc = ImmGetContext(hwnd); ok(himc != NULL, "ImmGetContext failed\n"); ok(ImmReleaseContext(hwnd, himc), "ImmReleaseContext failed\n"); } static LRESULT (WINAPI *old_imm_wnd_proc)(HWND, UINT, WPARAM, LPARAM); static LRESULT WINAPI imm_wnd_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { ok(msg != WM_DESTROY, "got WM_DESTROY message\n"); return old_imm_wnd_proc(hwnd, msg, wparam, lparam); } static HWND thread_ime_wnd; static DWORD WINAPI test_ImmGetDefaultIMEWnd_thread(void *arg) { CreateWindowA("static", "static", WS_POPUP, 0, 0, 1, 1, NULL, NULL, NULL, NULL); thread_ime_wnd = ImmGetDefaultIMEWnd(0); ok(thread_ime_wnd != 0, "ImmGetDefaultIMEWnd returned NULL\n"); old_imm_wnd_proc = (void*)SetWindowLongPtrW(thread_ime_wnd, GWLP_WNDPROC, (LONG_PTR)imm_wnd_proc); return 0; } static void test_ImmDefaultHwnd(void) { HIMC imc1, imc2, imc3; HWND def1, def3; HANDLE thread; HWND hwnd; char title[16]; LONG style; hwnd = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); ShowWindow(hwnd, SW_SHOWNORMAL); imc1 = ImmGetContext(hwnd); if (!imc1) { win_skip("IME support not implemented\n"); return; } def1 = ImmGetDefaultIMEWnd(hwnd); GetWindowTextA(def1, title, sizeof(title)); ok(!strcmp(title, "Default IME"), "got %s\n", title); style = GetWindowLongA(def1, GWL_STYLE); ok(style == (WS_DISABLED | WS_POPUP | WS_CLIPSIBLINGS), "got %08lx\n", style); style = GetWindowLongA(def1, GWL_EXSTYLE); ok(style == 0, "got %08lx\n", style); imc2 = ImmCreateContext(); ImmSetOpenStatus(imc2, TRUE); imc3 = ImmGetContext(hwnd); def3 = ImmGetDefaultIMEWnd(hwnd); ok(def3 == def1, "Default IME window should not change\n"); ok(imc1 == imc3, "IME context should not change\n"); ImmSetOpenStatus(imc2, FALSE); thread = CreateThread(NULL, 0, test_ImmGetDefaultIMEWnd_thread, NULL, 0, NULL); WaitForSingleObject(thread, INFINITE); ok(thread_ime_wnd != def1, "thread_ime_wnd == def1\n"); ok(!IsWindow(thread_ime_wnd), "thread_ime_wnd was not destroyed\n"); CloseHandle(thread); ImmReleaseContext(hwnd, imc1); ImmReleaseContext(hwnd, imc3); ImmDestroyContext(imc2); DestroyWindow(hwnd); } static BOOL CALLBACK is_ime_window_proc(HWND hWnd, LPARAM param) { WCHAR class_nameW[16]; HWND *ime_window = (HWND *)param; if (GetClassNameW(hWnd, class_nameW, ARRAY_SIZE(class_nameW)) && !lstrcmpW(class_nameW, L"IME")) { *ime_window = hWnd; return FALSE; } return TRUE; } static HWND get_ime_window(void) { HWND ime_window = NULL; EnumThreadWindows(GetCurrentThreadId(), is_ime_window_proc, (LPARAM)&ime_window); return ime_window; } struct testcase_ime_window { BOOL visible; BOOL top_level_window; }; static DWORD WINAPI test_default_ime_window_cb(void *arg) { struct testcase_ime_window *testcase = (struct testcase_ime_window *)arg; DWORD visible = testcase->visible ? WS_VISIBLE : 0; HWND hwnd1, hwnd2, default_ime_wnd, ime_wnd; ok(!get_ime_window(), "Expected no IME windows\n"); if (testcase->top_level_window) { test_phase = FIRST_WINDOW; hwnd1 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); } else { hwnd1 = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "Wine imm32.dll test", WS_CHILD | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 24, hwnd, NULL, GetModuleHandleW(NULL), NULL); } ime_wnd = get_ime_window(); ok(ime_wnd != NULL, "Expected IME window existence\n"); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd1); ok(ime_wnd == default_ime_wnd, "Expected %p, got %p\n", ime_wnd, default_ime_wnd); test_phase = SECOND_WINDOW; hwnd2 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); DestroyWindow(hwnd2); ok(IsWindow(ime_wnd) || broken(!testcase->visible /* Vista */) || broken(!testcase->top_level_window /* Vista */) , "Expected IME window existence\n"); DestroyWindow(hwnd1); flaky ok(!IsWindow(ime_wnd), "Expected no IME windows\n"); return 1; } static DWORD WINAPI test_default_ime_window_cancel_cb(void *arg) { struct testcase_ime_window *testcase = (struct testcase_ime_window *)arg; DWORD visible = testcase->visible ? WS_VISIBLE : 0; HWND hwnd1, hwnd2, default_ime_wnd, ime_wnd; ok(!get_ime_window(), "Expected no IME windows\n"); test_phase = NCCREATE_CANCEL; hwnd1 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); ok(hwnd1 == NULL, "creation succeeded, got %p\n", hwnd1); ok(!get_ime_window(), "Expected no IME windows\n"); test_phase = CREATE_CANCEL; hwnd1 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); ok(hwnd1 == NULL, "creation succeeded, got %p\n", hwnd1); ok(!get_ime_window(), "Expected no IME windows\n"); test_phase = FIRST_WINDOW; hwnd2 = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | visible, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); ime_wnd = get_ime_window(); ok(ime_wnd != NULL, "Expected IME window existence\n"); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd2); ok(ime_wnd == default_ime_wnd, "Expected %p, got %p\n", ime_wnd, default_ime_wnd); DestroyWindow(hwnd2); ok(!IsWindow(ime_wnd), "Expected no IME windows\n"); return 1; } static DWORD WINAPI test_default_ime_disabled_cb(void *arg) { HWND hWnd, default_ime_wnd; ok(!get_ime_window(), "Expected no IME windows\n"); ImmDisableIME(GetCurrentThreadId()); test_phase = IME_DISABLED; hWnd = CreateWindowExA(WS_EX_CLIENTEDGE, wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); default_ime_wnd = ImmGetDefaultIMEWnd(hWnd); ok(!default_ime_wnd, "Expected no IME windows\n"); DestroyWindow(hWnd); return 1; } static DWORD WINAPI test_default_ime_with_message_only_window_cb(void *arg) { HWND hwnd1, hwnd2, default_ime_wnd; /* Message-only window doesn't create associated IME window. */ test_phase = PHASE_UNKNOWN; hwnd1 = CreateWindowA(wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd1); ok(!default_ime_wnd, "Expected no IME windows, got %p\n", default_ime_wnd); /* Setting message-only window as owner at creation, doesn't create associated IME window. */ hwnd2 = CreateWindowA(wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, hwnd1, NULL, GetModuleHandleW(NULL), NULL); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd2); todo_wine ok(!default_ime_wnd || broken(IsWindow(default_ime_wnd)), "Expected no IME windows, got %p\n", default_ime_wnd); DestroyWindow(hwnd2); DestroyWindow(hwnd1); /* Making window message-only after creation, doesn't disassociate IME window. */ hwnd1 = CreateWindowA(wndcls, "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleW(NULL), NULL); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd1); ok(IsWindow(default_ime_wnd), "Expected IME window existence\n"); SetParent(hwnd1, HWND_MESSAGE); default_ime_wnd = ImmGetDefaultIMEWnd(hwnd1); ok(IsWindow(default_ime_wnd), "Expected IME window existence\n"); DestroyWindow(hwnd1); return 1; } static void test_default_ime_window_creation(void) { HANDLE thread; size_t i; struct testcase_ime_window testcases[] = { /* visible, top-level window */ { TRUE, TRUE }, { FALSE, TRUE }, { TRUE, FALSE }, { FALSE, FALSE } }; for (i = 0; i < ARRAY_SIZE(testcases); i++) { thread = CreateThread(NULL, 0, test_default_ime_window_cb, &testcases[i], 0, NULL); ok(thread != NULL, "CreateThread failed with error %lu\n", GetLastError()); while (MsgWaitForMultipleObjects(1, &thread, FALSE, INFINITE, QS_ALLINPUT) == WAIT_OBJECT_0 + 1) { MSG msg; while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessageA(&msg); } } CloseHandle(thread); if (testcases[i].top_level_window) { thread = CreateThread(NULL, 0, test_default_ime_window_cancel_cb, &testcases[i], 0, NULL); ok(thread != NULL, "CreateThread failed with error %lu\n", GetLastError()); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); } } thread = CreateThread(NULL, 0, test_default_ime_disabled_cb, NULL, 0, NULL); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); thread = CreateThread(NULL, 0, test_default_ime_with_message_only_window_cb, NULL, 0, NULL); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); test_phase = PHASE_UNKNOWN; } static void test_ImmGetIMCLockCount(void) { HIMC imc; DWORD count, ret, i; INPUTCONTEXT *ic; imc = ImmCreateContext(); ImmDestroyContext(imc); SetLastError(0xdeadbeef); count = ImmGetIMCLockCount((HIMC)0xdeadcafe); ok(count == 0, "Invalid IMC should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); count = ImmGetIMCLockCount(0x00000000); ok(count == 0, "NULL IMC should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "Last Error should remain unchanged: %08lx\n",ret); count = ImmGetIMCLockCount(imc); ok(count == 0, "Destroyed IMC should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); imc = ImmCreateContext(); count = ImmGetIMCLockCount(imc); ok(count == 0, "expect 0, returned %ld\n", count); ic = ImmLockIMC(imc); ok(ic != NULL, "ImmLockIMC failed!\n"); count = ImmGetIMCLockCount(imc); ok(count == 1, "expect 1, returned %ld\n", count); ret = ImmUnlockIMC(imc); ok(ret == TRUE, "expect TRUE, ret %ld\n", ret); count = ImmGetIMCLockCount(imc); ok(count == 0, "expect 0, returned %ld\n", count); ret = ImmUnlockIMC(imc); ok(ret == TRUE, "expect TRUE, ret %ld\n", ret); count = ImmGetIMCLockCount(imc); ok(count == 0, "expect 0, returned %ld\n", count); for (i = 0; i < GMEM_LOCKCOUNT * 2; i++) { ic = ImmLockIMC(imc); ok(ic != NULL, "ImmLockIMC failed!\n"); } count = ImmGetIMCLockCount(imc); todo_wine ok(count == GMEM_LOCKCOUNT, "expect GMEM_LOCKCOUNT, returned %ld\n", count); for (i = 0; i < GMEM_LOCKCOUNT - 1; i++) ImmUnlockIMC(imc); count = ImmGetIMCLockCount(imc); todo_wine ok(count == 1, "expect 1, returned %ld\n", count); ImmUnlockIMC(imc); count = ImmGetIMCLockCount(imc); todo_wine ok(count == 0, "expect 0, returned %ld\n", count); ImmDestroyContext(imc); } static void test_ImmGetIMCCLockCount(void) { HIMCC imcc; DWORD count, g_count, i; BOOL ret; VOID *p; imcc = ImmCreateIMCC(sizeof(CANDIDATEINFO)); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "expect 0, returned %ld\n", count); ImmLockIMCC(imcc); count = ImmGetIMCCLockCount(imcc); ok(count == 1, "expect 1, returned %ld\n", count); ret = ImmUnlockIMCC(imcc); ok(ret == FALSE, "expect FALSE, ret %d\n", ret); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "expect 0, returned %ld\n", count); ret = ImmUnlockIMCC(imcc); ok(ret == FALSE, "expect FALSE, ret %d\n", ret); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "expect 0, returned %ld\n", count); p = ImmLockIMCC(imcc); ok(GlobalHandle(p) == imcc, "expect %p, returned %p\n", imcc, GlobalHandle(p)); for (i = 0; i < GMEM_LOCKCOUNT * 2; i++) { ImmLockIMCC(imcc); count = ImmGetIMCCLockCount(imcc); g_count = GlobalFlags(imcc) & GMEM_LOCKCOUNT; ok(count == g_count, "count %ld, g_count %ld\n", count, g_count); } count = ImmGetIMCCLockCount(imcc); ok(count == GMEM_LOCKCOUNT, "expect GMEM_LOCKCOUNT, returned %ld\n", count); for (i = 0; i < GMEM_LOCKCOUNT - 1; i++) GlobalUnlock(imcc); count = ImmGetIMCCLockCount(imcc); ok(count == 1, "expect 1, returned %ld\n", count); GlobalUnlock(imcc); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "expect 0, returned %ld\n", count); ImmDestroyIMCC(imcc); } static void test_ImmDestroyContext(void) { HIMC imc; DWORD ret, count; INPUTCONTEXT *ic; imc = ImmCreateContext(); count = ImmGetIMCLockCount(imc); ok(count == 0, "expect 0, returned %ld\n", count); ic = ImmLockIMC(imc); ok(ic != NULL, "ImmLockIMC failed!\n"); count = ImmGetIMCLockCount(imc); ok(count == 1, "expect 1, returned %ld\n", count); ret = ImmDestroyContext(imc); ok(ret == TRUE, "Destroy a locked IMC should success!\n"); ic = ImmLockIMC(imc); ok(ic == NULL, "Lock a destroyed IMC should fail!\n"); ret = ImmUnlockIMC(imc); ok(ret == FALSE, "Unlock a destroyed IMC should fail!\n"); count = ImmGetIMCLockCount(imc); ok(count == 0, "Get lock count of a destroyed IMC should return 0!\n"); SetLastError(0xdeadbeef); ret = ImmDestroyContext(imc); ok(ret == FALSE, "Destroy a destroyed IMC should fail!\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); } static void test_ImmDestroyIMCC(void) { HIMCC imcc; DWORD ret, count, size; VOID *p; imcc = ImmCreateIMCC(sizeof(CANDIDATEINFO)); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "expect 0, returned %ld\n", count); p = ImmLockIMCC(imcc); ok(p != NULL, "ImmLockIMCC failed!\n"); count = ImmGetIMCCLockCount(imcc); ok(count == 1, "expect 1, returned %ld\n", count); size = ImmGetIMCCSize(imcc); ok(size == sizeof(CANDIDATEINFO), "returned %ld\n", size); p = ImmDestroyIMCC(imcc); ok(p == NULL, "Destroy a locked IMCC should success!\n"); p = ImmLockIMCC(imcc); ok(p == NULL, "Lock a destroyed IMCC should fail!\n"); ret = ImmUnlockIMCC(imcc); ok(ret == FALSE, "Unlock a destroyed IMCC should return FALSE!\n"); count = ImmGetIMCCLockCount(imcc); ok(count == 0, "Get lock count of a destroyed IMCC should return 0!\n"); size = ImmGetIMCCSize(imcc); ok(size == 0, "Get size of a destroyed IMCC should return 0!\n"); SetLastError(0xdeadbeef); p = ImmDestroyIMCC(imcc); ok(p != NULL, "returned NULL\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); } static void test_ImmMessages(void) { CANDIDATEFORM cf; imm_msgs *msg; HWND defwnd; HIMC imc; UINT idx = 0; LPINPUTCONTEXT lpIMC; LPTRANSMSG lpTransMsg; HWND hwnd = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "Wine imm32.dll test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, GetModuleHandleA(NULL), NULL); ShowWindow(hwnd, SW_SHOWNORMAL); defwnd = ImmGetDefaultIMEWnd(hwnd); imc = ImmGetContext(hwnd); ImmSetOpenStatus(imc, TRUE); msg_spy_flush_msgs(); SendMessageA(defwnd, WM_IME_CONTROL, IMC_GETCANDIDATEPOS, (LPARAM)&cf ); do { msg = msg_spy_find_next_msg(WM_IME_CONTROL,&idx); if (msg) ok(!msg->post, "Message should not be posted\n"); } while (msg); msg_spy_flush_msgs(); lpIMC = ImmLockIMC(imc); lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + 1) * sizeof(TRANSMSG)); lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf); lpTransMsg += lpIMC->dwNumMsgBuf; lpTransMsg->message = WM_IME_STARTCOMPOSITION; lpTransMsg->wParam = 0; lpTransMsg->lParam = 0; ImmUnlockIMCC(lpIMC->hMsgBuf); lpIMC->dwNumMsgBuf++; ImmUnlockIMC(imc); ImmGenerateMessage(imc); idx = 0; do { msg = msg_spy_find_next_msg(WM_IME_STARTCOMPOSITION, &idx); if (msg) ok(!msg->post, "Message should not be posted\n"); } while (msg); msg_spy_flush_msgs(); lpIMC = ImmLockIMC(imc); lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + 1) * sizeof(TRANSMSG)); lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf); lpTransMsg += lpIMC->dwNumMsgBuf; lpTransMsg->message = WM_IME_COMPOSITION; lpTransMsg->wParam = 0; lpTransMsg->lParam = 0; ImmUnlockIMCC(lpIMC->hMsgBuf); lpIMC->dwNumMsgBuf++; ImmUnlockIMC(imc); ImmGenerateMessage(imc); idx = 0; do { msg = msg_spy_find_next_msg(WM_IME_COMPOSITION, &idx); if (msg) ok(!msg->post, "Message should not be posted\n"); } while (msg); msg_spy_flush_msgs(); lpIMC = ImmLockIMC(imc); lpIMC->hMsgBuf = ImmReSizeIMCC(lpIMC->hMsgBuf, (lpIMC->dwNumMsgBuf + 1) * sizeof(TRANSMSG)); lpTransMsg = ImmLockIMCC(lpIMC->hMsgBuf); lpTransMsg += lpIMC->dwNumMsgBuf; lpTransMsg->message = WM_IME_ENDCOMPOSITION; lpTransMsg->wParam = 0; lpTransMsg->lParam = 0; ImmUnlockIMCC(lpIMC->hMsgBuf); lpIMC->dwNumMsgBuf++; ImmUnlockIMC(imc); ImmGenerateMessage(imc); idx = 0; do { msg = msg_spy_find_next_msg(WM_IME_ENDCOMPOSITION, &idx); if (msg) ok(!msg->post, "Message should not be posted\n"); } while (msg); msg_spy_flush_msgs(); ImmSetOpenStatus(imc, FALSE); ImmReleaseContext(hwnd, imc); DestroyWindow(hwnd); } static LRESULT CALLBACK processkey_wnd_proc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ) { return DefWindowProcW(hWnd, msg, wParam, lParam); } static void test_ime_processkey(void) { MSG msg; WNDCLASSW wclass; HANDLE hInstance = GetModuleHandleW(NULL); TEST_INPUT inputs[2]; HIMC imc; INT rc; HWND hWndTest; wclass.lpszClassName = L"ProcessKeyTestClass"; wclass.style = CS_HREDRAW | CS_VREDRAW; wclass.lpfnWndProc = processkey_wnd_proc; wclass.hInstance = hInstance; wclass.hIcon = LoadIconW(0, (LPCWSTR)IDI_APPLICATION); wclass.hCursor = LoadCursorW( NULL, (LPCWSTR)IDC_ARROW); wclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wclass.lpszMenuName = 0; wclass.cbClsExtra = 0; wclass.cbWndExtra = 0; if(!RegisterClassW(&wclass)){ win_skip("Failed to register window.\n"); return; } /* create the test window that will receive the keystrokes */ hWndTest = CreateWindowW(wclass.lpszClassName, L"ProcessKey", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 100, 100, NULL, NULL, hInstance, NULL); ShowWindow(hWndTest, SW_SHOW); SetWindowPos(hWndTest, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE); SetForegroundWindow(hWndTest); UpdateWindow(hWndTest); imc = ImmGetContext(hWndTest); if (!imc) { win_skip("IME not supported\n"); DestroyWindow(hWndTest); return; } rc = ImmSetOpenStatus(imc, TRUE); if (rc != TRUE) { win_skip("Unable to open IME\n"); ImmReleaseContext(hWndTest, imc); DestroyWindow(hWndTest); return; } /* flush pending messages */ while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageW(&msg); SetFocus(hWndTest); /* init input data that never changes */ inputs[1].type = inputs[0].type = INPUT_KEYBOARD; inputs[1].u.ki.dwExtraInfo = inputs[0].u.ki.dwExtraInfo = 0; inputs[1].u.ki.time = inputs[0].u.ki.time = 0; /* Pressing a key */ inputs[0].u.ki.wVk = 0x41; inputs[0].u.ki.wScan = 0x1e; inputs[0].u.ki.dwFlags = 0x0; pSendInput(1, (INPUT*)inputs, sizeof(INPUT)); while(PeekMessageW(&msg, hWndTest, 0, 0, PM_NOREMOVE)) { if(msg.message != WM_KEYDOWN) PeekMessageW(&msg, hWndTest, 0, 0, PM_REMOVE); else { ok(msg.wParam != VK_PROCESSKEY,"Incorrect ProcessKey Found\n"); PeekMessageW(&msg, hWndTest, 0, 0, PM_REMOVE); if(msg.wParam == VK_PROCESSKEY) trace("ProcessKey was correctly found\n"); } TranslateMessage(&msg); /* test calling TranslateMessage multiple times */ TranslateMessage(&msg); DispatchMessageW(&msg); } inputs[0].u.ki.wVk = 0x41; inputs[0].u.ki.wScan = 0x1e; inputs[0].u.ki.dwFlags = KEYEVENTF_KEYUP; pSendInput(1, (INPUT*)inputs, sizeof(INPUT)); while(PeekMessageW(&msg, hWndTest, 0, 0, PM_NOREMOVE)) { if(msg.message != WM_KEYUP) PeekMessageW(&msg, hWndTest, 0, 0, PM_REMOVE); else { ok(msg.wParam != VK_PROCESSKEY,"Incorrect ProcessKey Found\n"); PeekMessageW(&msg, hWndTest, 0, 0, PM_REMOVE); ok(msg.wParam != VK_PROCESSKEY,"ProcessKey should still not be Found\n"); } TranslateMessage(&msg); DispatchMessageW(&msg); } ImmReleaseContext(hWndTest, imc); ImmSetOpenStatus(imc, FALSE); DestroyWindow(hWndTest); } static void test_InvalidIMC(void) { HIMC imc_destroy; HIMC imc_null = 0x00000000; HIMC imc_bad = (HIMC)0xdeadcafe; HIMC imc1, imc2, oldimc; DWORD ret; DWORD count; CHAR buffer[1000]; INPUTCONTEXT *ic; LOGFONTA lf; BOOL r; memset(&lf, 0, sizeof(lf)); imc_destroy = ImmCreateContext(); ret = ImmDestroyContext(imc_destroy); ok(ret == TRUE, "Destroy an IMC should success!\n"); /* Test associating destroyed imc */ imc1 = ImmGetContext(hwnd); SetLastError(0xdeadbeef); oldimc = ImmAssociateContext(hwnd, imc_destroy); ok(!oldimc, "Associating to a destroyed imc should fail!\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); imc2 = ImmGetContext(hwnd); ok(imc1 == imc2, "imc should not changed! imc1 %p, imc2 %p\n", imc1, imc2); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, TRUE); SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, TRUE); r = ImmSetActiveContext(hwnd, imc_destroy, TRUE); ok(!r, "ImmSetActiveContext succeeded\n"); SET_EXPECT(WM_IME_SETCONTEXT_DEACTIVATE); r = ImmSetActiveContext(hwnd, imc_destroy, FALSE); ok(r, "ImmSetActiveContext failed\n"); CHECK_CALLED(WM_IME_SETCONTEXT_DEACTIVATE); SET_ENABLE(WM_IME_SETCONTEXT_ACTIVATE, FALSE); SET_ENABLE(WM_IME_SETCONTEXT_DEACTIVATE, FALSE); /* Test associating NULL imc, which is different from an invalid imc */ oldimc = ImmAssociateContext(hwnd, imc_null); ok(oldimc != NULL, "Associating to NULL imc should success!\n"); imc2 = ImmGetContext(hwnd); ok(!imc2, "expect NULL, returned %p\n", imc2); oldimc = ImmAssociateContext(hwnd, imc1); ok(!oldimc, "expect NULL, returned %p\n", oldimc); imc2 = ImmGetContext(hwnd); ok(imc2 == imc1, "imc should not changed! imc2 %p, imc1 %p\n", imc2, imc1); /* Test associating invalid imc */ imc1 = ImmGetContext(hwnd); SetLastError(0xdeadbeef); oldimc = ImmAssociateContext(hwnd, imc_bad); ok(!oldimc, "Associating to a destroyed imc should fail!\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); imc2 = ImmGetContext(hwnd); ok(imc1 == imc2, "imc should not changed! imc1 %p, imc2 %p\n", imc1, imc2); /* Test ImmGetCandidateListA */ SetLastError(0xdeadbeef); ret = ImmGetCandidateListA(imc_bad, 0, NULL, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateListA(imc_null, 0, NULL, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateListA(imc_destroy, 0, NULL, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetCandidateListCountA*/ SetLastError(0xdeadbeef); ret = ImmGetCandidateListCountA(imc_bad,&count); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateListCountA(imc_null,&count); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateListCountA(imc_destroy,&count); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetCandidateWindow */ SetLastError(0xdeadbeef); ret = ImmGetCandidateWindow(imc_bad, 0, (LPCANDIDATEFORM)buffer); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateWindow(imc_null, 0, (LPCANDIDATEFORM)buffer); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCandidateWindow(imc_destroy, 0, (LPCANDIDATEFORM)buffer); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetCompositionFontA */ SetLastError(0xdeadbeef); ret = ImmGetCompositionFontA(imc_bad, (LPLOGFONTA)buffer); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionFontA(imc_null, (LPLOGFONTA)buffer); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionFontA(imc_destroy, (LPLOGFONTA)buffer); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetCompositionWindow */ SetLastError(0xdeadbeef); ret = ImmGetCompositionWindow(imc_bad, (LPCOMPOSITIONFORM)buffer); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionWindow(imc_null, (LPCOMPOSITIONFORM)buffer); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionWindow(imc_destroy, (LPCOMPOSITIONFORM)buffer); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetCompositionStringA */ SetLastError(0xdeadbeef); ret = ImmGetCompositionStringA(imc_bad, GCS_COMPSTR, NULL, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionStringA(imc_null, GCS_COMPSTR, NULL, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetCompositionStringA(imc_destroy, GCS_COMPSTR, NULL, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmSetOpenStatus */ SetLastError(0xdeadbeef); ret = ImmSetOpenStatus(imc_bad, 1); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetOpenStatus(imc_null, 1); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetOpenStatus(imc_destroy, 1); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetOpenStatus */ SetLastError(0xdeadbeef); ret = ImmGetOpenStatus(imc_bad); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetOpenStatus(imc_null); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetOpenStatus(imc_destroy); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetStatusWindowPos */ SetLastError(0xdeadbeef); ret = ImmGetStatusWindowPos(imc_bad, NULL); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetStatusWindowPos(imc_null, NULL); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetStatusWindowPos(imc_destroy, NULL); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmRequestMessageA */ SetLastError(0xdeadbeef); ret = ImmRequestMessageA(imc_bad, WM_CHAR, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmRequestMessageA(imc_null, WM_CHAR, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmRequestMessageA(imc_destroy, WM_CHAR, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmSetCompositionFontA */ SetLastError(0xdeadbeef); ret = ImmSetCompositionFontA(imc_bad, &lf); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetCompositionFontA(imc_null, &lf); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetCompositionFontA(imc_destroy, &lf); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmSetCompositionWindow */ SetLastError(0xdeadbeef); ret = ImmSetCompositionWindow(imc_bad, NULL); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetCompositionWindow(imc_null, NULL); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetCompositionWindow(imc_destroy, NULL); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmSetConversionStatus */ SetLastError(0xdeadbeef); ret = ImmSetConversionStatus(imc_bad, 0, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetConversionStatus(imc_null, 0, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetConversionStatus(imc_destroy, 0, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmSetStatusWindowPos */ SetLastError(0xdeadbeef); ret = ImmSetStatusWindowPos(imc_bad, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetStatusWindowPos(imc_null, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmSetStatusWindowPos(imc_destroy, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGetImeMenuItemsA */ SetLastError(0xdeadbeef); ret = ImmGetImeMenuItemsA(imc_bad, 0, 0, NULL, NULL, 0); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetImeMenuItemsA(imc_null, 0, 0, NULL, NULL, 0); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGetImeMenuItemsA(imc_destroy, 0, 0, NULL, NULL, 0); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmLockIMC */ SetLastError(0xdeadbeef); ic = ImmLockIMC(imc_bad); ok(ic == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ic = ImmLockIMC(imc_null); ok(ic == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ic = ImmLockIMC(imc_destroy); ok(ic == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmUnlockIMC */ SetLastError(0xdeadbeef); ret = ImmUnlockIMC(imc_bad); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmUnlockIMC(imc_null); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == 0xdeadbeef, "last error should remain unchanged %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmUnlockIMC(imc_destroy); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); /* Test ImmGenerateMessage */ SetLastError(0xdeadbeef); ret = ImmGenerateMessage(imc_bad); ok(ret == 0, "Bad IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGenerateMessage(imc_null); ok(ret == 0, "NULL IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); SetLastError(0xdeadbeef); ret = ImmGenerateMessage(imc_destroy); ok(ret == 0, "Destroyed IME should return 0\n"); ret = GetLastError(); ok(ret == ERROR_INVALID_HANDLE, "wrong last error %08lx!\n", ret); } #define test_apttype(apttype) _test_apttype(apttype, __LINE__) static void _test_apttype(APTTYPE apttype, unsigned int line) { APTTYPEQUALIFIER qualifier; HRESULT hr, hr_expected; APTTYPE type; hr = CoGetApartmentType(&type, &qualifier); hr_expected = (apttype == -1 ? CO_E_NOTINITIALIZED : S_OK); ok_(__FILE__, line)(hr == hr_expected, "CoGetApartmentType returned %lx\n", hr); if (FAILED(hr)) return; ok_(__FILE__, line)(type == apttype, "type %x\n", type); ok_(__FILE__, line)(!qualifier, "qualifier %x\n", qualifier); } static DWORD WINAPI com_initialization_thread(void *arg) { HRESULT hr; BOOL r; test_apttype(-1); ImmDisableIME(GetCurrentThreadId()); r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(APTTYPE_MAINSTA); hr = CoInitialize(NULL); ok(hr == S_OK, "CoInitialize returned %lx\n", hr); CoUninitialize(); test_apttype(-1); /* Changes IMM behavior so it no longer initialized COM */ r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(APTTYPE_MAINSTA); hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); ok(hr == S_OK, "CoInitialize returned %lx\n", hr); test_apttype(APTTYPE_MTA); CoUninitialize(); test_apttype(-1); r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(-1); return 0; } static void test_com_initialization(void) { APTTYPEQUALIFIER qualifier; HANDLE thread; APTTYPE type; HRESULT hr; HWND wnd; BOOL r; thread = CreateThread(NULL, 0, com_initialization_thread, NULL, 0, NULL); ok(thread != NULL, "CreateThread failed\n"); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); test_apttype(-1); r = ImmSetActiveContext(NULL, (HIMC)0xdeadbeef, TRUE); ok(!r, "ImmSetActiveContext succeeded\n"); test_apttype(-1); r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(APTTYPE_MAINSTA); /* Force default IME window destruction */ wnd = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); DestroyWindow(wnd); test_apttype(-1); r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(APTTYPE_MAINSTA); hr = CoInitialize(NULL); ok(hr == S_OK, "CoInitialize returned %lx\n", hr); CoUninitialize(); test_apttype(-1); /* Test with default IME window created */ wnd = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); test_apttype(-1); r = ImmSetActiveContext(NULL, NULL, TRUE); ok(r, "ImmSetActiveContext failed\n"); test_apttype(APTTYPE_MAINSTA); hr = CoInitialize(NULL); ok(hr == S_OK, "CoInitialize returned %lx\n", hr); CoUninitialize(); test_apttype(APTTYPE_MAINSTA); DestroyWindow(wnd); test_apttype(-1); wnd = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); r = ImmSetActiveContext(NULL, NULL, TRUE); CoUninitialize(); hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); ok(hr == S_OK, "CoInitialize returned %lx\n", hr); test_apttype(APTTYPE_MTA); DestroyWindow(wnd); hr = CoGetApartmentType(&type, &qualifier); ok(hr == CO_E_NOTINITIALIZED || broken(hr == S_OK) /* w10v22H2 */, "CoGetApartmentType returned %#lx\n", hr); test_apttype(hr == S_OK ? APTTYPE_MTA : -1); wnd = CreateWindowA("static", "static", WS_POPUP, 0, 0, 100, 100, 0, 0, 0, 0); ok(wnd != NULL, "CreateWindow failed\n"); test_apttype(hr == S_OK ? APTTYPE_MTA : -1); ShowWindow(wnd, SW_SHOW); test_apttype(hr == S_OK ? APTTYPE_MTA : APTTYPE_MAINSTA); DestroyWindow(wnd); test_apttype(-1); } static DWORD WINAPI disable_ime_thread(void *arg) { HWND h, def; MSG msg; BOOL r; h = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(h != NULL, "CreateWindow failed\n"); def = ImmGetDefaultIMEWnd(h); ok(def != NULL, "ImmGetDefaultIMEWnd returned NULL\n"); r = ImmDisableIME(arg ? GetCurrentThreadId() : 0); ok(r, "ImmDisableIME failed\n"); if (arg) { def = ImmGetDefaultIMEWnd(h); todo_wine ok(def != NULL, "ImmGetDefaultIMEWnd returned NULL\n"); while(PeekMessageA(&msg, 0, 0, 0, PM_REMOVE)) DispatchMessageA(&msg); } def = ImmGetDefaultIMEWnd(h); ok(!def, "ImmGetDefaultIMEWnd returned %p\n", def); return 0; } static DWORD WINAPI check_not_disabled_ime_thread(void *arg) { HWND def, hwnd; WaitForSingleObject(arg, INFINITE); hwnd = CreateWindowA("static", "static", 0, 0, 0, 0, 0, 0, 0, 0, 0); ok(hwnd != NULL, "CreateWindow failed\n"); def = ImmGetDefaultIMEWnd(hwnd); ok(def != NULL, "ImmGetDefaultIMEWnd returned %p\n", def); return 0; } static DWORD WINAPI disable_ime_process(void *arg) { BOOL r = ImmDisableIME(-1); ok(r, "ImmDisableIME failed\n"); return 0; } static void test_ImmDisableIME(void) { HANDLE thread, event; DWORD tid; HWND def, def2; BOOL r; def = ImmGetDefaultIMEWnd(hwnd); ok(def != NULL, "ImmGetDefaultIMEWnd(hwnd) returned NULL\n"); event = CreateEventW(NULL, TRUE, FALSE, FALSE); thread = CreateThread(NULL, 0, check_not_disabled_ime_thread, event, 0, &tid); ok(thread != NULL, "CreateThread failed\n"); r = ImmDisableIME(tid); ok(!r, "ImmDisableIME(tid) succeeded\n"); SetEvent(event); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); CloseHandle(event); thread = CreateThread(NULL, 0, disable_ime_thread, 0, 0, NULL); ok(thread != NULL, "CreateThread failed\n"); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); thread = CreateThread(NULL, 0, disable_ime_thread, (void*)1, 0, NULL); ok(thread != NULL, "CreateThread failed\n"); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); msg_spy_pump_msg_queue(); thread = CreateThread(NULL, 0, disable_ime_process, 0, 0, NULL); ok(thread != NULL, "CreateThread failed\n"); WaitForSingleObject(thread, INFINITE); CloseHandle(thread); ok(IsWindow(def), "not a window\n"); def2 = ImmGetDefaultIMEWnd(hwnd); ok(def2 == def, "ImmGetDefaultIMEWnd(hwnd) returned %p\n", def2); ok(IsWindow(def), "not a window\n"); msg_spy_pump_msg_queue(); ok(!IsWindow(def), "window is still valid\n"); def = ImmGetDefaultIMEWnd(hwnd); ok(!def, "ImmGetDefaultIMEWnd(hwnd) returned %p\n", def); r = ImmDisableIME(-1); ok(r, "ImmDisableIME(-1) failed\n"); def = ImmGetDefaultIMEWnd(hwnd); ok(!def, "ImmGetDefaultIMEWnd(hwnd) returned %p\n", def); } static BOOL WINAPI ime_ImeConfigure( HKL hkl, HWND hwnd, DWORD mode, void *data ) { ime_trace( "hkl %p, hwnd %p, mode %lu, data %p\n", hkl, hwnd, mode, data ); ok( 0, "unexpected call\n" ); return FALSE; } static DWORD WINAPI ime_ImeConversionList( HIMC himc, const WCHAR *source, CANDIDATELIST *dest, DWORD dest_len, UINT flag ) { ime_trace( "himc %p, source %s, dest %p, dest_len %lu, flag %#x\n", himc, debugstr_w(source), dest, dest_len, flag ); ok( 0, "unexpected call\n" ); return 0; } static BOOL WINAPI ime_ImeDestroy( UINT force ) { ime_trace( "force %u\n", force ); todo_wine_if( todo_ImeDestroy ) CHECK_EXPECT( ImeDestroy ); ok( !force, "got force %u\n", force ); return TRUE; } static UINT WINAPI ime_ImeEnumRegisterWord( REGISTERWORDENUMPROCW proc, const WCHAR *reading, DWORD style, const WCHAR *string, void *data ) { ime_trace( "proc %p, reading %s, style %lu, string %s, data %p\n", proc, debugstr_w(reading), style, debugstr_w(string), data ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); CHECK_EXPECT( ImeEnumRegisterWord ); if (!style) { ok_eq( 0, reading, const void *, "%p" ); ok_eq( 0, string, const void *, "%p" ); } else if (ime_info.fdwProperty & IME_PROP_UNICODE) { ok_eq( 0xdeadbeef, style, UINT, "%#x" ); ok_wcs( L"Reading", reading ); ok_wcs( L"String", string ); } else { ok_eq( 0xdeadbeef, style, UINT, "%#x" ); ok_str( "Reading", (char *)reading ); ok_str( "String", (char *)string ); } if (style) return proc( reading, style, string, data ); return 0; } static LRESULT WINAPI ime_ImeEscape( HIMC himc, UINT escape, void *data ) { ime_trace( "himc %p, escape %#x, data %p\n", himc, escape, data ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); CHECK_EXPECT( ImeEscape ); switch (escape) { case IME_ESC_SET_EUDC_DICTIONARY: if (!data) return 4; if (ime_info.fdwProperty & IME_PROP_UNICODE) ok_wcs( L"EscapeIme", data ); else ok_str( "EscapeIme", data ); /* fallthrough */ case IME_ESC_QUERY_SUPPORT: case IME_ESC_SEQUENCE_TO_INTERNAL: case IME_ESC_GET_EUDC_DICTIONARY: case IME_ESC_MAX_KEY: case IME_ESC_IME_NAME: case IME_ESC_HANJA_MODE: case IME_ESC_GETHELPFILENAME: if (!data) return 4; if (ime_info.fdwProperty & IME_PROP_UNICODE) wcscpy( data, L"ImeEscape" ); else strcpy( data, "ImeEscape" ); return 4; } ok_eq( 0xdeadbeef, escape, UINT, "%#x" ); ok_eq( NULL, data, void *, "%p" ); return TRUE; } static DWORD WINAPI ime_ImeGetImeMenuItems( HIMC himc, DWORD flags, DWORD type, IMEMENUITEMINFOW *parent, IMEMENUITEMINFOW *menu, DWORD size ) { ime_trace( "himc %p, flags %#lx, type %lu, parent %p, menu %p, size %#lx\n", himc, flags, type, parent, menu, size ); ok( 0, "unexpected call\n" ); return 0; } static UINT WINAPI ime_ImeGetRegisterWordStyle( UINT item, STYLEBUFW *style ) { ime_trace( "item %u, style %p\n", item, style ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); CHECK_EXPECT( ImeGetRegisterWordStyle ); if (!style) ok_eq( 16, item, UINT, "%u" ); else if (ime_info.fdwProperty & IME_PROP_UNICODE) { STYLEBUFW *styleW = style; styleW->dwStyle = 0xdeadbeef; wcscpy( styleW->szDescription, L"StyleDescription" ); } else { STYLEBUFA *styleA = (STYLEBUFA *)style; styleA->dwStyle = 0xdeadbeef; strcpy( styleA->szDescription, "StyleDescription" ); } return 0xdeadbeef; } static BOOL WINAPI ime_ImeInquire( IMEINFO *info, WCHAR *ui_class, DWORD flags ) { ime_trace( "info %p, ui_class %p, flags %#lx\n", info, ui_class, flags ); todo_wine_if( todo_ImeInquire ) CHECK_EXPECT( ImeInquire ); ok( !!info, "got info %p\n", info ); ok( !!ui_class, "got ui_class %p\n", ui_class ); ok( !flags, "got flags %#lx\n", flags ); *info = ime_info; if (ime_info.fdwProperty & IME_PROP_UNICODE) wcscpy( ui_class, ime_ui_class.lpszClassName ); else WideCharToMultiByte( CP_ACP, 0, ime_ui_class.lpszClassName, -1, (char *)ui_class, 17, NULL, NULL ); return TRUE; } static BOOL WINAPI ime_ImeProcessKey( HIMC himc, UINT vkey, LPARAM lparam, BYTE *state ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = vkey, .lparam = lparam} }; ime_trace( "himc %p, vkey %u, lparam %#Ix, state %p\n", himc, vkey, lparam, state ); ime_calls[ime_call_count++] = call; return LOWORD(lparam); } static BOOL WINAPI ime_ImeRegisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) { ime_trace( "reading %s, style %lu, string %s\n", debugstr_w(reading), style, debugstr_w(string) ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); CHECK_EXPECT( ImeRegisterWord ); if (style) ok_eq( 0xdeadbeef, style, UINT, "%#x" ); if (ime_info.fdwProperty & IME_PROP_UNICODE) { if (reading) ok_wcs( L"Reading", reading ); if (string) ok_wcs( L"String", string ); } else { if (reading) ok_str( "Reading", (char *)reading ); if (string) ok_str( "String", (char *)string ); } return FALSE; } static BOOL WINAPI ime_ImeSelect( HIMC himc, BOOL select ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = himc, .func = IME_SELECT, .select = select }; INPUTCONTEXT *ctx; ime_trace( "himc %p, select %d\n", himc, select ); ime_calls[ime_call_count++] = call; if (ImeSelect_init_status && select) { ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ctx->fOpen = ~0; ctx->fdwConversion = ~0; ctx->fdwSentence = ~0; ImmUnlockIMC( himc ); } return TRUE; } static BOOL WINAPI ime_ImeSetActiveContext( HIMC himc, BOOL flag ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = himc, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = flag} }; ime_trace( "himc %p, flag %#x\n", himc, flag ); ime_calls[ime_call_count++] = call; return TRUE; } static BOOL WINAPI ime_ImeSetCompositionString( HIMC himc, DWORD index, const void *comp, DWORD comp_len, const void *read, DWORD read_len ) { ime_trace( "himc %p, index %lu, comp %p, comp_len %lu, read %p, read_len %lu\n", himc, index, comp, comp_len, read, read_len ); CHECK_EXPECT( ImeSetCompositionString ); ok_eq( expect_ime, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ne( default_himc, himc, HIMC, "%p" ); if (ime_info.fdwProperty & IME_PROP_UNICODE) { switch (index) { case SCS_SETSTR: todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 22, comp_len, UINT, "%#x" ); ok_wcs( L"CompString", comp ); break; case SCS_CHANGECLAUSE: { const UINT *clause = comp; ok_eq( 8, comp_len, UINT, "%#x" ); ok_eq( 0, clause[0], UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 1, clause[1], UINT, "%#x"); break; } case SCS_CHANGEATTR: { const BYTE *attr = comp; todo_wine_if( todo_ImeSetCompositionString && comp_len != 4 ) ok_eq( 4, comp_len, UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString && attr[0] != 0xcd ) ok_eq( 0xcd, attr[0], UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 0xcd, attr[1], UINT, "%#x" ); break; } default: ok( 0, "unexpected index %#lx\n", index ); break; } } else { switch (index) { case SCS_SETSTR: todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 11, comp_len, UINT, "%#x" ); ok_str( "CompString", comp ); break; case SCS_CHANGECLAUSE: { const UINT *clause = comp; ok_eq( 8, comp_len, UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 0, clause[0], UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 1, clause[1], UINT, "%#x"); break; } case SCS_CHANGEATTR: { const BYTE *attr = comp; todo_wine_if( todo_ImeSetCompositionString && comp_len != 4 ) ok_eq( 4, comp_len, UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 0xcd, attr[0], UINT, "%#x" ); todo_wine_if( todo_ImeSetCompositionString ) ok_eq( 0xcd, attr[1], UINT, "%#x" ); break; } default: ok( 0, "unexpected index %#lx\n", index ); break; } } ok_eq( NULL, read, const void *, "%p" ); ok_eq( 0, read_len, UINT, "%#x" ); return TRUE; } static UINT WINAPI ime_ImeToAsciiEx( UINT vkey, UINT vsc, BYTE *state, TRANSMSGLIST *msgs, UINT flags, HIMC himc ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = himc, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = vkey, .vsc = vsc, .flags = flags} }; INPUTCONTEXT *ctx; UINT count = 0; ime_trace( "vkey %#x, vsc %#x, state %p, msgs %p, flags %#x, himc %p\n", vkey, vsc, state, msgs, flags, himc ); ime_calls[ime_call_count++] = call; ok_ne( NULL, msgs, TRANSMSGLIST *, "%p" ); todo_wine ok_eq( 256, msgs->uMsgCount, UINT, "%u" ); ctx = ImmLockIMC( himc ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( ctx->hWnd ) ); if (vsc & 0x200) { msgs->TransMsg[0].message = WM_IME_STARTCOMPOSITION; msgs->TransMsg[0].wParam = 1; msgs->TransMsg[0].lParam = 0; count++; msgs->TransMsg[1].message = WM_IME_ENDCOMPOSITION; msgs->TransMsg[1].wParam = 1; msgs->TransMsg[1].lParam = 0; count++; } if (vsc & 0x400) { TRANSMSG *msgs; ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); ctx->hMsgBuf = ImmReSizeIMCC( ctx->hMsgBuf, 64 * sizeof(*msgs) ); ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_ne( NULL, msgs, TRANSMSG *, "%p" ); msgs[ctx->dwNumMsgBuf].message = WM_IME_STARTCOMPOSITION; msgs[ctx->dwNumMsgBuf].wParam = 2; msgs[ctx->dwNumMsgBuf].lParam = 0; ctx->dwNumMsgBuf++; msgs[ctx->dwNumMsgBuf].message = WM_IME_ENDCOMPOSITION; msgs[ctx->dwNumMsgBuf].wParam = 2; msgs[ctx->dwNumMsgBuf].lParam = 0; ctx->dwNumMsgBuf++; ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); } ok_ret( 1, ImmUnlockIMC( himc ) ); if (vsc & 0x800) count = ~0; return count; } static BOOL WINAPI ime_ImeUnregisterWord( const WCHAR *reading, DWORD style, const WCHAR *string ) { ime_trace( "reading %s, style %lu, string %s\n", debugstr_w(reading), style, debugstr_w(string) ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); CHECK_EXPECT( ImeUnregisterWord ); if (style) ok_eq( 0xdeadbeef, style, UINT, "%#x" ); if (ime_info.fdwProperty & IME_PROP_UNICODE) { if (reading) ok_wcs( L"Reading", reading ); if (string) ok_wcs( L"String", string ); } else { if (reading) ok_str( "Reading", (char *)reading ); if (string) ok_str( "String", (char *)string ); } return FALSE; } static BOOL WINAPI ime_NotifyIME( HIMC himc, DWORD action, DWORD index, DWORD value ) { struct ime_call call = { .hkl = GetKeyboardLayout( 0 ), .himc = himc, .func = IME_NOTIFY, .notify = {.action = action, .index = index, .value = value} }; ime_trace( "himc %p, action %#lx, index %lu, value %lu\n", himc, action, index, value ); if (!ignore_IME_NOTIFY) ime_calls[ime_call_count++] = call; return FALSE; } static BOOL WINAPI ime_DllMain( HINSTANCE instance, DWORD reason, LPVOID reserved ) { ime_trace( "instance %p, reason %lu, reserved %p.\n", instance, reason, reserved ); switch (reason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls( instance ); ime_ui_class.hInstance = instance; RegisterClassExW( &ime_ui_class ); todo_wine_if(todo_IME_DLL_PROCESS_ATTACH) CHECK_EXPECT( IME_DLL_PROCESS_ATTACH ); break; case DLL_PROCESS_DETACH: UnregisterClassW( ime_ui_class.lpszClassName, instance ); todo_wine_if(todo_IME_DLL_PROCESS_DETACH) CHECK_EXPECT( IME_DLL_PROCESS_DETACH ); break; } return TRUE; } static struct ime_functions ime_functions = { ime_ImeConfigure, ime_ImeConversionList, ime_ImeDestroy, ime_ImeEnumRegisterWord, ime_ImeEscape, ime_ImeGetImeMenuItems, ime_ImeGetRegisterWordStyle, ime_ImeInquire, ime_ImeProcessKey, ime_ImeRegisterWord, ime_ImeSelect, ime_ImeSetActiveContext, ime_ImeSetCompositionString, ime_ImeToAsciiEx, ime_ImeUnregisterWord, ime_NotifyIME, ime_DllMain, }; static HKL ime_install(void) { WCHAR buffer[MAX_PATH]; HMODULE module; DWORD len, ret; HKEY hkey; HKL hkl; /* IME module gets cached and won't reload from disk as soon as a window has * loaded it. To workaround the issue we load the module first as a DLL, * set its function pointers from the test, and later when the cached IME * gets loaded, read the function pointers from the separately loaded DLL. */ load_resource( L"ime_wrapper.dll", buffer ); SetLastError( 0xdeadbeef ); ret = MoveFileW( buffer, L"c:\\windows\\system32\\winetest_ime.dll" ); if (!ret) { ok( GetLastError() == ERROR_ACCESS_DENIED, "got error %lu\n", GetLastError() ); win_skip( "Failed to copy DLL to system directory\n" ); return 0; } module = LoadLibraryW( L"c:\\windows\\system32\\winetest_ime.dll" ); ok( !!module, "LoadLibraryW failed, error %lu\n", GetLastError() ); *(struct ime_functions *)GetProcAddress( module, "ime_functions" ) = ime_functions; /* install the actual IME module, it will lookup the functions from the DLL */ load_resource( L"ime_wrapper.dll", buffer ); SetLastError( 0xdeadbeef ); swprintf( ime_path, ARRAY_SIZE(ime_path), L"c:\\windows\\system32\\wine%04x.ime", ime_count++ ); ret = MoveFileW( buffer, ime_path ); todo_wine_if( GetLastError() == ERROR_ALREADY_EXISTS ) ok( ret || broken( !ret ) /* sometimes still in use */, "MoveFileW failed, error %lu\n", GetLastError() ); hkl = ImmInstallIMEW( ime_path, L"WineTest IME" ); ok( hkl == expect_ime, "ImmInstallIMEW returned %p, error %lu\n", hkl, GetLastError() ); swprintf( buffer, ARRAY_SIZE(buffer), L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08x", hkl ); ret = RegOpenKeyW( HKEY_LOCAL_MACHINE, buffer, &hkey ); ok( !ret, "RegOpenKeyW returned %#lx, error %lu\n", ret, GetLastError() ); len = sizeof(buffer); memset( buffer, 0xcd, sizeof(buffer) ); ret = RegQueryValueExW( hkey, L"Ime File", NULL, NULL, (BYTE *)buffer, &len ); ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); ok( !wcsicmp( buffer, wcsrchr( ime_path, '\\' ) + 1 ), "got Ime File %s\n", debugstr_w(buffer) ); len = sizeof(buffer); memset( buffer, 0xcd, sizeof(buffer) ); ret = RegQueryValueExW( hkey, L"Layout Text", NULL, NULL, (BYTE *)buffer, &len ); ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); ok( !wcscmp( buffer, L"WineTest IME" ), "got Layout Text %s\n", debugstr_w(buffer) ); len = sizeof(buffer); memset( buffer, 0, sizeof(buffer) ); ret = RegQueryValueExW( hkey, L"Layout File", NULL, NULL, (BYTE *)buffer, &len ); todo_wine ok( !ret, "RegQueryValueExW returned %#lx, error %lu\n", ret, GetLastError() ); todo_wine ok( !wcscmp( buffer, L"kbdus.dll" ), "got Layout File %s\n", debugstr_w(buffer) ); ret = RegCloseKey( hkey ); ok( !ret, "RegCloseKey returned %#lx, error %lu\n", ret, GetLastError() ); return hkl; } static void ime_cleanup( HKL hkl, BOOL free ) { HMODULE module = GetModuleHandleW( L"winetest_ime.dll" ); WCHAR buffer[MAX_PATH], value[MAX_PATH]; DWORD i, buffer_len, value_len, ret; HKEY hkey; ret = UnloadKeyboardLayout( hkl ); todo_wine ok( ret, "UnloadKeyboardLayout failed, error %lu\n", GetLastError() ); if (free) ok_ret( 1, ImmFreeLayout( hkl ) ); swprintf( buffer, ARRAY_SIZE(buffer), L"System\\CurrentControlSet\\Control\\Keyboard Layouts\\%08x", hkl ); ret = RegDeleteKeyW( HKEY_LOCAL_MACHINE, buffer ); ok( !ret, "RegDeleteKeyW returned %#lx, error %lu\n", ret, GetLastError() ); ret = RegOpenKeyW( HKEY_CURRENT_USER, L"Keyboard Layout\\Preload", &hkey ); ok( !ret, "RegOpenKeyW returned %#lx, error %lu\n", ret, GetLastError() ); value_len = ARRAY_SIZE(value); buffer_len = sizeof(buffer); for (i = 0; !RegEnumValueW( hkey, i, value, &value_len, NULL, NULL, (void *)buffer, &buffer_len ); i++) { value_len = ARRAY_SIZE(value); buffer_len = sizeof(buffer); if (HandleToUlong( hkl ) != wcstoul( buffer, NULL, 16 )) continue; ret = RegDeleteValueW( hkey, value ); ok( !ret, "RegDeleteValueW returned %#lx, error %lu\n", ret, GetLastError() ); } ret = RegCloseKey( hkey ); ok( !ret, "RegCloseKey returned %#lx, error %lu\n", ret, GetLastError() ); ret = DeleteFileW( ime_path ); todo_wine_if( GetLastError() == ERROR_ACCESS_DENIED ) ok( ret || broken( !ret ) /* sometimes still in use */, "DeleteFileW failed, error %lu\n", GetLastError() ); ret = FreeLibrary( module ); ok( ret, "FreeLibrary failed, error %lu\n", GetLastError() ); ret = DeleteFileW( L"c:\\windows\\system32\\winetest_ime.dll" ); ok( ret, "DeleteFileW failed, error %lu\n", GetLastError() ); } static BOOL CALLBACK enum_get_context( HIMC himc, LPARAM lparam ) { ime_trace( "himc %p\n", himc ); *(HIMC *)lparam = himc; return TRUE; } static BOOL CALLBACK enum_find_context( HIMC himc, LPARAM lparam ) { ime_trace( "himc %p\n", himc ); if (lparam && lparam == (LPARAM)himc) return FALSE; return TRUE; } static void test_ImmEnumInputContext(void) { HIMC himc; ok_ret( 1, ImmEnumInputContext( 0, enum_get_context, (LPARAM)&default_himc ) ); ok_ret( 1, ImmEnumInputContext( -1, enum_find_context, 0 ) ); ok_ret( 1, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, 0 ) ); todo_wine ok_ret( 0, ImmEnumInputContext( 1, enum_find_context, 0 ) ); todo_wine ok_ret( 0, ImmEnumInputContext( GetCurrentProcessId(), enum_find_context, 0 ) ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ok_ret( 0, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, (LPARAM)himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmEnumInputContext( GetCurrentThreadId(), enum_find_context, (LPARAM)himc ) ); } static void test_ImmInstallIME(void) { UINT ret; HKL hkl; SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (!(hkl = ime_install())) goto cleanup; SET_EXPECT( IME_DLL_PROCESS_ATTACH ); SET_EXPECT( ImeInquire ); ret = ImmLoadIME( hkl ); ok( ret, "ImmLoadIME returned %#x\n", ret ); CHECK_CALLED( IME_DLL_PROCESS_ATTACH ); CHECK_CALLED( ImeInquire ); ret = ImmLoadIME( hkl ); ok( ret, "ImmLoadIME returned %#x\n", ret ); SET_EXPECT( ImeDestroy ); SET_EXPECT( IME_DLL_PROCESS_DETACH ); ret = ImmFreeLayout( hkl ); ok( ret, "ImmFreeLayout returned %#x\n", ret ); CHECK_CALLED( ImeDestroy ); CHECK_CALLED( IME_DLL_PROCESS_DETACH ); ret = ImmFreeLayout( hkl ); ok( ret, "ImmFreeLayout returned %#x\n", ret ); ime_info.fdwProperty = 0; SET_EXPECT( IME_DLL_PROCESS_ATTACH ); SET_EXPECT( ImeInquire ); ret = ImmLoadIME( hkl ); ok( ret, "ImmLoadIME returned %#x\n", ret ); CHECK_CALLED( IME_DLL_PROCESS_ATTACH ); CHECK_CALLED( ImeInquire ); ret = ImmLoadIME( hkl ); ok( ret, "ImmLoadIME returned %#x\n", ret ); SET_EXPECT( ImeDestroy ); SET_EXPECT( IME_DLL_PROCESS_DETACH ); ret = ImmFreeLayout( hkl ); ok( ret, "ImmFreeLayout returned %#x\n", ret ); CHECK_CALLED( ImeDestroy ); CHECK_CALLED( IME_DLL_PROCESS_DETACH ); ret = ImmFreeLayout( hkl ); ok( ret, "ImmFreeLayout returned %#x\n", ret ); ime_cleanup( hkl, FALSE ); cleanup: SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); } static void test_ImmIsIME(void) { HKL hkl = GetKeyboardLayout( 0 ); SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmIsIME( 0 ) ); ok_ret( 0xdeadbeef, GetLastError() ); ok_ret( 1, ImmIsIME( hkl ) ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (!(hkl = wineime_hkl)) goto cleanup; todo_ImeInquire = TRUE; todo_ImeDestroy = TRUE; todo_IME_DLL_PROCESS_ATTACH = TRUE; todo_IME_DLL_PROCESS_DETACH = TRUE; ok_ret( 1, ImmIsIME( hkl ) ); todo_IME_DLL_PROCESS_ATTACH = FALSE; todo_IME_DLL_PROCESS_DETACH = FALSE; todo_ImeInquire = FALSE; todo_ImeDestroy = FALSE; cleanup: SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); } static void test_ImmGetProperty(void) { static const IMEINFO expect_ime_info = { .fdwProperty = IME_PROP_UNICODE | IME_PROP_AT_CARET, }; static const IMEINFO expect_ime_info_ja_JP = /* MS Japanese IME */ { .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE | IME_CMODE_KATAKANA, .fdwSentenceCaps = IME_SMODE_PLAURALCLAUSE | IME_SMODE_CONVERSATION, .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD, .fdwSelectCaps = SELECT_CAP_CONVERSION | SELECT_CAP_SENTENCE, .fdwUICaps = UI_CAP_ROT90, }; static const IMEINFO expect_ime_info_ko_KR = /* MS Korean IME */ { .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, .fdwSentenceCaps = IME_SMODE_NONE, .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING, .fdwSelectCaps = SELECT_CAP_CONVERSION, .fdwUICaps = UI_CAP_ROT90, }; static const IMEINFO expect_ime_info_zh_CN = /* MS Chinese IME */ { .fdwProperty = IME_PROP_CANDLIST_START_FROM_1 | IME_PROP_UNICODE | IME_PROP_AT_CARET | 0xa, .fdwConversionCaps = IME_CMODE_NATIVE | IME_CMODE_FULLSHAPE, .fdwSentenceCaps = IME_SMODE_PLAURALCLAUSE, .fdwSCSCaps = SCS_CAP_COMPSTR | SCS_CAP_SETRECONVERTSTRING | SCS_CAP_MAKEREAD, .fdwUICaps = UI_CAP_ROT90, }; HKL hkl = GetKeyboardLayout( 0 ); const IMEINFO *expect; SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmGetProperty( 0, 0 ) ); ok_ret( 0, ImmGetProperty( hkl, 0 ) ); if (hkl == (HKL)0x04110411) expect = &expect_ime_info_ja_JP; else if (hkl == (HKL)0x04120412) expect = &expect_ime_info_ko_KR; else if (hkl == (HKL)0x08040804) expect = &expect_ime_info_zh_CN; else expect = &expect_ime_info; /* IME_PROP_COMPLETE_ON_UNSELECT seems to be sometimes set on CJK locales IMEs, sometimes not */ todo_wine_if( expect != &expect_ime_info ) ok_ret( expect->fdwProperty, ImmGetProperty( hkl, IGP_PROPERTY ) & ~IME_PROP_COMPLETE_ON_UNSELECT ); todo_wine_if( expect != &expect_ime_info_zh_CN && expect != &expect_ime_info_ko_KR ) ok_ret( expect->fdwConversionCaps, ImmGetProperty( hkl, IGP_CONVERSION ) ); todo_wine ok_ret( expect->fdwSentenceCaps, ImmGetProperty( hkl, IGP_SENTENCE ) ); todo_wine_if( expect != &expect_ime_info ) ok_ret( expect->fdwSCSCaps, ImmGetProperty( hkl, IGP_SETCOMPSTR ) ); todo_wine_if( expect != &expect_ime_info_ko_KR ) ok_ret( expect->fdwSelectCaps, ImmGetProperty( hkl, IGP_SELECT ) ); ok_ret( IMEVER_0400, ImmGetProperty( hkl, IGP_GETIMEVERSION ) ); todo_wine_if( expect != &expect_ime_info ) ok_ret( expect->fdwUICaps, ImmGetProperty( hkl, IGP_UI ) ); todo_wine ok_ret( 0xdeadbeef, GetLastError() ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (!(hkl = wineime_hkl)) goto cleanup; SET_EXPECT( ImeInquire ); SET_EXPECT( ImeDestroy ); ok_ret( 0, ImmGetProperty( hkl, 0 ) ); CHECK_CALLED( ImeInquire ); CHECK_CALLED( ImeDestroy ); expect = &ime_info; todo_ImeInquire = TRUE; todo_ImeDestroy = TRUE; ok_ret( expect->fdwProperty, ImmGetProperty( hkl, IGP_PROPERTY ) ); ok_ret( expect->fdwConversionCaps, ImmGetProperty( hkl, IGP_CONVERSION ) ); ok_ret( expect->fdwSentenceCaps, ImmGetProperty( hkl, IGP_SENTENCE ) ); ok_ret( expect->fdwSCSCaps, ImmGetProperty( hkl, IGP_SETCOMPSTR ) ); ok_ret( expect->fdwSelectCaps, ImmGetProperty( hkl, IGP_SELECT ) ); ok_ret( IMEVER_0400, ImmGetProperty( hkl, IGP_GETIMEVERSION ) ); ok_ret( expect->fdwUICaps, ImmGetProperty( hkl, IGP_UI ) ); todo_ImeInquire = FALSE; called_ImeInquire = FALSE; todo_ImeDestroy = FALSE; called_ImeDestroy = FALSE; cleanup: SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); } static void test_ImmGetDescription(void) { HKL hkl = GetKeyboardLayout( 0 ); WCHAR bufferW[MAX_PATH]; char bufferA[MAX_PATH]; DWORD ret; SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); SetLastError( 0xdeadbeef ); ret = ImmGetDescriptionW( NULL, NULL, 0 ); ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); ret = ImmGetDescriptionA( NULL, NULL, 0 ); ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); ret = ImmGetDescriptionW( NULL, NULL, 100 ); ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); ret = ImmGetDescriptionA( NULL, NULL, 100 ); ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); ret = ImmGetDescriptionW( hkl, bufferW, 100 ); ok( !ret, "ImmGetDescriptionW returned %lu\n", ret ); ret = ImmGetDescriptionA( hkl, bufferA, 100 ); ok( !ret, "ImmGetDescriptionA returned %lu\n", ret ); ret = GetLastError(); ok( ret == 0xdeadbeef, "got error %lu\n", ret ); if (!(hkl = wineime_hkl)) goto cleanup; memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetDescriptionW( hkl, bufferW, 2 ); ok( ret == 1, "ImmGetDescriptionW returned %lu\n", ret ); ok( !wcscmp( bufferW, L"W" ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetDescriptionA( hkl, bufferA, 2 ); ok( ret == 0, "ImmGetDescriptionA returned %lu\n", ret ); ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetDescriptionW( hkl, bufferW, 11 ); ok( ret == 10, "ImmGetDescriptionW returned %lu\n", ret ); ok( !wcscmp( bufferW, L"WineTest I" ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetDescriptionA( hkl, bufferA, 11 ); ok( ret == 0, "ImmGetDescriptionA returned %lu\n", ret ); ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetDescriptionW( hkl, bufferW, 12 ); ok( ret == 11, "ImmGetDescriptionW returned %lu\n", ret ); ok( !wcscmp( bufferW, L"WineTest IM" ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetDescriptionA( hkl, bufferA, 12 ); ok( ret == 12, "ImmGetDescriptionA returned %lu\n", ret ); ok( !strcmp( bufferA, "WineTest IME" ), "got bufferA %s\n", debugstr_a(bufferA) ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetDescriptionW( hkl, bufferW, 13 ); ok( ret == 12, "ImmGetDescriptionW returned %lu\n", ret ); ok( !wcscmp( bufferW, L"WineTest IME" ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetDescriptionA( hkl, bufferA, 13 ); ok( ret == 12, "ImmGetDescriptionA returned %lu\n", ret ); ok( !strcmp( bufferA, "WineTest IME" ), "got bufferA %s\n", debugstr_a(bufferA) ); cleanup: SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); } static void test_ImmGetIMEFileName(void) { HKL hkl = GetKeyboardLayout( 0 ); WCHAR bufferW[MAX_PATH], expectW[16]; char bufferA[MAX_PATH], expectA[16]; DWORD ret; SET_ENABLE( IME_DLL_PROCESS_ATTACH, TRUE ); SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, TRUE ); SetLastError( 0xdeadbeef ); ret = ImmGetIMEFileNameW( NULL, NULL, 0 ); ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); ret = ImmGetIMEFileNameA( NULL, NULL, 0 ); ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); ret = ImmGetIMEFileNameW( NULL, NULL, 100 ); ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); ret = ImmGetIMEFileNameA( NULL, NULL, 100 ); ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); ret = ImmGetIMEFileNameW( hkl, bufferW, 100 ); ok( !ret, "ImmGetIMEFileNameW returned %lu\n", ret ); ret = ImmGetIMEFileNameA( hkl, bufferA, 100 ); ok( !ret, "ImmGetIMEFileNameA returned %lu\n", ret ); ret = GetLastError(); ok( ret == 0xdeadbeef, "got error %lu\n", ret ); if (!(hkl = wineime_hkl)) goto cleanup; memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetIMEFileNameW( hkl, bufferW, 2 ); ok( ret == 1, "ImmGetIMEFileNameW returned %lu\n", ret ); ok( !wcscmp( bufferW, L"W" ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetIMEFileNameA( hkl, bufferA, 2 ); ok( ret == 0, "ImmGetIMEFileNameA returned %lu\n", ret ); ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.I", ime_count - 1 ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetIMEFileNameW( hkl, bufferW, 11 ); ok( ret == 10, "ImmGetIMEFileNameW returned %lu\n", ret ); ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetIMEFileNameA( hkl, bufferA, 11 ); ok( ret == 0, "ImmGetIMEFileNameA returned %lu\n", ret ); ok( !strcmp( bufferA, "" ), "got bufferA %s\n", debugstr_a(bufferA) ); swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.IM", ime_count - 1 ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetIMEFileNameW( hkl, bufferW, 12 ); ok( ret == 11, "ImmGetIMEFileNameW returned %lu\n", ret ); ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); snprintf( expectA, ARRAY_SIZE(expectA), "WINE%04X.IME", ime_count - 1 ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetIMEFileNameA( hkl, bufferA, 12 ); ok( ret == 12, "ImmGetIMEFileNameA returned %lu\n", ret ); ok( !strcmp( bufferA, expectA ), "got bufferA %s\n", debugstr_a(bufferA) ); swprintf( expectW, ARRAY_SIZE(expectW), L"WINE%04X.IME", ime_count - 1 ); memset( bufferW, 0xcd, sizeof(bufferW) ); ret = ImmGetIMEFileNameW( hkl, bufferW, 13 ); ok( ret == 12, "ImmGetIMEFileNameW returned %lu\n", ret ); ok( !wcscmp( bufferW, expectW ), "got bufferW %s\n", debugstr_w(bufferW) ); memset( bufferA, 0xcd, sizeof(bufferA) ); ret = ImmGetIMEFileNameA( hkl, bufferA, 13 ); ok( ret == 12, "ImmGetIMEFileNameA returned %lu\n", ret ); ok( !strcmp( bufferA, expectA ), "got bufferA %s\n", debugstr_a(bufferA) ); cleanup: SET_ENABLE( IME_DLL_PROCESS_ATTACH, FALSE ); SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); SET_ENABLE( IME_DLL_PROCESS_DETACH, FALSE ); } static void test_ImmEscape( BOOL unicode ) { HKL hkl = GetKeyboardLayout( 0 ); DWORD i, codes[] = { IME_ESC_QUERY_SUPPORT, IME_ESC_SEQUENCE_TO_INTERNAL, IME_ESC_GET_EUDC_DICTIONARY, IME_ESC_SET_EUDC_DICTIONARY, IME_ESC_MAX_KEY, IME_ESC_IME_NAME, IME_ESC_HANJA_MODE, IME_ESC_GETHELPFILENAME, }; WCHAR bufferW[512]; char bufferA[512]; SET_ENABLE( ImeEscape, TRUE ); winetest_push_context( unicode ? "unicode" : "ansi" ); ok_ret( 0, ImmEscapeW( hkl, 0, 0, NULL ) ); ok_ret( 0, ImmEscapeA( hkl, 0, 0, NULL ) ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; for (i = 0; i < ARRAY_SIZE(codes); ++i) { winetest_push_context( "esc %#lx", codes[i] ); SET_EXPECT( ImeEscape ); ok_ret( 4, ImmEscapeW( hkl, 0, codes[i], NULL ) ); CHECK_CALLED( ImeEscape ); SET_EXPECT( ImeEscape ); memset( bufferW, 0xcd, sizeof(bufferW) ); if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) wcscpy( bufferW, L"EscapeIme" ); ok_ret( 4, ImmEscapeW( hkl, 0, codes[i], bufferW ) ); if (unicode || codes[i] == IME_ESC_GET_EUDC_DICTIONARY || codes[i] == IME_ESC_IME_NAME || codes[i] == IME_ESC_GETHELPFILENAME) { ok_wcs( L"ImeEscape", bufferW ); ok_eq( 0xcdcd, bufferW[10], WORD, "%#x" ); } else if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) { ok_wcs( L"EscapeIme", bufferW ); ok_eq( 0xcdcd, bufferW[10], WORD, "%#x" ); } else if (codes[i] == IME_ESC_HANJA_MODE) { todo_wine ok_eq( 0xcdcd, bufferW[0], WORD, "%#x" ); } else { ok( !memcmp( bufferW, "ImeEscape", 10 ), "got bufferW %s\n", debugstr_w(bufferW) ); ok_eq( 0xcdcd, bufferW[5], WORD, "%#x" ); } CHECK_CALLED( ImeEscape ); SET_EXPECT( ImeEscape ); ok_ret( 4, ImmEscapeA( hkl, 0, codes[i], NULL ) ); CHECK_CALLED( ImeEscape ); SET_EXPECT( ImeEscape ); memset( bufferA, 0xcd, sizeof(bufferA) ); if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) strcpy( bufferA, "EscapeIme" ); ok_ret( 4, ImmEscapeA( hkl, 0, codes[i], bufferA ) ); if (!unicode || codes[i] == IME_ESC_GET_EUDC_DICTIONARY || codes[i] == IME_ESC_IME_NAME || codes[i] == IME_ESC_GETHELPFILENAME) { ok_str( "ImeEscape", bufferA ); ok_eq( 0xcd, bufferA[10], BYTE, "%#x" ); } else if (codes[i] == IME_ESC_SET_EUDC_DICTIONARY) { ok_str( "EscapeIme", bufferA ); ok_eq( 0xcd, bufferA[10], BYTE, "%#x" ); } else if (codes[i] == IME_ESC_HANJA_MODE) { todo_wine ok_eq( 0xcd, bufferA[0], BYTE, "%#x" ); } else { ok( !memcmp( bufferA, L"ImeEscape", 10 * sizeof(WCHAR) ), "got bufferA %s\n", debugstr_a(bufferA) ); ok_eq( 0xcd, bufferA[20], BYTE, "%#x" ); } CHECK_CALLED( ImeEscape ); winetest_pop_context(); } cleanup: SET_ENABLE( ImeEscape, FALSE ); winetest_pop_context(); } static int CALLBACK enum_register_wordA( const char *reading, DWORD style, const char *string, void *user ) { ime_trace( "reading %s, style %#lx, string %s, user %p\n", debugstr_a(reading), style, debugstr_a(string), user ); ok_eq( 0xdeadbeef, style, UINT, "%#x" ); ok_str( "Reading", reading ); ok_str( "String", string ); return 0xdeadbeef; } static int CALLBACK enum_register_wordW( const WCHAR *reading, DWORD style, const WCHAR *string, void *user ) { ime_trace( "reading %s, style %#lx, string %s, user %p\n", debugstr_w(reading), style, debugstr_w(string), user ); ok_eq( 0xdeadbeef, style, UINT, "%#x" ); ok_wcs( L"Reading", reading ); ok_wcs( L"String", string ); return 0xdeadbeef; } static void test_ImmEnumRegisterWord( BOOL unicode ) { HKL hkl = GetKeyboardLayout( 0 ); winetest_push_context( unicode ? "unicode" : "ansi" ); SET_ENABLE( ImeEnumRegisterWord, TRUE ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmEnumRegisterWordW( NULL, enum_register_wordW, NULL, 0, NULL, NULL ) ); ok_ret( 0, ImmEnumRegisterWordA( NULL, enum_register_wordA, NULL, 0, NULL, NULL ) ); ok_ret( 0, ImmEnumRegisterWordW( hkl, enum_register_wordW, NULL, 0, NULL, NULL ) ); ok_ret( 0, ImmEnumRegisterWordA( hkl, enum_register_wordA, NULL, 0, NULL, NULL ) ); todo_wine ok_ret( 0xdeadbeef, GetLastError() ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; SET_EXPECT( ImeEnumRegisterWord ); ok_ret( 0, ImmEnumRegisterWordW( hkl, enum_register_wordW, NULL, 0, NULL, NULL ) ); CHECK_CALLED( ImeEnumRegisterWord ); SET_EXPECT( ImeEnumRegisterWord ); ok_ret( 0, ImmEnumRegisterWordA( hkl, enum_register_wordA, NULL, 0, NULL, NULL ) ); CHECK_CALLED( ImeEnumRegisterWord ); SET_EXPECT( ImeEnumRegisterWord ); ok_ret( 0xdeadbeef, ImmEnumRegisterWordW( hkl, enum_register_wordW, L"Reading", 0xdeadbeef, L"String", NULL ) ); CHECK_CALLED( ImeEnumRegisterWord ); SET_EXPECT( ImeEnumRegisterWord ); ok_ret( 0xdeadbeef, ImmEnumRegisterWordA( hkl, enum_register_wordA, "Reading", 0xdeadbeef, "String", NULL ) ); CHECK_CALLED( ImeEnumRegisterWord ); cleanup: SET_ENABLE( ImeEnumRegisterWord, FALSE ); winetest_pop_context(); } static void test_ImmRegisterWord( BOOL unicode ) { HKL hkl = GetKeyboardLayout( 0 ); SET_ENABLE( ImeRegisterWord, TRUE ); winetest_push_context( unicode ? "unicode" : "ansi" ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmRegisterWordW( NULL, NULL, 0, NULL ) ); ok_ret( 0, ImmRegisterWordA( NULL, NULL, 0, NULL ) ); ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, NULL ) ); ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, NULL ) ); todo_wine ok_ret( 0xdeadbeef, GetLastError() ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordW( hkl, L"Reading", 0, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordA( hkl, "Reading", 0, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0xdeadbeef, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0xdeadbeef, NULL ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordW( hkl, NULL, 0, L"String" ) ); CHECK_CALLED( ImeRegisterWord ); SET_EXPECT( ImeRegisterWord ); ok_ret( 0, ImmRegisterWordA( hkl, NULL, 0, "String" ) ); CHECK_CALLED( ImeRegisterWord ); cleanup: SET_ENABLE( ImeRegisterWord, FALSE ); winetest_pop_context(); } static void test_ImmGetRegisterWordStyle( BOOL unicode ) { HKL hkl = GetKeyboardLayout( 0 ); STYLEBUFW styleW; STYLEBUFA styleA; winetest_push_context( unicode ? "unicode" : "ansi" ); SET_ENABLE( ImeGetRegisterWordStyle, TRUE ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmGetRegisterWordStyleW( NULL, 0, &styleW ) ); ok_ret( 0, ImmGetRegisterWordStyleA( NULL, 0, &styleA ) ); ok_ret( 0, ImmGetRegisterWordStyleW( hkl, 0, &styleW ) ); ok_ret( 0, ImmGetRegisterWordStyleA( hkl, 0, &styleA ) ); todo_wine ok_ret( 0xdeadbeef, GetLastError() ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; if (!strcmp( winetest_platform, "wine" )) goto skip_null; SET_EXPECT( ImeGetRegisterWordStyle ); ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleW( hkl, 16, NULL ) ); CHECK_CALLED( ImeGetRegisterWordStyle ); SET_EXPECT( ImeGetRegisterWordStyle ); ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleA( hkl, 16, NULL ) ); CHECK_CALLED( ImeGetRegisterWordStyle ); skip_null: SET_EXPECT( ImeGetRegisterWordStyle ); memset( &styleW, 0xcd, sizeof(styleW) ); ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleW( hkl, 1, &styleW ) ); if (ime_info.fdwProperty & IME_PROP_UNICODE) { ok_eq( 0xdeadbeef, styleW.dwStyle, UINT, "%#x" ); ok_wcs( L"StyleDescription", styleW.szDescription ); } else { todo_wine ok_eq( 0xcdcdcdcd, styleW.dwStyle, UINT, "%#x" ); todo_wine ok_eq( 0xcdcd, styleW.szDescription[0], WORD, "%#x" ); } CHECK_CALLED( ImeGetRegisterWordStyle ); SET_EXPECT( ImeGetRegisterWordStyle ); memset( &styleA, 0xcd, sizeof(styleA) ); ok_ret( 0xdeadbeef, ImmGetRegisterWordStyleA( hkl, 1, &styleA ) ); if (ime_info.fdwProperty & IME_PROP_UNICODE) { todo_wine ok_eq( 0xcdcdcdcd, styleA.dwStyle, UINT, "%#x" ); todo_wine ok_eq( 0xcd, styleA.szDescription[0], BYTE, "%#x" ); } else { ok_eq( 0xdeadbeef, styleA.dwStyle, UINT, "%#x" ); ok_str( "StyleDescription", styleA.szDescription ); } CHECK_CALLED( ImeGetRegisterWordStyle ); cleanup: SET_ENABLE( ImeGetRegisterWordStyle, FALSE ); winetest_pop_context(); } static void test_ImmUnregisterWord( BOOL unicode ) { HKL hkl = GetKeyboardLayout( 0 ); winetest_push_context( unicode ? "unicode" : "ansi" ); SET_ENABLE( ImeUnregisterWord, TRUE ); SetLastError( 0xdeadbeef ); ok_ret( 0, ImmUnregisterWordW( NULL, NULL, 0, NULL ) ); ok_ret( 0, ImmUnregisterWordA( NULL, NULL, 0, NULL ) ); ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, NULL ) ); ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, NULL ) ); todo_wine ok_ret( 0xdeadbeef, GetLastError() ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordW( hkl, L"Reading", 0, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordA( hkl, "Reading", 0, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0xdeadbeef, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0xdeadbeef, NULL ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordW( hkl, NULL, 0, L"String" ) ); CHECK_CALLED( ImeUnregisterWord ); SET_EXPECT( ImeUnregisterWord ); ok_ret( 0, ImmUnregisterWordA( hkl, NULL, 0, "String" ) ); CHECK_CALLED( ImeUnregisterWord ); cleanup: SET_ENABLE( ImeUnregisterWord, FALSE ); winetest_pop_context(); } struct ime_windows { HWND ime_hwnd; HWND ime_ui_hwnd; }; static BOOL CALLBACK enum_thread_ime_windows( HWND hwnd, LPARAM lparam ) { struct ime_windows *params = (void *)lparam; WCHAR buffer[256]; UINT ret; ime_trace( "hwnd %p, lparam %#Ix\n", hwnd, lparam ); ret = RealGetWindowClassW( hwnd, buffer, ARRAY_SIZE(buffer) ); ok( ret, "RealGetWindowClassW returned %#x\n", ret ); if (!wcscmp( buffer, L"IME" )) { ok( !params->ime_hwnd, "Found extra IME window %p\n", hwnd ); params->ime_hwnd = hwnd; } if (!wcscmp( buffer, ime_ui_class.lpszClassName )) { ok( !params->ime_ui_hwnd, "Found extra IME UI window %p\n", hwnd ); params->ime_ui_hwnd = hwnd; } return TRUE; } static void test_ImmSetConversionStatus(void) { const struct ime_call set_conversion_status_0_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSENTENCEMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, }, {0}, }; const struct ime_call set_conversion_status_1_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0xdeadbeef, .value = IMC_SETCONVERSIONMODE}, }, {0}, }; const struct ime_call set_conversion_status_2_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0xfeedcafe, .value = IMC_SETSENTENCEMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, }, {0}, }; DWORD old_conversion, old_sentence, conversion, sentence; HKL hkl; INPUTCONTEXT *ctx; HWND hwnd; ok_ret( 0, ImmGetConversionStatus( 0, &old_conversion, &old_sentence ) ); ok_ret( 1, ImmGetConversionStatus( default_himc, &old_conversion, &old_sentence ) ); ctx = ImmLockIMC( default_himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_eq( old_conversion, ctx->fdwConversion, UINT, "%#x" ); ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); process_messages(); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( old_conversion, conversion, UINT, "%#x" ); ok_eq( old_sentence, sentence, UINT, "%#x" ); ok_eq( old_conversion, ctx->fdwConversion, UINT, "%#x" ); ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( 0, conversion, UINT, "%#x" ); ok_eq( 0, sentence, UINT, "%#x" ); ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); process_messages(); /* initial values are dependent on both old and new IME */ ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( 0, conversion, UINT, "%#x" ); ok_eq( 0, sentence, UINT, "%#x" ); ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); ok_seq( empty_sequence ); ok_ret( 1, ImmSetConversionStatus( default_himc, 0xdeadbeef, 0xfeedcafe ) ); ok_seq( set_conversion_status_0_seq ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( 0xdeadbeef, conversion, UINT, "%#x" ); ok_eq( 0xfeedcafe, sentence, UINT, "%#x" ); ok_eq( 0xdeadbeef, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmSetConversionStatus( default_himc, 0xdeadbeef, 0xfeedcafe ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, NULL ) ); ok_eq( 0xdeadbeef, conversion, UINT, "%#x" ); ok_eq( 0xdeadbeef, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); ctx->hWnd = 0; ok_seq( empty_sequence ); ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0xfeedcafe ) ); ok_seq( set_conversion_status_1_seq ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( 0, conversion, UINT, "%#x" ); ok_eq( 0xfeedcafe, sentence, UINT, "%#x" ); ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0xfeedcafe, ctx->fdwSentence, UINT, "%#x" ); ctx->hWnd = hwnd; ok_seq( empty_sequence ); ok_ret( 1, ImmSetConversionStatus( default_himc, ~0, ~0 ) ); ok_seq( set_conversion_status_2_seq ); ok_ret( 1, ImmGetConversionStatus( default_himc, NULL, &sentence ) ); ok_eq( ~0, sentence, UINT, "%#x" ); ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmSetConversionStatus( default_himc, ~0, ~0 ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( ~0, conversion, UINT, "%#x" ); ok_eq( ~0, sentence, UINT, "%#x" ); ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); /* status is cached and some bits are kept from the previous active IME */ ok_ret( 1, ImmActivateLayout( default_hkl ) ); todo_wine ok_eq( 0x200, ctx->fdwConversion, UINT, "%#x" ); ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_eq( ~0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( ~0, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); todo_wine ok_eq( 0x200, ctx->fdwConversion, UINT, "%#x" ); ok_eq( old_sentence, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, ImmFreeLayout( hkl ) ); cleanup: /* sanitize conversion status to some sane default */ ok_ret( 1, ImmSetConversionStatus( default_himc, 0, 0 ) ); ok_ret( 1, ImmGetConversionStatus( default_himc, &conversion, &sentence ) ); ok_eq( 0, conversion, UINT, "%#x" ); ok_eq( 0, sentence, UINT, "%#x" ); ok_eq( 0, ctx->fdwConversion, UINT, "%#x" ); ok_eq( 0, ctx->fdwSentence, UINT, "%#x" ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 1, ImmUnlockIMC( default_himc ) ); } static void test_ImmSetOpenStatus(void) { const struct ime_call set_open_status_0_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, }, {0}, }; const struct ime_call set_open_status_1_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, }, {0}, }; const struct ime_call set_open_status_2_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, }, {0}, }; HKL hkl; struct ime_windows ime_windows = {0}; DWORD old_status, status; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; ok_ret( 0, ImmGetOpenStatus( 0 ) ); old_status = ImmGetOpenStatus( default_himc ); ctx = ImmLockIMC( default_himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); process_messages(); status = ImmGetOpenStatus( default_himc ); ok_eq( old_status, status, UINT, "%#x" ); ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); status = ImmGetOpenStatus( default_himc ); ok_eq( 0, status, UINT, "%#x" ); ok_eq( 0, ctx->fOpen, UINT, "%#x" ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); process_messages(); /* initial values are dependent on both old and new IME */ ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; status = ImmGetOpenStatus( default_himc ); ok_eq( 0, status, UINT, "%#x" ); ok_eq( 0, ctx->fOpen, UINT, "%#x" ); ok_seq( empty_sequence ); ok_ret( 1, ImmSetOpenStatus( default_himc, 0xdeadbeef ) ); ok_seq( set_open_status_0_seq ); status = ImmGetOpenStatus( default_himc ); ok_eq( 0xdeadbeef, status, UINT, "%#x" ); ok_eq( 0xdeadbeef, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmSetOpenStatus( default_himc, 0xdeadbeef ) ); ok_seq( empty_sequence ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ok_ret( 1, ImmDestroyContext( himc ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; status = ImmGetOpenStatus( default_himc ); ok( status == 0xdeadbeef || status == 0 /* MS Korean IME */, "got status %#lx\n", status ); ok_eq( status, ctx->fOpen, UINT, "%#x" ); ctx->hWnd = 0; ok_ret( 1, ImmSetOpenStatus( default_himc, 0xfeedcafe ) ); ok_seq( set_open_status_1_seq ); status = ImmGetOpenStatus( default_himc ); ok_eq( 0xfeedcafe, status, UINT, "%#x" ); ok_eq( 0xfeedcafe, ctx->fOpen, UINT, "%#x" ); ctx->hWnd = hwnd; ok_seq( empty_sequence ); ok_ret( 1, ImmSetOpenStatus( default_himc, ~0 ) ); ok_seq( set_open_status_2_seq ); status = ImmGetOpenStatus( default_himc ); ok_eq( ~0, status, UINT, "%#x" ); ok_eq( ~0, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmSetOpenStatus( default_himc, ~0 ) ); ok_seq( empty_sequence ); status = ImmGetOpenStatus( default_himc ); ok_eq( ~0, status, UINT, "%#x" ); ok_eq( ~0, ctx->fOpen, UINT, "%#x" ); /* status is cached between IME activations */ ok_ret( 1, ImmActivateLayout( default_hkl ) ); status = ImmGetOpenStatus( default_himc ); ok_eq( old_status, status, UINT, "%#x" ); ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmActivateLayout( hkl ) ); status = ImmGetOpenStatus( default_himc ); todo_wine ok_eq( 1, status, UINT, "%#x" ); todo_wine ok_eq( 1, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); status = ImmGetOpenStatus( default_himc ); ok_eq( old_status, status, UINT, "%#x" ); ok_eq( old_status, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, ImmFreeLayout( hkl ) ); cleanup: /* sanitize open status to some sane default */ ok_ret( 1, ImmSetOpenStatus( default_himc, 0 ) ); status = ImmGetOpenStatus( default_himc ); ok_eq( 0, status, UINT, "%#x" ); ok_eq( 0, ctx->fOpen, UINT, "%#x" ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 1, ImmUnlockIMC( default_himc ) ); } static void test_ImmProcessKey(void) { const struct ime_call process_key_0[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(0, 0x1e)}, }, {0}, }; const struct ime_call process_key_1[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(1, 0x1e)}, }, {0}, }; const struct ime_call process_key_2[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'A', .lparam = MAKELONG(2, 0x1e)}, }, {0}, }; HKL hkl; UINT_PTR ret; HIMC himc; HWND hwnd; hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); process_messages(); ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); ok_seq( empty_sequence ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 0, ImmProcessKey( 0, hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmProcessKey( hwnd, hkl, 'A', MAKELONG(0, 0x1e), 0 ) ); ok_seq( process_key_0 ); ret = ImmProcessKey( hwnd, hkl, 'A', MAKELONG(1, 0x1e), 0 ); todo_wine ok_ret( 2, ret ); ok_seq( process_key_1 ); ok_ret( 2, ImmProcessKey( hwnd, hkl, 'A', MAKELONG(2, 0x1e), 0 ) ); ok_seq( process_key_2 ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'A', MAKELONG(1, 0x1e), 0 ) ); ok_seq( empty_sequence ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ok_ret( 'A', ImmGetVirtualKey( hwnd ) ); ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); todo_wine ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_eq( himc, ImmAssociateContext( hwnd, default_himc ), HIMC, "%p" ); ok_ret( 'A', ImmGetVirtualKey( hwnd ) ); ImmDestroyContext( himc ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'A', 0 ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, ImmFreeLayout( hkl ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: ok_ret( 1, DestroyWindow( hwnd ) ); } static void test_ImmActivateLayout(void) { const struct ime_call activate_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_SELECT, .select = 1, }, {0}, }; const struct ime_call deactivate_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, .todo = TRUE, }, { .hkl = default_hkl, .himc = default_himc, .func = IME_SELECT, .select = 0, }, {0}, }; struct ime_call activate_with_window_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_SELECT, .select = 1, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_SELECT, .select = 1, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 1, .lparam = (LPARAM)expect_ime}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_OPENSTATUSWINDOW}, .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, .todo = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, .todo = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSENTENCEMODE}, .todo = TRUE, }, {0}, }; struct ime_call deactivate_with_window_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, .todo = TRUE, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, .todo = TRUE, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_CLOSESTATUSWINDOW}, .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 0, .lparam = (LPARAM)expect_ime}, }, { .hkl = default_hkl, .himc = default_himc, .func = IME_SELECT, .select = 0, .flaky_himc = TRUE, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = IME_SELECT, .select = 0, .flaky_himc = TRUE, }, {0}, }; HKL hkl; struct ime_windows ime_windows = {0}; HIMC himc; HWND hwnd; UINT ret; SET_ENABLE( ImeInquire, TRUE ); SET_ENABLE( ImeDestroy, TRUE ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; /* ActivateKeyboardLayout doesn't call ImeInquire / ImeDestroy */ ok_seq( empty_sequence ); ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); ok_seq( empty_sequence ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); /* ImmActivateLayout changes active HKL */ SET_EXPECT( ImeInquire ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_seq( activate_seq ); CHECK_CALLED( ImeInquire ); ok_ret( 1, ImmLoadIME( hkl ) ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_seq( deactivate_seq ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); /* ImmActivateLayout leaks the IME, we need to free it manually */ SET_EXPECT( ImeDestroy ); ret = ImmFreeLayout( hkl ); ok( ret, "ImmFreeLayout returned %u\n", ret ); CHECK_CALLED( ImeDestroy ); ok_seq( empty_sequence ); /* when there's a window, ActivateKeyboardLayout calls ImeInquire */ hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); process_messages(); ok_seq( empty_sequence ); himc = ImmCreateContext(); ok( !!himc, "got himc %p\n", himc ); ok_seq( empty_sequence ); SET_EXPECT( ImeInquire ); ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); CHECK_CALLED( ImeInquire ); activate_with_window_seq[1].himc = himc; ok_seq( activate_with_window_seq ); ok_ret( 1, ImmLoadIME( hkl ) ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); /* FIXME: ignore spurious VK_CONTROL ImeProcessKey / ImeToAsciiEx calls */ process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); deactivate_with_window_seq[1].himc = himc; deactivate_with_window_seq[5].himc = himc; ok_seq( deactivate_with_window_seq ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_seq( empty_sequence ); ok_eq( default_hkl, ActivateKeyboardLayout( hkl, 0 ), HKL, "%p" ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); ok( !!ime_windows.ime_hwnd, "missing IME window\n" ); ok( !!ime_windows.ime_ui_hwnd, "missing IME UI window\n" ); ok_ret( (UINT_PTR)ime_windows.ime_hwnd, (UINT_PTR)GetParent( ime_windows.ime_ui_hwnd ) ); ok_eq( hkl, ActivateKeyboardLayout( default_hkl, 0 ), HKL, "%p" ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); process_messages(); SET_EXPECT( ImeDestroy ); ok_ret( 1, ImmFreeLayout( hkl ) ); CHECK_CALLED( ImeDestroy ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: SET_ENABLE( ImeInquire, FALSE ); SET_ENABLE( ImeDestroy, FALSE ); } static void test_ImmCreateInputContext(void) { struct ime_call activate_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_SELECT, .select = 1, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = 0/*himc[0]*/, .func = IME_SELECT, .select = 1, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 1, .lparam = (LPARAM)expect_ime}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_OPENSTATUSWINDOW}, .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ }, {0}, }; struct ime_call select1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc[0]*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, .todo = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, .todo = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = 0/*himc[1]*/, .func = IME_SELECT, .select = 1, }, {0}, }; struct ime_call select0_seq[] = { { .hkl = expect_ime, .himc = 0/*himc[1]*/, .func = IME_SELECT, .select = 0, }, {0}, }; struct ime_call deactivate_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, .todo = TRUE, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = 0/*himc[0]*/, .func = IME_NOTIFY, .notify = {.action = NI_COMPOSITIONSTR, .index = CPS_CANCEL, .value = 0}, .todo = TRUE, .flaky_himc = TRUE, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_CLOSESTATUSWINDOW}, .todo = TRUE, .broken = TRUE, /* broken after SetForegroundWindow(GetDesktopWindow()) as in d3d8:device */ }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SELECT, .wparam = 0, .lparam = (LPARAM)expect_ime}, }, { .hkl = default_hkl, .himc = default_himc, .func = IME_SELECT, .select = 0, .flaky_himc = TRUE, }, { .hkl = default_hkl, .himc = 0/*himc[0]*/, .func = IME_SELECT, .select = 0, .flaky_himc = TRUE, }, {0}, }; HKL hkl; INPUTCONTEXT *ctx; HIMC himc[2]; HWND hwnd; ctx = ImmLockIMC( default_himc ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ok_ret( 0, IsWindow( ctx->hWnd ) ); ok_ret( 1, ImmUnlockIMC( default_himc ) ); /* new input contexts cannot be locked before IME window has been created */ himc[0] = ImmCreateContext(); ok( !!himc[0], "ImmCreateContext failed, error %lu\n", GetLastError() ); ctx = ImmLockIMC( himc[0] ); todo_wine ok( !ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); if (ctx) ImmUnlockIMCC( himc[0] ); hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); process_messages(); ctx = ImmLockIMC( default_himc ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmUnlockIMC( default_himc ) ); ctx = ImmLockIMC( himc[0] ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmUnlockIMC( himc[0] ) ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; ime_info.dwPrivateDataSize = 123; if (!(hkl = wineime_hkl)) goto cleanup; ok_ret( 1, ImmLoadIME( hkl ) ); /* Activating the layout calls ImeSelect 1 on existing HIMC */ ok_seq( empty_sequence ); ok_ret( 1, ImmActivateLayout( hkl ) ); activate_seq[1].himc = himc[0]; ok_seq( activate_seq ); ok_eq( hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ctx = ImmLockIMC( default_himc ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmUnlockIMC( default_himc ) ); ctx = ImmLockIMC( himc[0] ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmUnlockIMC( himc[0] ) ); /* ImmLockIMC triggers the ImeSelect call, to allocate private data */ himc[1] = ImmCreateContext(); ok( !!himc[1], "ImmCreateContext failed, error %lu\n", GetLastError() ); ok_seq( empty_sequence ); ctx = ImmLockIMC( himc[1] ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); select1_seq[0].himc = himc[0]; select1_seq[4].himc = himc[1]; ok_seq( select1_seq ); ok_ret( 1, ImmUnlockIMC( himc[1] ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmDestroyContext( himc[1] ) ); select0_seq[0].himc = himc[1]; ok_seq( select0_seq ); /* Deactivating the layout calls ImeSelect 0 on existing HIMC */ ok_ret( 1, ImmActivateLayout( default_hkl ) ); deactivate_seq[1].himc = himc[0]; deactivate_seq[5].himc = himc[0]; ok_seq( deactivate_seq ); ok_eq( default_hkl, GetKeyboardLayout( 0 ), HKL, "%p" ); ok_ret( 1, ImmFreeLayout( hkl ) ); ok_seq( empty_sequence ); cleanup: ok_ret( 1, ImmDestroyContext( himc[0] ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ime_info.dwPrivateDataSize = 0; } static void test_DefWindowProc(void) { const struct ime_call start_composition_seq[] = { {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION}}, {0}, }; const struct ime_call end_composition_seq[] = { {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION}}, {0}, }; const struct ime_call composition_seq[] = { {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_COMPOSITION}}, {0}, }; const struct ime_call set_context_seq[] = { {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT}}, {0}, }; const struct ime_call notify_seq[] = { {.hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY}}, {0}, }; HKL hkl; UINT_PTR ret; HWND hwnd; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 0, DefWindowProcW( hwnd, WM_IME_STARTCOMPOSITION, 0, 0 ) ); ok_seq( start_composition_seq ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_ENDCOMPOSITION, 0, 0 ) ); ok_seq( end_composition_seq ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_COMPOSITION, 0, 0 ) ); ok_seq( composition_seq ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_SETCONTEXT, 0, 0 ) ); ok_seq( set_context_seq ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_NOTIFY, 0, 0 ) ); ok_seq( notify_seq ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_CONTROL, 0, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_COMPOSITIONFULL, 0, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_SELECT, 0, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_CHAR, 0, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, DefWindowProcW( hwnd, 0x287, 0, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, DefWindowProcW( hwnd, WM_IME_REQUEST, 0, 0 ) ); ok_seq( empty_sequence ); ret = DefWindowProcW( hwnd, WM_IME_KEYDOWN, 0, 0 ); todo_wine ok_ret( 0, ret ); ok_seq( empty_sequence ); ret = DefWindowProcW( hwnd, WM_IME_KEYUP, 0, 0 ); todo_wine ok_ret( 0, ret ); ok_seq( empty_sequence ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmSetActiveContext(void) { const struct ime_call activate_0_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 1} }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 1, .lparam = ISC_SHOWUIALL} }, {0}, }; const struct ime_call deactivate_0_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} }, {0}, }; struct ime_call deactivate_1_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETOPENSTATUS}, .todo = TRUE, .flaky_himc = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETOPENSTATUS}, .todo = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCONVERSIONMODE}, .todo = TRUE, .broken = TRUE /* sometimes */, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_SELECT, .select = 1, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} }, {0}, }; struct ime_call deactivate_2_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 0} }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 0, .lparam = ISC_SHOWUIALL} }, {0}, }; struct ime_call activate_1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_SET_ACTIVE_CONTEXT, .set_active_context = {.flag = 1} }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_SETCONTEXT, .wparam = 1, .lparam = ISC_SHOWUIALL} }, {0}, }; HKL hkl; struct ime_windows ime_windows = {0}; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( L"static", NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_ret( 1, ImmLoadIME( hkl ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); SetLastError( 0xdeadbeef ); ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, TRUE ) ); ok_seq( activate_0_seq ); ok_ret( 0, GetLastError() ); ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, TRUE ) ); ok_seq( activate_0_seq ); ok_ret( 1, ImmSetActiveContext( hwnd, default_himc, FALSE ) ); ok_seq( deactivate_0_seq ); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_eq( 0, ctx->hWnd, HWND, "%p" ); ok_ret( 1, ImmSetActiveContext( hwnd, himc, FALSE ) ); deactivate_1_seq[3].himc = himc; deactivate_1_seq[4].himc = himc; ok_seq( deactivate_1_seq ); ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); activate_1_seq[0].himc = himc; ok_seq( activate_1_seq ); ctx->hWnd = (HWND)0xdeadbeef; ok_ret( 1, ImmSetActiveContext( hwnd, himc, FALSE ) ); ok_eq( (HWND)0xdeadbeef, ctx->hWnd, HWND, "%p" ); deactivate_2_seq[0].himc = himc; ok_seq( deactivate_2_seq ); ctx->hWnd = 0; ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); ok_eq( hwnd, ctx->hWnd, HWND, "%p" ); activate_1_seq[0].himc = himc; ok_seq( activate_1_seq ); ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); ok_eq( default_himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ctx->hWnd = 0; ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); ok_eq( himc, (HIMC)GetWindowLongPtrW( ime_windows.ime_ui_hwnd, IMMGWL_IMC ), HIMC, "%p" ); ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); ok_eq( hwnd, ctx->hWnd, HWND, "%p" ); ctx->hWnd = (HWND)0xdeadbeef; ok_eq( himc, ImmGetContext( hwnd ), HIMC, "%p" ); ok_eq( (HWND)0xdeadbeef, ctx->hWnd, HWND, "%p" ); ok_ret( 1, ImmReleaseContext( hwnd, himc ) ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmRequestMessage(void) { struct ime_call composition_window_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_COMPOSITIONWINDOW, .lparam = 0/*&comp_form*/} }, {0}, }; struct ime_call candidate_window_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_CANDIDATEWINDOW, .lparam = 0/*&cand_form*/} }, {0}, }; struct ime_call composition_font_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_COMPOSITIONFONT, .lparam = 0/*&log_font*/} }, {0}, }; struct ime_call reconvert_string_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_RECONVERTSTRING, .lparam = 0/*&reconv*/} }, {0}, }; struct ime_call confirm_reconvert_string_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_CONFIRMRECONVERTSTRING, .lparam = 0/*&reconv*/} }, {0}, }; struct ime_call query_char_position_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_QUERYCHARPOSITION, .lparam = 0/*&char_pos*/} }, {0}, }; struct ime_call document_feed_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_REQUEST, .wparam = IMR_DOCUMENTFEED, .lparam = 0/*&reconv*/} }, {0}, }; HKL hkl; COMPOSITIONFORM comp_form = {0}; IMECHARPOSITION char_pos = {0}; RECONVERTSTRING reconv = {0}; CANDIDATEFORM cand_form = {0}; LOGFONTW log_font = {0}; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; 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; ok_ret( 0, ImmRequestMessageW( default_himc, 0xdeadbeef, 0 ) ); todo_wine ok_seq( empty_sequence ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_COMPOSITIONWINDOW, (LPARAM)&comp_form ) ); composition_window_seq[0].message.lparam = (LPARAM)&comp_form; ok_seq( composition_window_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); candidate_window_seq[0].message.lparam = (LPARAM)&cand_form; ok_seq( candidate_window_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_COMPOSITIONFONT, (LPARAM)&log_font ) ); composition_font_seq[0].message.lparam = (LPARAM)&log_font; ok_seq( composition_font_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); todo_wine ok_seq( empty_sequence ); reconv.dwSize = sizeof(RECONVERTSTRING); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_RECONVERTSTRING, (LPARAM)&reconv ) ); reconvert_string_seq[0].message.lparam = (LPARAM)&reconv; ok_seq( reconvert_string_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CONFIRMRECONVERTSTRING, (LPARAM)&reconv ) ); confirm_reconvert_string_seq[0].message.lparam = (LPARAM)&reconv; ok_seq( confirm_reconvert_string_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_QUERYCHARPOSITION, (LPARAM)&char_pos ) ); query_char_position_seq[0].message.lparam = (LPARAM)&char_pos; ok_seq( query_char_position_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_DOCUMENTFEED, (LPARAM)&reconv ) ); document_feed_seq[0].message.lparam = (LPARAM)&reconv; ok_seq( document_feed_seq ); ok_ret( 0, ImmRequestMessageW( himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmSetActiveContext( hwnd, himc, TRUE ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ok_ret( 0, ImmRequestMessageW( himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); candidate_window_seq[0].message.lparam = (LPARAM)&cand_form; ok_seq( candidate_window_seq ); ok_ret( 0, ImmRequestMessageW( default_himc, IMR_CANDIDATEWINDOW, (LPARAM)&cand_form ) ); ok_seq( candidate_window_seq ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmGetCandidateList( BOOL unicode ) { char buffer[512], expect_bufW[512] = {0}, expect_bufA[512] = {0}; CANDIDATELIST *cand_list = (CANDIDATELIST *)buffer, *expect_listW, *expect_listA; HKL hkl; CANDIDATEINFO *cand_info; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; expect_listW = (CANDIDATELIST *)expect_bufW; expect_listW->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 32 * sizeof(WCHAR); expect_listW->dwStyle = 0xdeadbeef; expect_listW->dwCount = 2; expect_listW->dwSelection = 3; expect_listW->dwPageStart = 4; expect_listW->dwPageSize = 5; expect_listW->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]) + 2 * sizeof(WCHAR); expect_listW->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 16 * sizeof(WCHAR); wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[0]), L"Candidate 1" ); wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[1]), L"Candidate 2" ); expect_listA = (CANDIDATELIST *)expect_bufA; expect_listA->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 32; expect_listA->dwStyle = 0xdeadbeef; expect_listA->dwCount = 2; expect_listA->dwSelection = 3; expect_listA->dwPageStart = 4; expect_listA->dwPageSize = 5; expect_listA->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]) + 2; expect_listA->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 16; strcpy( (char *)(expect_bufA + expect_listA->dwOffset[0]), "Candidate 1" ); strcpy( (char *)(expect_bufA + expect_listA->dwOffset[1]), "Candidate 2" ); winetest_push_context( unicode ? "unicode" : "ansi" ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; ok_ret( 0, ImmGetCandidateListW( default_himc, 0, NULL, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateListW( default_himc, 1, NULL, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateListW( default_himc, 0, cand_list, sizeof(buffer) ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateListW( himc, 0, cand_list, sizeof(buffer) ) ); ok_seq( empty_sequence ); todo_wine ok_seq( empty_sequence ); ctx->hCandInfo = ImmReSizeIMCC( ctx->hCandInfo, sizeof(*cand_info) + sizeof(buffer) ); ok( !!ctx->hCandInfo, "ImmReSizeIMCC failed, error %lu\n", GetLastError() ); cand_info = ImmLockIMCC( ctx->hCandInfo ); ok( !!cand_info, "ImmLockIMCC failed, error %lu\n", GetLastError() ); cand_info->dwCount = 1; cand_info->dwOffset[0] = sizeof(*cand_info); if (unicode) memcpy( cand_info + 1, expect_bufW, sizeof(expect_bufW) ); else memcpy( cand_info + 1, expect_bufA, sizeof(expect_bufA) ); ok_ret( 0, ImmUnlockIMCC( ctx->hCandInfo ) ); ok_ret( (unicode ? 96 : 80), ImmGetCandidateListW( himc, 0, NULL, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateListW( himc, 1, NULL, 0 ) ); ok_seq( empty_sequence ); memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( (unicode ? 96 : 80), ImmGetCandidateListW( himc, 0, cand_list, sizeof(buffer) ) ); ok_seq( empty_sequence ); if (!unicode) { expect_listW->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 24 * sizeof(WCHAR); expect_listW->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]); expect_listW->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 12 * sizeof(WCHAR); wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[0]), L"Candidate 1" ); wcscpy( (WCHAR *)(expect_bufW + expect_listW->dwOffset[1]), L"Candidate 2" ); } check_candidate_list_( __LINE__, cand_list, expect_listW, TRUE ); ok_ret( (unicode ? 56 : 64), ImmGetCandidateListA( himc, 0, NULL, 0 ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateListA( himc, 1, NULL, 0 ) ); ok_seq( empty_sequence ); memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( (unicode ? 56 : 64), ImmGetCandidateListA( himc, 0, cand_list, sizeof(buffer) ) ); ok_seq( empty_sequence ); if (unicode) { expect_listA->dwSize = offsetof(CANDIDATELIST, dwOffset[2]) + 24; expect_listA->dwOffset[0] = offsetof(CANDIDATELIST, dwOffset[2]); expect_listA->dwOffset[1] = offsetof(CANDIDATELIST, dwOffset[2]) + 12; strcpy( (char *)(expect_bufA + expect_listA->dwOffset[0]), "Candidate 1" ); strcpy( (char *)(expect_bufA + expect_listA->dwOffset[1]), "Candidate 2" ); } check_candidate_list_( __LINE__, cand_list, expect_listA, FALSE ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: winetest_pop_context(); } static void test_ImmGetCandidateListCount( BOOL unicode ) { HKL hkl; CANDIDATEINFO *cand_info; INPUTCONTEXT *ctx; DWORD count; HIMC himc; HWND hwnd; winetest_push_context( unicode ? "unicode" : "ansi" ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; ok_ret( 144, ImmGetCandidateListCountW( default_himc, &count ) ); ok_eq( 0, count, UINT, "%u" ); ok_seq( empty_sequence ); ok_ret( 144, ImmGetCandidateListCountA( default_himc, &count ) ); ok_eq( 0, count, UINT, "%u" ); ok_seq( empty_sequence ); ok_ret( 144, ImmGetCandidateListCountW( himc, &count ) ); ok_eq( 0, count, UINT, "%u" ); ok_seq( empty_sequence ); ok_ret( 144, ImmGetCandidateListCountA( himc, &count ) ); ok_eq( 0, count, UINT, "%u" ); ok_seq( empty_sequence ); cand_info = ImmLockIMCC( ctx->hCandInfo ); ok( !!cand_info, "ImmLockIMCC failed, error %lu\n", GetLastError() ); cand_info->dwCount = 1; ok_ret( 0, ImmUnlockIMCC( ctx->hCandInfo ) ); todo_wine_if(!unicode) ok_ret( (unicode ? 144 : 172), ImmGetCandidateListCountW( himc, &count ) ); ok_eq( 1, count, UINT, "%u" ); ok_seq( empty_sequence ); todo_wine_if(unicode) ok_ret( (unicode ? 172 : 144), ImmGetCandidateListCountA( himc, &count ) ); ok_eq( 1, count, UINT, "%u" ); ok_seq( empty_sequence ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: winetest_pop_context(); } static void test_ImmGetCandidateWindow(void) { HKL hkl; const CANDIDATEFORM expect_form = { .dwIndex = 0xdeadbeef, .dwStyle = 0xfeedcafe, .ptCurrentPos = {.x = 123, .y = 456}, .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, }; CANDIDATEFORM cand_form; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; memset( &cand_form, 0xcd, sizeof(cand_form) ); ok_ret( 0, ImmGetCandidateWindow( default_himc, 0, &cand_form ) ); ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); ok_ret( 0, ImmGetCandidateWindow( default_himc, 1, &cand_form ) ); ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); ok_ret( 0, ImmGetCandidateWindow( default_himc, 2, &cand_form ) ); ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); ok_ret( 0, ImmGetCandidateWindow( default_himc, 3, &cand_form ) ); ok_eq( 0xcdcdcdcd, cand_form.dwIndex, UINT, "%u" ); ok_ret( 1, ImmGetCandidateWindow( default_himc, 4, &cand_form ) ); ok_seq( empty_sequence ); ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); ok_seq( empty_sequence ); ok_seq( empty_sequence ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ctx->cfCandForm[0] = expect_form; ok_ret( 1, ImmGetCandidateWindow( himc, 0, &cand_form ) ); check_candidate_form( &cand_form, &expect_form ); ok_seq( empty_sequence ); ok_seq( empty_sequence ); ok( !!ctx, "ImmLockIMC failed, error %lu\n", GetLastError() ); ctx->cfCandForm[0].dwIndex = -1; ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); ok_seq( empty_sequence ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmGetCompositionString( BOOL unicode ) { static COMPOSITIONSTRING expect_string_empty = {.dwSize = sizeof(COMPOSITIONSTRING)}; static COMPOSITIONSTRING expect_stringA = { .dwSize = 176, .dwCompReadAttrLen = 8, .dwCompReadAttrOffset = 116, .dwCompReadClauseLen = 8, .dwCompReadClauseOffset = 108, .dwCompReadStrLen = 8, .dwCompReadStrOffset = 100, .dwCompAttrLen = 4, .dwCompAttrOffset = 136, .dwCompClauseLen = 8, .dwCompClauseOffset = 128, .dwCompStrLen = 4, .dwCompStrOffset = 124, .dwCursorPos = 3, .dwDeltaStart = 1, .dwResultReadClauseLen = 8, .dwResultReadClauseOffset = 150, .dwResultReadStrLen = 10, .dwResultReadStrOffset = 140, .dwResultClauseLen = 8, .dwResultClauseOffset = 164, .dwResultStrLen = 6, .dwResultStrOffset = 158, .dwPrivateSize = 4, .dwPrivateOffset = 172, }; static const COMPOSITIONSTRING expect_stringW = { .dwSize = 204, .dwCompReadAttrLen = 8, .dwCompReadAttrOffset = 124, .dwCompReadClauseLen = 8, .dwCompReadClauseOffset = 116, .dwCompReadStrLen = 8, .dwCompReadStrOffset = 100, .dwCompAttrLen = 4, .dwCompAttrOffset = 148, .dwCompClauseLen = 8, .dwCompClauseOffset = 140, .dwCompStrLen = 4, .dwCompStrOffset = 132, .dwCursorPos = 3, .dwDeltaStart = 1, .dwResultReadClauseLen = 8, .dwResultReadClauseOffset = 172, .dwResultReadStrLen = 10, .dwResultReadStrOffset = 152, .dwResultClauseLen = 8, .dwResultClauseOffset = 192, .dwResultStrLen = 6, .dwResultStrOffset = 180, .dwPrivateSize = 4, .dwPrivateOffset = 200, }; static const UINT gcs_indexes[] = { GCS_COMPREADSTR, GCS_COMPREADATTR, GCS_COMPREADCLAUSE, GCS_COMPSTR, GCS_COMPATTR, GCS_COMPCLAUSE, GCS_CURSORPOS, GCS_DELTASTART, GCS_RESULTREADSTR, GCS_RESULTREADCLAUSE, GCS_RESULTSTR, GCS_RESULTCLAUSE, }; static const UINT scs_indexes[] = { SCS_SETSTR, SCS_CHANGEATTR, SCS_CHANGECLAUSE, }; static const UINT expect_retW[ARRAY_SIZE(gcs_indexes)] = {16, 8, 8, 8, 4, 8, 3, 1, 20, 8, 12, 8}; static const UINT expect_retA[ARRAY_SIZE(gcs_indexes)] = {8, 8, 8, 4, 4, 8, 3, 1, 10, 8, 6, 8}; HKL hkl; COMPOSITIONSTRING *string; char buffer[1024]; INPUTCONTEXT *old_ctx, *ctx; const void *str; HIMCC old_himcc; UINT i, len; BYTE *dst; HIMC himc; HWND hwnd; SET_ENABLE( ImeSetCompositionString, TRUE ); winetest_push_context( unicode ? "unicode" : "ansi" ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; memset( buffer, 0xcd, sizeof(buffer) ); todo_wine ok_ret( -2, ImmGetCompositionStringW( default_himc, GCS_COMPSTR | GCS_COMPATTR, buffer, sizeof(buffer) ) ); memset( buffer, 0xcd, sizeof(buffer) ); todo_wine ok_ret( -2, ImmGetCompositionStringA( default_himc, GCS_COMPSTR | GCS_COMPATTR, buffer, sizeof(buffer) ) ); for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) { memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( 0, ImmGetCompositionStringW( default_himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( 0, ImmGetCompositionStringA( default_himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( 0, ImmGetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); memset( buffer, 0xcd, sizeof(buffer) ); ok_ret( 0, ImmGetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); } ctx->hCompStr = ImmReSizeIMCC( ctx->hCompStr, unicode ? expect_stringW.dwSize : expect_stringA.dwSize ); string = ImmLockIMCC( ctx->hCompStr ); ok( !!string, "ImmLockIMCC failed, error %lu\n", GetLastError() ); check_composition_string( string, &expect_string_empty ); string->dwCursorPos = 3; string->dwDeltaStart = 1; if (unicode) str = L"ReadComp"; else str = "ReadComp"; len = unicode ? wcslen( str ) : strlen( str ); string->dwCompReadStrLen = len; string->dwCompReadStrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompReadStrOffset; memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); string->dwCompReadClauseLen = 2 * sizeof(DWORD); string->dwCompReadClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwCompReadClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; string->dwSize += 2 * sizeof(DWORD); string->dwCompReadAttrLen = len; string->dwCompReadAttrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompReadAttrOffset; memset( dst, ATTR_INPUT, len ); string->dwSize += len; if (unicode) str = L"Comp"; else str = "Comp"; len = unicode ? wcslen( str ) : strlen( str ); string->dwCompStrLen = len; string->dwCompStrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompStrOffset; memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); string->dwCompClauseLen = 2 * sizeof(DWORD); string->dwCompClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwCompClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; string->dwSize += 2 * sizeof(DWORD); string->dwCompAttrLen = len; string->dwCompAttrOffset = string->dwSize; dst = (BYTE *)string + string->dwCompAttrOffset; memset( dst, ATTR_INPUT, len ); string->dwSize += len; if (unicode) str = L"ReadResult"; else str = "ReadResult"; len = unicode ? wcslen( str ) : strlen( str ); string->dwResultReadStrLen = len; string->dwResultReadStrOffset = string->dwSize; dst = (BYTE *)string + string->dwResultReadStrOffset; memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); string->dwResultReadClauseLen = 2 * sizeof(DWORD); string->dwResultReadClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwResultReadClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; string->dwSize += 2 * sizeof(DWORD); if (unicode) str = L"Result"; else str = "Result"; len = unicode ? wcslen( str ) : strlen( str ); string->dwResultStrLen = len; string->dwResultStrOffset = string->dwSize; dst = (BYTE *)string + string->dwResultStrOffset; memcpy( dst, str, len * (unicode ? sizeof(WCHAR) : 1) ); string->dwSize += len * (unicode ? sizeof(WCHAR) : 1); string->dwResultClauseLen = 2 * sizeof(DWORD); string->dwResultClauseOffset = string->dwSize; dst = (BYTE *)string + string->dwResultClauseOffset; *(DWORD *)(dst + 0 * sizeof(DWORD)) = 0; *(DWORD *)(dst + 1 * sizeof(DWORD)) = len; string->dwSize += 2 * sizeof(DWORD); string->dwPrivateSize = 4; string->dwPrivateOffset = string->dwSize; dst = (BYTE *)string + string->dwPrivateOffset; memset( dst, 0xa5, string->dwPrivateSize ); string->dwSize += 4; check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); old_himcc = ctx->hCompStr; for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) { UINT_PTR expect; winetest_push_context( "%u", i ); memset( buffer, 0xcd, sizeof(buffer) ); expect = expect_retW[i]; ok_ret( expect, ImmGetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); memset( buffer + expect, 0, 4 ); if (i == 0) ok_wcs( L"ReadComp", (WCHAR *)buffer ); else if (i == 3) ok_wcs( L"Comp", (WCHAR *)buffer ); else if (i == 8) ok_wcs( L"ReadResult", (WCHAR *)buffer ); else if (i == 10) ok_wcs( L"Result", (WCHAR *)buffer ); else if (i != 6 && i != 7) ok_wcs( L"", (WCHAR *)buffer ); memset( buffer, 0xcd, sizeof(buffer) ); expect = expect_retA[i]; ok_ret( expect, ImmGetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer) ) ); memset( buffer + expect, 0, 4 ); if (i == 0) ok_str( "ReadComp", (char *)buffer ); else if (i == 3) ok_str( "Comp", (char *)buffer ); else if (i == 8) ok_str( "ReadResult", (char *)buffer ); else if (i == 10) ok_str( "Result", (char *)buffer ); else if (i != 6 && i != 7) ok_str( "", (char *)buffer ); winetest_pop_context(); } for (i = 0; i < ARRAY_SIZE(gcs_indexes); ++i) { winetest_push_context( "%u", i ); ok_ret( 0, ImmSetCompositionStringW( himc, gcs_indexes[i], buffer, sizeof(buffer), NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringA( himc, gcs_indexes[i], buffer, sizeof(buffer), NULL, 0 ) ); winetest_pop_context(); } ok_ret( 0, ImmSetCompositionStringW( himc, SCS_SETSTR | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringA( himc, SCS_SETSTR | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringW( himc, SCS_CHANGECLAUSE | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); ok_ret( 0, ImmSetCompositionStringA( himc, SCS_CHANGECLAUSE | SCS_CHANGEATTR, buffer, sizeof(buffer), NULL, 0 ) ); for (i = 0; i < ARRAY_SIZE(scs_indexes); ++i) { winetest_push_context( "%u", i ); if (scs_indexes[i] == SCS_CHANGECLAUSE) { memset( buffer, 0, sizeof(buffer) ); *((DWORD *)buffer + 1) = 1; len = 2 * sizeof(DWORD); } else if (scs_indexes[i] == SCS_CHANGEATTR) { memset( buffer, 0xcd, sizeof(buffer) ); len = expect_stringW.dwCompAttrLen; } else if (scs_indexes[i] == SCS_SETSTR) { wcscpy( (WCHAR *)buffer, L"CompString" ); len = 11 * sizeof(WCHAR); } todo_ImeSetCompositionString = !unicode; SET_EXPECT( ImeSetCompositionString ); ok_ret( 1, ImmSetCompositionStringW( himc, scs_indexes[i], buffer, len, NULL, 0 ) ); CHECK_CALLED( ImeSetCompositionString ); todo_ImeSetCompositionString = FALSE; ok_seq( empty_sequence ); string = ImmLockIMCC( ctx->hCompStr ); ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); if (scs_indexes[i] == SCS_CHANGECLAUSE) { memset( buffer, 0, sizeof(buffer) ); *((DWORD *)buffer + 1) = 1; len = 2 * sizeof(DWORD); } else if (scs_indexes[i] == SCS_CHANGEATTR) { memset( buffer, 0xcd, sizeof(buffer) ); len = expect_stringA.dwCompAttrLen; } else if (scs_indexes[i] == SCS_SETSTR) { strcpy( buffer, "CompString" ); len = 11; } todo_ImeSetCompositionString = unicode; SET_EXPECT( ImeSetCompositionString ); ok_ret( 1, ImmSetCompositionStringA( himc, scs_indexes[i], buffer, len, NULL, 0 ) ); CHECK_CALLED( ImeSetCompositionString ); todo_ImeSetCompositionString = FALSE; ok_seq( empty_sequence ); string = ImmLockIMCC( ctx->hCompStr ); ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); check_composition_string( string, unicode ? &expect_stringW : &expect_stringA ); ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); winetest_pop_context(); } ok_seq( empty_sequence ); old_ctx = ctx; ok_ret( 1, ImmUnlockIMC( himc ) ); /* composition strings are kept between IME selections */ ok_ret( 1, ImmActivateLayout( default_hkl ) ); ctx = ImmLockIMC( himc ); ok_eq( old_ctx, ctx, INPUTCONTEXT *, "%p" ); ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); string = ImmLockIMCC( ctx->hCompStr ); ok_ne( NULL, string, COMPOSITIONSTRING *, "%p" ); *string = expect_string_empty; ok_ret( 0, ImmUnlockIMCC( ctx->hCompStr ) ); ok_ret( 1, ImmActivateLayout( hkl ) ); ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); check_composition_string( string, &expect_string_empty ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_eq( old_himcc, ctx->hCompStr, HIMCC, "%p" ); check_composition_string( string, &expect_string_empty ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: winetest_pop_context(); SET_ENABLE( ImeSetCompositionString, FALSE ); } static void test_ImmSetCompositionWindow(void) { struct ime_call set_composition_window_0_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONWINDOW}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONWINDOW}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONWINDOW}, }, {0}, }; struct ime_call set_composition_window_1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONWINDOW}, }, {0}, }; COMPOSITIONFORM comp_form, expect_form = { .dwStyle = 0xfeedcafe, .ptCurrentPos = {.x = 123, .y = 456}, .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, }; struct ime_windows ime_windows = {0}; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; HKL hkl; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; set_composition_window_0_seq[0].himc = himc; set_composition_window_1_seq[0].himc = himc; ok_ret( 1, EnumThreadWindows( GetCurrentThreadId(), enum_thread_ime_windows, (LPARAM)&ime_windows ) ); ok_ne( NULL, ime_windows.ime_hwnd, HWND, "%p" ); ok_ne( NULL, ime_windows.ime_ui_hwnd, HWND, "%p" ); ctx->cfCompForm = expect_form; ctx->fdwInit = ~INIT_COMPFORM; memset( &comp_form, 0xcd, sizeof(comp_form) ); ok_ret( 0, ImmGetCompositionWindow( himc, &comp_form ) ); ok_eq( 0xcdcdcdcd, comp_form.dwStyle, UINT, "%#x" ); ctx->fdwInit = INIT_COMPFORM; ok_ret( 1, ImmGetCompositionWindow( himc, &comp_form ) ); check_composition_form( &comp_form, &expect_form ); ok_seq( empty_sequence ); ok_ret( 0, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); ok_ret( 0, ShowWindow( ime_windows.ime_ui_hwnd, SW_SHOWNOACTIVATE ) ); process_messages(); ok_seq( empty_sequence ); check_WM_SHOWWINDOW = TRUE; ctx->hWnd = hwnd; ctx->fdwInit = 0; memset( &comp_form, 0xcd, sizeof(comp_form) ); ok_ret( 1, ImmSetCompositionWindow( himc, &comp_form ) ); process_messages(); ok_seq( set_composition_window_0_seq ); ok_eq( INIT_COMPFORM, ctx->fdwInit, UINT, "%u" ); check_composition_form( &ctx->cfCompForm, &comp_form ); ok_ret( 1, IsWindowVisible( ime_windows.ime_ui_hwnd ) ); check_WM_SHOWWINDOW = FALSE; ShowWindow( ime_windows.ime_ui_hwnd, SW_HIDE ); process_messages(); ok_seq( empty_sequence ); ok_ret( 1, ImmSetCompositionWindow( himc, &expect_form ) ); ok_seq( set_composition_window_0_seq ); check_composition_form( &ctx->cfCompForm, &expect_form ); ctx->cfCompForm = expect_form; ok_ret( 1, ImmGetCompositionWindow( himc, &comp_form ) ); check_composition_form( &comp_form, &expect_form ); ok_seq( empty_sequence ); ctx->hWnd = 0; memset( &comp_form, 0xcd, sizeof(comp_form) ); ok_ret( 1, ImmSetCompositionWindow( himc, &comp_form ) ); ok_seq( set_composition_window_1_seq ); check_composition_form( &ctx->cfCompForm, &comp_form ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmSetStatusWindowPos(void) { struct ime_call set_status_window_pos_0_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSTATUSWINDOWPOS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSTATUSWINDOWPOS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETSTATUSWINDOWPOS}, }, {0}, }; struct ime_call set_status_window_pos_1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETSTATUSWINDOWPOS}, }, {0}, }; INPUTCONTEXT *ctx; POINT pos; HIMC himc; HWND hwnd; HKL hkl; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; set_status_window_pos_0_seq[0].himc = himc; set_status_window_pos_1_seq[0].himc = himc; memset( &pos, 0xcd, sizeof(pos) ); ctx->ptStatusWndPos.x = 0xdeadbeef; ctx->ptStatusWndPos.y = 0xfeedcafe; ctx->fdwInit = ~INIT_STATUSWNDPOS; ok_ret( 0, ImmGetStatusWindowPos( himc, &pos ) ); ok_eq( 0xcdcdcdcd, pos.x, UINT, "%u" ); ok_eq( 0xcdcdcdcd, pos.y, UINT, "%u" ); ctx->fdwInit = INIT_STATUSWNDPOS; ok_ret( 1, ImmGetStatusWindowPos( himc, &pos ) ); ok_eq( 0xdeadbeef, pos.x, UINT, "%u" ); ok_eq( 0xfeedcafe, pos.y, UINT, "%u" ); ok_seq( empty_sequence ); pos.x = 123; pos.y = 456; ctx->hWnd = hwnd; ctx->fdwInit = 0; ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); ok_seq( set_status_window_pos_0_seq ); ok_eq( INIT_STATUSWNDPOS, ctx->fdwInit, UINT, "%u" ); ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); ok_seq( set_status_window_pos_0_seq ); ok_ret( 1, ImmGetStatusWindowPos( himc, &pos ) ); ok_eq( 123, pos.x, UINT, "%u" ); ok_eq( 123, ctx->ptStatusWndPos.x, UINT, "%u" ); ok_eq( 456, pos.y, UINT, "%u" ); ok_eq( 456, ctx->ptStatusWndPos.y, UINT, "%u" ); ok_seq( empty_sequence ); ctx->hWnd = 0; ok_ret( 1, ImmSetStatusWindowPos( himc, &pos ) ); ok_seq( set_status_window_pos_1_seq ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmSetCompositionFont( BOOL unicode ) { struct ime_call set_composition_font_0_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONFONT}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONFONT}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCOMPOSITIONFONT}, }, {0}, }; struct ime_call set_composition_font_1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCOMPOSITIONFONT}, }, {0}, }; LOGFONTW fontW, expect_fontW = { .lfHeight = 1, .lfWidth = 2, .lfEscapement = 3, .lfOrientation = 4, .lfWeight = 5, .lfItalic = 6, .lfUnderline = 7, .lfStrikeOut = 8, .lfCharSet = 8, .lfOutPrecision = 10, .lfClipPrecision = 11, .lfQuality = 12, .lfPitchAndFamily = 13, .lfFaceName = L"FontFace", }; LOGFONTA fontA, expect_fontA = { .lfHeight = 1, .lfWidth = 2, .lfEscapement = 3, .lfOrientation = 4, .lfWeight = 5, .lfItalic = 6, .lfUnderline = 7, .lfStrikeOut = 8, .lfCharSet = 8, .lfOutPrecision = 10, .lfClipPrecision = 11, .lfQuality = 12, .lfPitchAndFamily = 13, .lfFaceName = "FontFace", }; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; HKL hkl; winetest_push_context( unicode ? "unicode" : "ansi" ); /* IME_PROP_END_UNLOAD for the IME to unload / reload. */ ime_info.fdwProperty = IME_PROP_END_UNLOAD; if (unicode) ime_info.fdwProperty |= IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) goto cleanup; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; set_composition_font_0_seq[0].himc = himc; set_composition_font_1_seq[0].himc = himc; memset( &fontW, 0xcd, sizeof(fontW) ); memset( &fontA, 0xcd, sizeof(fontA) ); if (unicode) ctx->lfFont.W = expect_fontW; else ctx->lfFont.A = expect_fontA; ctx->fdwInit = ~INIT_LOGFONT; ok_ret( 0, ImmGetCompositionFontW( himc, &fontW ) ); ok_ret( 0, ImmGetCompositionFontA( himc, &fontA ) ); ctx->fdwInit = INIT_LOGFONT; ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); check_logfont_w( &fontW, &expect_fontW ); ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); check_logfont_a( &fontA, &expect_fontA ); ctx->hWnd = hwnd; ctx->fdwInit = 0; memset( &ctx->lfFont, 0xcd, sizeof(ctx->lfFont) ); ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); ok_eq( INIT_LOGFONT, ctx->fdwInit, UINT, "%u" ); ok_seq( set_composition_font_0_seq ); ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); ok_seq( set_composition_font_0_seq ); if (unicode) check_logfont_w( &ctx->lfFont.W, &expect_fontW ); else check_logfont_a( &ctx->lfFont.A, &expect_fontA ); ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); check_logfont_w( &fontW, &expect_fontW ); ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); check_logfont_a( &fontA, &expect_fontA ); ctx->hWnd = hwnd; ctx->fdwInit = 0; memset( &ctx->lfFont, 0xcd, sizeof(ctx->lfFont) ); ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); ok_eq( INIT_LOGFONT, ctx->fdwInit, UINT, "%u" ); ok_seq( set_composition_font_0_seq ); ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); ok_seq( set_composition_font_0_seq ); if (unicode) check_logfont_w( &ctx->lfFont.W, &expect_fontW ); else check_logfont_a( &ctx->lfFont.A, &expect_fontA ); ok_ret( 1, ImmGetCompositionFontW( himc, &fontW ) ); check_logfont_w( &fontW, &expect_fontW ); ok_ret( 1, ImmGetCompositionFontA( himc, &fontA ) ); check_logfont_a( &fontA, &expect_fontA ); ctx->hWnd = 0; ok_ret( 1, ImmSetCompositionFontW( himc, &expect_fontW ) ); ok_seq( set_composition_font_1_seq ); ok_ret( 1, ImmSetCompositionFontA( himc, &expect_fontA ) ); ok_seq( set_composition_font_1_seq ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: winetest_pop_context(); } static void test_ImmSetCandidateWindow(void) { struct ime_call set_candidate_window_0_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCANDIDATEPOS}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCANDIDATEPOS, .lparam = 4}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_NOTIFY, .wparam = IMN_SETCANDIDATEPOS, .lparam = 4}, }, {0}, }; struct ime_call set_candidate_window_1_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_NOTIFY, .notify = {.action = NI_CONTEXTUPDATED, .index = 0, .value = IMC_SETCANDIDATEPOS}, }, {0}, }; CANDIDATEFORM cand_form, expect_form = { .dwIndex = 2, .dwStyle = 0xfeedcafe, .ptCurrentPos = {.x = 123, .y = 456}, .rcArea = {.left = 1, .top = 2, .right = 3, .bottom = 4}, }; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; HKL hkl; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; set_candidate_window_0_seq[0].himc = himc; set_candidate_window_1_seq[0].himc = himc; ctx->cfCandForm[1] = expect_form; ctx->cfCandForm[2] = expect_form; ctx->fdwInit = 0; memset( &cand_form, 0xcd, sizeof(cand_form) ); ok_ret( 0, ImmGetCandidateWindow( himc, 0, &cand_form ) ); ok_eq( 0xcdcdcdcd, cand_form.dwStyle, UINT, "%#x" ); ok_ret( 1, ImmGetCandidateWindow( himc, 1, &cand_form ) ); check_candidate_form( &cand_form, &expect_form ); ok_ret( 1, ImmGetCandidateWindow( himc, 2, &cand_form ) ); check_candidate_form( &cand_form, &expect_form ); ok_seq( empty_sequence ); ctx->hWnd = hwnd; memset( &cand_form, 0xcd, sizeof(cand_form) ); cand_form.dwIndex = 2; ok_ret( 1, ImmSetCandidateWindow( himc, &cand_form ) ); ok_seq( set_candidate_window_0_seq ); check_candidate_form( &ctx->cfCandForm[2], &cand_form ); ok_eq( 0, ctx->fdwInit, UINT, "%u" ); ok_ret( 1, ImmSetCandidateWindow( himc, &expect_form ) ); ok_seq( set_candidate_window_0_seq ); check_candidate_form( &ctx->cfCandForm[2], &expect_form ); ctx->hWnd = 0; memset( &cand_form, 0xcd, sizeof(cand_form) ); cand_form.dwIndex = 2; ok_ret( 1, ImmSetCandidateWindow( himc, &cand_form ) ); ok_seq( set_candidate_window_1_seq ); check_candidate_form( &ctx->cfCandForm[2], &cand_form ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmGenerateMessage(void) { const struct ime_call generate_sequence[] = { { .hkl = expect_ime, .himc = default_himc, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_COMPOSITION, .wparam = 0, .lparam = GCS_COMPSTR}, }, { .hkl = expect_ime, .himc = default_himc, .func = MSG_IME_UI, .message = {.msg = WM_IME_COMPOSITION, .wparam = 0, .lparam = GCS_COMPSTR}, }, {0}, }; TRANSMSG *msgs, *tmp_msgs; INPUTCONTEXT *ctx; HIMC himc; HWND hwnd; HKL hkl; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (!(hkl = wineime_hkl)) return; hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); 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; todo_wine ok_ret( 4, ImmGetIMCCSize( ctx->hMsgBuf ) ); ctx->hMsgBuf = ImmReSizeIMCC( ctx->hMsgBuf, sizeof(*msgs) ); ok_ne( NULL, ctx->hMsgBuf, HIMCC, "%p" ); msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_ne( NULL, msgs, TRANSMSG *, "%p" ); msgs[0].message = WM_IME_COMPOSITION; msgs[0].wParam = 0; msgs[0].lParam = GCS_COMPSTR; ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); ctx->hWnd = 0; ctx->dwNumMsgBuf = 0; ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( empty_sequence ); ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); ctx->dwNumMsgBuf = 1; ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( empty_sequence ); ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); ctx->hWnd = hwnd; ctx->dwNumMsgBuf = 0; ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( empty_sequence ); ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); ctx->dwNumMsgBuf = 1; ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( generate_sequence ); ok_eq( 0, ctx->dwNumMsgBuf, UINT, "%u" ); ok_ret( sizeof(*msgs), ImmGetIMCCSize( ctx->hMsgBuf ) ); tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); tmp_msgs = ImmLockIMCC( ctx->hMsgBuf ); ok_eq( msgs, tmp_msgs, TRANSMSG *, "%p" ); ok_ret( 0, ImmUnlockIMCC( ctx->hMsgBuf ) ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_ImmTranslateMessage( BOOL kbd_char_first ) { const struct ime_call process_key_seq[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x10)}, }, { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc010)}, }, {0}, }; const struct ime_call to_ascii_ex_0[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x10}, }, {0}, }; const struct ime_call to_ascii_ex_1[] = { { .hkl = expect_ime, .himc = default_himc, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc010)}, }, { .hkl = expect_ime, .himc = default_himc, .func = IME_TO_ASCII_EX, /* FIXME what happened to kbd_char_first here!? */ .to_ascii_ex = {.vkey = 'Q', .vsc = 0xc010}, }, {0}, }; struct ime_call to_ascii_ex_2[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x210)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x210}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0x410)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0x410}, }, {0}, }; struct ime_call to_ascii_ex_3[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xa10)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0xa10}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = 'Q', .lparam = MAKELONG(2, 0xc10)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG('Q', 'q') : 'Q', .vsc = 0xc10}, }, {0}, }; struct ime_call key_down_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = VK_RETURN, .lparam = MAKELONG(1, 0x1c)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = kbd_char_first ? MAKELONG(VK_RETURN, VK_RETURN) : VK_RETURN, .vsc = 0x1c}, }, {0}, }; struct ime_call key_up_seq[] = { { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_PROCESS_KEY, .process_key = {.vkey = VK_RETURN, .lparam = MAKELONG(1, 0xc01c)}, }, { .hkl = expect_ime, .himc = 0/*himc*/, .func = IME_TO_ASCII_EX, .to_ascii_ex = {.vkey = VK_RETURN, .vsc = 0xc01c}, }, {0}, }; struct ime_call post_messages[] = { {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 1}}, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 1}, .todo_himc = TRUE /* on some Wine configurations the IME UI doesn't get an HIMC */ }, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 1}}, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 1}, .todo_himc = TRUE /* on some Wine configurations the IME UI doesn't get an HIMC */ }, {0}, }; struct ime_call sent_messages[] = { {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 2}}, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 2}, .todo_himc = TRUE /* on some Wine configurations the IME UI doesn't get an HIMC */ }, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 2}}, {.hkl = expect_ime, .himc = 0/*himc*/, .func = MSG_IME_UI, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 2}, .todo_himc = TRUE /* on some Wine configurations the IME UI doesn't get an HIMC */ }, {0}, }; HWND hwnd, other_hwnd; INPUTCONTEXT *ctx; HIMC himc; HKL hkl; UINT i; ime_info.fdwProperty = IME_PROP_END_UNLOAD | IME_PROP_UNICODE; if (kbd_char_first) ime_info.fdwProperty |= IME_PROP_KBD_CHAR_FIRST; winetest_push_context( kbd_char_first ? "kbd_char_first" : "default" ); if (!(hkl = wineime_hkl)) goto cleanup; other_hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!other_hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); flush_events(); hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", 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; ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x10), 0 ) ); ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc010), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, 0, 0, 0 ) ); ok_ret( 'Q', ImmGetVirtualKey( hwnd ) ); ok_seq( process_key_seq ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'Q', MAKELONG(2, 0x10) ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_seq( to_ascii_ex_0 ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc010) ) ); ok_seq( empty_sequence ); ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc010), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc010) ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_seq( to_ascii_ex_1 ); ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); ok_eq( default_himc, ImmAssociateContext( other_hwnd, himc ), HIMC, "%p" ); for (i = 0; i < ARRAY_SIZE(to_ascii_ex_2); i++) to_ascii_ex_2[i].himc = himc; for (i = 0; i < ARRAY_SIZE(to_ascii_ex_3); i++) to_ascii_ex_3[i].himc = himc; for (i = 0; i < ARRAY_SIZE(post_messages); i++) post_messages[i].himc = himc; for (i = 0; i < ARRAY_SIZE(sent_messages); i++) sent_messages[i].himc = himc; for (i = 0; i < ARRAY_SIZE(key_down_seq); i++) key_down_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(key_up_seq); i++) key_up_seq[i].himc = himc; memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; ctx->hWnd = hwnd; ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x210), 0 ) ); ok_ret( 1, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x210) ) ); ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0x410), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x410) ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_seq( to_ascii_ex_2 ); process_messages(); ok_seq( post_messages ); ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( sent_messages ); ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xa10), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xa10) ) ); ok_ret( 2, ImmProcessKey( hwnd, expect_ime, 'Q', MAKELONG(2, 0xc10), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0xc10) ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( hwnd ) ); ok_seq( to_ascii_ex_3 ); process_messages(); ok_seq( empty_sequence ); ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( sent_messages ); ctx->hWnd = 0; ok_ret( 2, ImmProcessKey( other_hwnd, expect_ime, 'Q', MAKELONG(2, 0x210), 0 ) ); ok_ret( 1, ImmTranslateMessage( other_hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x210) ) ); ok_ret( 2, ImmProcessKey( other_hwnd, expect_ime, 'Q', MAKELONG(2, 0x410), 0 ) ); ok_ret( 0, ImmTranslateMessage( other_hwnd, WM_KEYUP, 'Q', MAKELONG(2, 0x410) ) ); ok_ret( VK_PROCESSKEY, ImmGetVirtualKey( other_hwnd ) ); ok_seq( to_ascii_ex_2 ); process_messages_( hwnd ); ok_seq( empty_sequence ); process_messages_( other_hwnd ); ok_seq( post_messages ); ok_ret( 1, ImmGenerateMessage( himc ) ); ok_seq( empty_sequence ); ignore_WM_IME_NOTIFY = TRUE; ignore_IME_NOTIFY = TRUE; keybd_event( VK_RETURN, 0x1c, 0, 0 ); flush_events(); process_messages_( hwnd ); ok_seq( key_down_seq ); keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); flush_events(); process_messages_( hwnd ); ok_seq( key_up_seq ); ignore_WM_IME_NOTIFY = FALSE; ignore_IME_NOTIFY = FALSE; ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, ImmActivateLayout( default_hkl ) ); ok_ret( 1, DestroyWindow( other_hwnd ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); ok_ret( 1, ImmFreeLayout( hkl ) ); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; cleanup: winetest_pop_context(); } static void test_ga_na_da(void) { /* These sequences have some additional WM_IME_NOTIFY messages with unknown wparam > IMN_PRIVATE */ struct ime_call complete_seq[] = { /* G */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x1b, .lparam = GCS_CURSORPOS|GCS_DELTASTART|GCS_COMPSTR|GCS_COMPATTR|GCS_COMPCLAUSE| GCS_COMPREADSTR|GCS_COMPREADATTR|GCS_COMPREADCLAUSE}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, /* G */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* N */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* D */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub09f", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb09f, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb098, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* RETURN */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\ub2e4", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb2e4, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_KEYDOWN, .wparam = 0xd, .lparam = 0x1c0001}}, {0}, }; struct ime_call partial_g_seq[] = { {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_ga_seq[] = { { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_n_seq[] = { { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_na_seq[] = { { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_d_seq[] = { { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub09f", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb09f, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_da_seq[] = { { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb098, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub2e4", .result = L"\ub098", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, {0}, }; struct ime_call partial_return_seq[] = { {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\ub2e4", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb2e4, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb2e4, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_KEYDOWN, .wparam = 0xd, .lparam = 0x1c0001}}, {0}, }; struct ime_call cancel_seq[] = { /* G */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* N */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* CPS_CANCEL */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, {0}, }; struct ime_call closed_seq[] = { /* G */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u3131", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x3131, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac00", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* N */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uac04", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac04, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* A */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xac00, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xac00, .lparam = 0x1}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\ub098", .result = L"\uac00", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_COMPSTR|GCS_COMPATTR|CS_INSERTCHAR|CS_NOMOVECARET}, }, /* CPS_COMPLETE */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\ub098", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xb098, .lparam = GCS_RESULTSTR}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0xb098, .lparam = 0x1}}, {0}, }; INPUTCONTEXT *ctx; HWND hwnd; HIMC himc; UINT i; /* this test doesn't work on Win32 / WoW64 */ if (broken(sizeof(void *) == 4) || default_hkl != (HKL)0x04120412 /* MS Korean IME */) { skip( "Got hkl %p, skipping Korean IME-specific test\n", default_hkl ); process_messages(); return; } hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); flush_events(); ignore_WM_IME_NOTIFY = TRUE; ignore_IME_NOTIFY = TRUE; himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); ok_ret( 1, ImmSetConversionStatus( himc, IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE, IME_SMODE_PHRASEPREDICT ) ); flush_events(); keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( VK_RETURN, 0x1c, 0, 0 ); flush_events(); keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); flush_events(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; for (i = 0; i < ARRAY_SIZE(complete_seq); i++) complete_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_g_seq); i++) partial_g_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_ga_seq); i++) partial_ga_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_n_seq); i++) partial_n_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_na_seq); i++) partial_na_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_d_seq); i++) partial_d_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_da_seq); i++) partial_da_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(partial_return_seq); i++) partial_return_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(cancel_seq); i++) cancel_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(closed_seq); i++) closed_seq[i].himc = himc; keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( VK_BACK, 0x0e, 0, 0 ); flush_events(); keybd_event( VK_BACK, 0x0e, KEYEVENTF_KEYUP, 0 ); keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); keybd_event( 'S', 0x1f, 0, 0 ); flush_events(); keybd_event( 'S', 0x1f, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); keybd_event( 'E', 0x12, 0, 0 ); flush_events(); keybd_event( 'E', 0x12, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); keybd_event( VK_RETURN, 0x1c, 0, 0 ); flush_events(); keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); flush_events(); todo_wine ok_seq( complete_seq ); /* Korean IME uses ImeProcessKey and posts messages */ todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'R', MAKELONG(1, 0x13), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'R', MAKELONG(1, 0x13) ) ); ok_seq( empty_sequence ); process_messages(); todo_wine ok_seq( partial_g_seq ); /* Korean IME doesn't eat WM_KEYUP */ ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'R', MAKELONG(1, 0xc013), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'R', MAKELONG(1, 0xc013) ) ); process_messages(); ok_seq( empty_sequence ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); process_messages(); todo_wine ok_seq( partial_ga_seq ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'S', MAKELONG(1, 0x1f), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'S', MAKELONG(1, 0x1f) ) ); process_messages(); todo_wine ok_seq( partial_n_seq ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); process_messages(); todo_wine ok_seq( partial_na_seq ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'E', MAKELONG(1, 0x12), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'E', MAKELONG(1, 0x12) ) ); process_messages(); todo_wine ok_seq( partial_d_seq ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, 'K', MAKELONG(1, 0x25), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'K', MAKELONG(1, 0x25) ) ); process_messages(); todo_wine ok_seq( partial_da_seq ); todo_wine ok_ret( 2, ImmProcessKey( hwnd, default_hkl, VK_RETURN, MAKELONG(1, 0x1c), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, VK_RETURN, MAKELONG(1, 0x1c) ) ); process_messages(); todo_wine ok_seq( partial_return_seq ); /* cancelling clears the composition string */ keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); keybd_event( 'S', 0x1f, 0, 0 ); flush_events(); keybd_event( 'S', 0x1f, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0 ) ); flush_events(); todo_wine ok_seq( cancel_seq ); /* CPS_COMPLETE and ImmSetOpenStatus( himc, FALSE ) do the same thing */ keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); keybd_event( 'S', 0x1f, 0, 0 ); flush_events(); keybd_event( 'S', 0x1f, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0 ) ); flush_events(); todo_wine ok_seq( closed_seq ); keybd_event( 'R', 0x13, 0, 0 ); flush_events(); keybd_event( 'R', 0x13, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); /* does nothing, already open */ ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); keybd_event( 'S', 0x1f, 0, 0 ); flush_events(); keybd_event( 'S', 0x1f, KEYEVENTF_KEYUP, 0 ); keybd_event( 'K', 0x25, 0, 0 ); flush_events(); keybd_event( 'K', 0x25, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); flush_events(); todo_wine ok_seq( closed_seq ); ignore_WM_IME_NOTIFY = FALSE; ignore_IME_NOTIFY = FALSE; ok_ret( 1, ImmSetConversionStatus( himc, 0, IME_SMODE_PHRASEPREDICT ) ); ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } static void test_nihongo_no(void) { /* These sequences have some additional WM_IME_NOTIFY messages with wparam > IMN_PRIVATE */ /* Some out-of-order WM_IME_REQUEST and WM_IME_NOTIFY messages are also ignored */ struct ime_call complete_seq[] = { {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\uff48", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\uff4e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\u3093\uff47", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b\u3093\u3054", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u65e5\u672c\u8a9e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x65e5, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\u65e5\u672c\u8a9e", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x65e5, .lparam = GCS_RESULTSTR|GCS_RESULTCLAUSE|GCS_RESULTREADSTR|GCS_RESULTREADCLAUSE}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x65e5, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x672c, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x8a9e, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\u306e", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306e, .lparam = GCS_RESULTSTR|GCS_RESULTCLAUSE|GCS_RESULTREADSTR|GCS_RESULTREADCLAUSE}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x306e, .lparam = 0x1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, {0}, }; struct ime_call cancel_seq[] = { {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\uff48", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, /* CPS_CANCEL */ {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, {0}, }; struct ime_call closed_seq[] = { {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_STARTCOMPOSITION, .wparam = 0, .lparam = 0}}, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\uff4e", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0xff4e, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\uff48", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"\u306b\u307b", .result = L"", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_COMPSTR|GCS_COMPCLAUSE|GCS_COMPATTR|GCS_COMPREADSTR|GCS_DELTASTART|GCS_CURSORPOS}, }, /* CPS_COMPLETE */ { .hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .comp = L"", .result = L"\u306b\u307b", .message = {.msg = WM_IME_COMPOSITION, .wparam = 0x306b, .lparam = GCS_RESULTSTR|GCS_RESULTCLAUSE|GCS_RESULTREADSTR|GCS_RESULTREADCLAUSE}, }, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x306b, .lparam = 1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_CHAR, .wparam = 0x307b, .lparam = 1}}, {.hkl = default_hkl, .himc = 0/*himc*/, .func = MSG_TEST_WIN, .message = {.msg = WM_IME_ENDCOMPOSITION, .wparam = 0, .lparam = 0}}, {0}, }; INPUTCONTEXT *ctx; HWND hwnd; HIMC himc; UINT i; /* this test doesn't work on Win32 / WoW64 */ if (broken(sizeof(void *) == 4) || default_hkl != (HKL)0x04110411 /* MS Japanese IME */) { skip( "Got hkl %p, skipping Japanese IME-specific test\n", default_hkl ); return; } hwnd = CreateWindowW( test_class.lpszClassName, NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 100, 100, NULL, NULL, NULL, NULL ); ok( !!hwnd, "CreateWindowW failed, error %lu\n", GetLastError() ); flush_events(); himc = ImmCreateContext(); ok_ne( NULL, himc, HIMC, "%p" ); ctx = ImmLockIMC( himc ); ok_ne( NULL, ctx, INPUTCONTEXT *, "%p" ); ok_eq( default_himc, ImmAssociateContext( hwnd, himc ), HIMC, "%p" ); ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); ok_ret( 1, ImmSetConversionStatus( himc, IME_CMODE_FULLSHAPE | IME_CMODE_NATIVE, IME_SMODE_PHRASEPREDICT ) ); flush_events(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; for (i = 0; i < ARRAY_SIZE(complete_seq); i++) complete_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(cancel_seq); i++) cancel_seq[i].himc = himc; for (i = 0; i < ARRAY_SIZE(closed_seq); i++) closed_seq[i].himc = himc; ignore_WM_IME_REQUEST = TRUE; ignore_WM_IME_NOTIFY = TRUE; ignore_IME_NOTIFY = TRUE; keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'I', 0x17, 0, 0 ); flush_events(); keybd_event( 'I', 0x17, KEYEVENTF_KEYUP, 0 ); keybd_event( 'H', 0x23, 0, 0 ); flush_events(); keybd_event( 'H', 0x23, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'G', 0x22, 0, 0 ); flush_events(); keybd_event( 'G', 0x22, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); keybd_event( VK_SPACE, 0x39, 0, 0 ); flush_events(); keybd_event( VK_SPACE, 0x39, KEYEVENTF_KEYUP, 0 ); keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); keybd_event( VK_RETURN, 0x1c, 0, 0 ); flush_events(); keybd_event( VK_RETURN, 0x1c, KEYEVENTF_KEYUP, 0 ); flush_events(); todo_wine ok_seq( complete_seq ); ignore_WM_IME_REQUEST = FALSE; ignore_WM_IME_NOTIFY = FALSE; ignore_IME_NOTIFY = FALSE; /* Japanese IME doesn't take input from ImmProcessKey */ ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'N', MAKELONG(1, 0x31), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYDOWN, 'N', MAKELONG(1, 0x31) ) ); flush_events(); ok_ret( 0, ImmProcessKey( hwnd, default_hkl, 'N', MAKELONG(1, 0xc031), 0 ) ); ok_ret( 0, ImmTranslateMessage( hwnd, WM_KEYUP, 'N', MAKELONG(1, 0xc031) ) ); flush_events(); ok_seq( empty_sequence ); /* changing the open status completes the composition string */ ignore_WM_IME_REQUEST = TRUE; ignore_WM_IME_NOTIFY = TRUE; ignore_IME_NOTIFY = TRUE; /* cancelling clears the composition string */ keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'I', 0x17, 0, 0 ); flush_events(); keybd_event( 'I', 0x17, KEYEVENTF_KEYUP, 0 ); keybd_event( 'H', 0x23, 0, 0 ); flush_events(); keybd_event( 'H', 0x23, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0 ) ); flush_events(); todo_wine ok_seq( cancel_seq ); /* CPS_COMPLETE and ImmSetOpenStatus( himc, FALSE ) do the same thing */ keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'I', 0x17, 0, 0 ); flush_events(); keybd_event( 'I', 0x17, KEYEVENTF_KEYUP, 0 ); keybd_event( 'H', 0x23, 0, 0 ); flush_events(); keybd_event( 'H', 0x23, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmNotifyIME( himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0 ) ); flush_events(); todo_wine ok_seq( closed_seq ); keybd_event( 'N', 0x31, 0, 0 ); flush_events(); keybd_event( 'N', 0x31, KEYEVENTF_KEYUP, 0 ); keybd_event( 'I', 0x17, 0, 0 ); flush_events(); keybd_event( 'I', 0x17, KEYEVENTF_KEYUP, 0 ); /* does nothing, already open */ ok_ret( 1, ImmSetOpenStatus( himc, TRUE ) ); keybd_event( 'H', 0x23, 0, 0 ); flush_events(); keybd_event( 'H', 0x23, KEYEVENTF_KEYUP, 0 ); keybd_event( 'O', 0x18, 0, 0 ); flush_events(); keybd_event( 'O', 0x18, KEYEVENTF_KEYUP, 0 ); ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); flush_events(); todo_wine ok_seq( closed_seq ); ignore_WM_IME_REQUEST = FALSE; ignore_WM_IME_NOTIFY = FALSE; ignore_IME_NOTIFY = FALSE; ok_ret( 1, ImmSetConversionStatus( himc, 0, IME_SMODE_PHRASEPREDICT ) ); ok_ret( 1, ImmSetOpenStatus( himc, FALSE ) ); ok_ret( 1, ImmUnlockIMC( himc ) ); ok_ret( 1, ImmDestroyContext( himc ) ); ok_ret( 1, DestroyWindow( hwnd ) ); process_messages(); memset( ime_calls, 0, sizeof(ime_calls) ); ime_call_count = 0; } START_TEST(imm32) { default_hkl = GetKeyboardLayout( 0 ); test_class.hInstance = GetModuleHandleW( NULL ); RegisterClassExW( &test_class ); if (!is_ime_enabled()) { win_skip("IME support not implemented\n"); return; } test_com_initialization(); test_ImmEnumInputContext(); /* run these before installing the custom IME, sometimes it takes a moment * to uninstall and the default IME doesn't activate immediately */ test_ga_na_da(); test_nihongo_no(); test_ImmInstallIME(); wineime_hkl = ime_install(); test_ImmGetDescription(); test_ImmGetIMEFileName(); test_ImmIsIME(); test_ImmGetProperty(); test_ImmEscape( FALSE ); test_ImmEscape( TRUE ); test_ImmEnumRegisterWord( FALSE ); test_ImmEnumRegisterWord( TRUE ); test_ImmRegisterWord( FALSE ); test_ImmRegisterWord( TRUE ); test_ImmGetRegisterWordStyle( FALSE ); test_ImmGetRegisterWordStyle( TRUE ); test_ImmUnregisterWord( FALSE ); test_ImmUnregisterWord( TRUE ); /* test these first to sanitize conversion / open statuses */ test_ImmSetConversionStatus(); test_ImmSetOpenStatus(); ImeSelect_init_status = TRUE; test_ImmActivateLayout(); test_ImmCreateInputContext(); test_ImmProcessKey(); test_DefWindowProc(); test_ImmSetActiveContext(); test_ImmRequestMessage(); test_ImmGetCandidateList( TRUE ); test_ImmGetCandidateList( FALSE ); test_ImmGetCandidateListCount( TRUE ); test_ImmGetCandidateListCount( FALSE ); test_ImmGetCandidateWindow(); test_ImmGetCompositionString( TRUE ); test_ImmGetCompositionString( FALSE ); test_ImmSetCompositionWindow(); test_ImmSetStatusWindowPos(); test_ImmSetCompositionFont( TRUE ); test_ImmSetCompositionFont( FALSE ); test_ImmSetCandidateWindow(); test_ImmGenerateMessage(); test_ImmTranslateMessage( FALSE ); test_ImmTranslateMessage( TRUE ); if (wineime_hkl) ime_cleanup( wineime_hkl, TRUE ); if (init()) { test_ImmNotifyIME(); test_SCS_SETSTR(); test_ImmIME(); test_ImmAssociateContextEx(); test_NtUserAssociateInputContext(); test_cross_thread_himc(); test_ImmIsUIMessage(); test_ImmGetContext(); test_ImmDefaultHwnd(); test_default_ime_window_creation(); test_ImmGetIMCLockCount(); test_ImmGetIMCCLockCount(); test_ImmDestroyContext(); test_ImmDestroyIMCC(); test_InvalidIMC(); msg_spy_cleanup(); /* Reinitialize the hooks to capture all windows */ msg_spy_init(NULL); test_ImmMessages(); msg_spy_cleanup(); if (pSendInput) test_ime_processkey(); else win_skip("SendInput is not available\n"); /* there's no way of enabling IME - keep the test last */ test_ImmDisableIME(); } cleanup(); UnregisterClassW( test_class.lpszClassName, test_class.hInstance ); }