4 * Copyright (c) 2008 Michael Jung
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include "wine/test.h"
28 #define NUMELEMS(array) (sizeof((array))/sizeof((array)[0]))
30 static BOOL (WINAPI
*pImmAssociateContextEx
)(HWND
,HIMC
,DWORD
);
31 static BOOL (WINAPI
*pImmIsUIMessageA
)(HWND
,UINT
,WPARAM
,LPARAM
);
34 * msgspy - record and analyse message traces sent to a certain window
36 static struct _msg_spy
{
39 HHOOK call_wnd_proc_hook
;
44 static LRESULT CALLBACK
get_msg_filter(int nCode
, WPARAM wParam
, LPARAM lParam
)
46 if (HC_ACTION
== nCode
) {
47 MSG
*msg
= (MSG
*)lParam
;
49 if ((msg
->hwnd
== msg_spy
.hwnd
) &&
50 (msg_spy
.i_msg
< NUMELEMS(msg_spy
.msgs
)))
52 msg_spy
.msgs
[msg_spy
.i_msg
].hwnd
= msg
->hwnd
;
53 msg_spy
.msgs
[msg_spy
.i_msg
].message
= msg
->message
;
54 msg_spy
.msgs
[msg_spy
.i_msg
].wParam
= msg
->wParam
;
55 msg_spy
.msgs
[msg_spy
.i_msg
].lParam
= msg
->lParam
;
60 return CallNextHookEx(msg_spy
.get_msg_hook
, nCode
, wParam
, lParam
);
63 static LRESULT CALLBACK
call_wnd_proc_filter(int nCode
, WPARAM wParam
,
66 if (HC_ACTION
== nCode
) {
67 CWPSTRUCT
*cwp
= (CWPSTRUCT
*)lParam
;
69 if ((cwp
->hwnd
== msg_spy
.hwnd
) &&
70 (msg_spy
.i_msg
< NUMELEMS(msg_spy
.msgs
)))
72 memcpy(&msg_spy
.msgs
[msg_spy
.i_msg
], cwp
, sizeof(msg_spy
.msgs
[0]));
77 return CallNextHookEx(msg_spy
.call_wnd_proc_hook
, nCode
, wParam
, lParam
);
80 static void msg_spy_pump_msg_queue(void) {
83 while(PeekMessage(&msg
, NULL
, 0, 0, PM_REMOVE
)) {
84 TranslateMessage(&msg
);
85 DispatchMessage(&msg
);
91 static void msg_spy_flush_msgs(void) {
92 msg_spy_pump_msg_queue();
96 static CWPSTRUCT
* msg_spy_find_msg(UINT message
) {
99 msg_spy_pump_msg_queue();
101 if (msg_spy
.i_msg
>= NUMELEMS(msg_spy
.msgs
))
102 fprintf(stdout
, "%s:%d: msg_spy: message buffer overflow!\n",
105 for (i
= 0; i
< msg_spy
.i_msg
; i
++)
106 if (msg_spy
.msgs
[i
].message
== message
)
107 return &msg_spy
.msgs
[i
];
112 static void msg_spy_init(HWND hwnd
) {
114 msg_spy
.get_msg_hook
=
115 SetWindowsHookEx(WH_GETMESSAGE
, get_msg_filter
, GetModuleHandle(0),
116 GetCurrentThreadId());
117 msg_spy
.call_wnd_proc_hook
=
118 SetWindowsHookEx(WH_CALLWNDPROC
, call_wnd_proc_filter
,
119 GetModuleHandle(0), GetCurrentThreadId());
122 msg_spy_flush_msgs();
125 static void msg_spy_cleanup(void) {
126 if (msg_spy
.get_msg_hook
)
127 UnhookWindowsHookEx(msg_spy
.get_msg_hook
);
128 if (msg_spy
.call_wnd_proc_hook
)
129 UnhookWindowsHookEx(msg_spy
.call_wnd_proc_hook
);
130 memset(&msg_spy
, 0, sizeof(msg_spy
));
134 * imm32 test cases - Issue some IMM commands on a dummy window and analyse the
135 * messages being sent to this window in response.
137 static const char wndcls
[] = "winetest_imm32_wndcls";
140 static LRESULT WINAPI
wndProc(HWND hWnd
, UINT msg
, WPARAM wParam
, LPARAM lParam
)
144 case WM_IME_SETCONTEXT
:
150 return DefWindowProcA(hwnd
,msg
,wParam
,lParam
);
153 static BOOL
init(void) {
158 hmod
= GetModuleHandleA("imm32.dll");
159 pImmAssociateContextEx
= (void*)GetProcAddress(hmod
, "ImmAssociateContextEx");
160 pImmIsUIMessageA
= (void*)GetProcAddress(hmod
, "ImmIsUIMessageA");
162 wc
.cbSize
= sizeof(WNDCLASSEX
);
164 wc
.lpfnWndProc
= wndProc
;
167 wc
.hInstance
= GetModuleHandle(0);
168 wc
.hIcon
= LoadIcon(NULL
, IDI_APPLICATION
);
169 wc
.hCursor
= LoadCursor(NULL
, IDC_ARROW
);
170 wc
.hbrBackground
= (HBRUSH
)(COLOR_WINDOW
+1);
171 wc
.lpszMenuName
= NULL
;
172 wc
.lpszClassName
= wndcls
;
173 wc
.hIconSm
= LoadIcon(NULL
, IDI_APPLICATION
);
175 if (!RegisterClassExA(&wc
))
178 hwnd
= CreateWindowEx(WS_EX_CLIENTEDGE
, wndcls
, "Wine imm32.dll test",
179 WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, CW_USEDEFAULT
,
180 240, 120, NULL
, NULL
, GetModuleHandle(0), NULL
);
184 imc
= ImmGetContext(hwnd
);
187 win_skip("IME support not implemented\n");
190 ImmReleaseContext(hwnd
, imc
);
192 ShowWindow(hwnd
, SW_SHOWNORMAL
);
200 static void cleanup(void) {
204 UnregisterClass(wndcls
, GetModuleHandle(0));
207 static void test_ImmNotifyIME(void) {
208 static const char string
[] = "wine";
209 char resstr
[16] = "";
213 imc
= ImmGetContext(hwnd
);
214 msg_spy_flush_msgs();
216 ret
= ImmNotifyIME(imc
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
219 "Canceling an empty composition string should succeed.\n");
220 ok(!msg_spy_find_msg(WM_IME_COMPOSITION
), "Windows does not post "
221 "WM_IME_COMPOSITION in response to NI_COMPOSITIONSTR / CPS_CANCEL, if "
222 "the composition string being canceled is empty.\n");
224 ImmSetCompositionString(imc
, SCS_SETSTR
, string
, sizeof(string
), NULL
, 0);
225 msg_spy_flush_msgs();
227 ImmNotifyIME(imc
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
228 msg_spy_flush_msgs();
230 /* behavior differs between win9x and NT */
231 ret
= ImmGetCompositionString(imc
, GCS_COMPSTR
, resstr
, sizeof(resstr
));
232 ok(!ret
, "After being cancelled the composition string is empty.\n");
234 msg_spy_flush_msgs();
236 ret
= ImmNotifyIME(imc
, NI_COMPOSITIONSTR
, CPS_CANCEL
, 0);
239 "Canceling an empty composition string should succeed.\n");
240 ok(!msg_spy_find_msg(WM_IME_COMPOSITION
), "Windows does not post "
241 "WM_IME_COMPOSITION in response to NI_COMPOSITIONSTR / CPS_CANCEL, if "
242 "the composition string being canceled is empty.\n");
244 msg_spy_flush_msgs();
245 ImmReleaseContext(hwnd
, imc
);
248 static void test_ImmGetCompositionString(void)
251 static const WCHAR string
[] = {'w','i','n','e',0x65e5,0x672c,0x8a9e};
257 imc
= ImmGetContext(hwnd
);
258 ImmSetCompositionStringW(imc
, SCS_SETSTR
, string
, sizeof(string
), NULL
,0);
259 alen
= ImmGetCompositionStringA(imc
, GCS_COMPSTR
, cstring
, 20);
260 wlen
= ImmGetCompositionStringW(imc
, GCS_COMPSTR
, wstring
, 20);
261 /* windows machines without any IME installed just return 0 above */
264 len
= ImmGetCompositionStringW(imc
, GCS_COMPATTR
, NULL
, 0);
265 ok(len
*sizeof(WCHAR
)==wlen
,"GCS_COMPATTR(W) not returning correct count\n");
266 len
= ImmGetCompositionStringA(imc
, GCS_COMPATTR
, NULL
, 0);
267 ok(len
==alen
,"GCS_COMPATTR(A) not returning correct count\n");
269 ImmReleaseContext(hwnd
, imc
);
272 static void test_ImmSetCompositionString(void)
277 SetLastError(0xdeadbeef);
278 imc
= ImmGetContext(hwnd
);
279 ok(imc
!= 0, "ImmGetContext() failed. Last error: %u\n", GetLastError());
283 ret
= ImmSetCompositionStringW(imc
, SCS_SETSTR
, NULL
, 0, NULL
, 0);
286 "ImmSetCompositionStringW() failed.\n");
288 ret
= ImmSetCompositionStringW(imc
, SCS_SETSTR
| SCS_CHANGEATTR
,
290 ok(!ret
, "ImmSetCompositionStringW() succeeded.\n");
292 ret
= ImmSetCompositionStringW(imc
, SCS_SETSTR
| SCS_CHANGECLAUSE
,
294 ok(!ret
, "ImmSetCompositionStringW() succeeded.\n");
296 ret
= ImmSetCompositionStringW(imc
, SCS_CHANGEATTR
| SCS_CHANGECLAUSE
,
298 ok(!ret
, "ImmSetCompositionStringW() succeeded.\n");
300 ret
= ImmSetCompositionStringW(imc
, SCS_SETSTR
| SCS_CHANGEATTR
| SCS_CHANGECLAUSE
,
302 ok(!ret
, "ImmSetCompositionStringW() succeeded.\n");
304 ImmReleaseContext(hwnd
, imc
);
307 static void test_ImmIME(void)
311 imc
= ImmGetContext(hwnd
);
315 rc
= ImmConfigureIMEA(imc
, NULL
, IME_CONFIG_REGISTERWORD
, NULL
);
316 ok (rc
== 0, "ImmConfigureIMEA did not fail\n");
317 rc
= ImmConfigureIMEW(imc
, NULL
, IME_CONFIG_REGISTERWORD
, NULL
);
318 ok (rc
== 0, "ImmConfigureIMEW did not fail\n");
320 ImmReleaseContext(hwnd
,imc
);
323 static void test_ImmAssociateContextEx(void)
328 if (!pImmAssociateContextEx
) return;
330 imc
= ImmGetContext(hwnd
);
335 newimc
= ImmCreateContext();
336 ok(newimc
!= imc
, "handles should not be the same\n");
337 rc
= pImmAssociateContextEx(NULL
, NULL
, 0);
338 ok(!rc
, "ImmAssociateContextEx succeeded\n");
339 rc
= pImmAssociateContextEx(hwnd
, NULL
, 0);
340 ok(rc
, "ImmAssociateContextEx failed\n");
341 rc
= pImmAssociateContextEx(NULL
, imc
, 0);
342 ok(!rc
, "ImmAssociateContextEx succeeded\n");
344 rc
= pImmAssociateContextEx(hwnd
, imc
, 0);
345 ok(rc
, "ImmAssociateContextEx failed\n");
346 retimc
= ImmGetContext(hwnd
);
347 ok(retimc
== imc
, "handles should be the same\n");
348 ImmReleaseContext(hwnd
,retimc
);
350 rc
= pImmAssociateContextEx(hwnd
, newimc
, 0);
351 ok(rc
, "ImmAssociateContextEx failed\n");
352 retimc
= ImmGetContext(hwnd
);
353 ok(retimc
== newimc
, "handles should be the same\n");
354 ImmReleaseContext(hwnd
,retimc
);
356 rc
= pImmAssociateContextEx(hwnd
, NULL
, IACE_DEFAULT
);
357 ok(rc
, "ImmAssociateContextEx failed\n");
359 ImmReleaseContext(hwnd
,imc
);
362 typedef struct _igc_threadinfo
{
369 static DWORD WINAPI
ImmGetContextThreadFunc( LPVOID lpParam
)
375 igc_threadinfo
*info
= (igc_threadinfo
*)lpParam
;
376 info
->hwnd
= CreateWindowEx(WS_EX_CLIENTEDGE
, wndcls
, "Wine imm32.dll test",
377 WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, CW_USEDEFAULT
,
378 240, 120, NULL
, NULL
, GetModuleHandle(0), NULL
);
380 h1
= ImmGetContext(hwnd
);
381 todo_wine
ok(info
->himc
== h1
, "hwnd context changed in new thread\n");
382 h2
= ImmGetContext(info
->hwnd
);
383 todo_wine
ok(h2
!= h1
, "new hwnd in new thread should have different context\n");
385 ImmReleaseContext(hwnd
,h1
);
387 hwnd2
= CreateWindowEx(WS_EX_CLIENTEDGE
, wndcls
, "Wine imm32.dll test",
388 WS_OVERLAPPEDWINDOW
, CW_USEDEFAULT
, CW_USEDEFAULT
,
389 240, 120, NULL
, NULL
, GetModuleHandle(0), NULL
);
390 h1
= ImmGetContext(hwnd2
);
392 ok(h1
== h2
, "Windows in same thread should have same default context\n");
393 ImmReleaseContext(hwnd2
,h1
);
394 ImmReleaseContext(info
->hwnd
,h2
);
395 DestroyWindow(hwnd2
);
397 /* priming for later tests */
398 ImmSetCompositionWindow(h1
, &cf
);
399 ImmSetStatusWindowPos(h1
, &pt
);
401 SetEvent(info
->event
);
406 static void test_ImmThreads(void)
408 HIMC himc
, otherHimc
, h1
;
409 igc_threadinfo threadinfo
;
415 DWORD status
, sentence
;
418 himc
= ImmGetContext(hwnd
);
419 threadinfo
.event
= CreateEvent(NULL
, TRUE
, FALSE
, NULL
);
420 threadinfo
.himc
= himc
;
421 hThread
= CreateThread(NULL
, 0, ImmGetContextThreadFunc
, &threadinfo
, 0, &dwThreadId
);
422 WaitForSingleObject(threadinfo
.event
, INFINITE
);
424 otherHimc
= ImmGetContext(threadinfo
.hwnd
);
426 todo_wine
ok(himc
!= otherHimc
, "Windows from other threads should have different himc\n");
427 todo_wine
ok(otherHimc
== threadinfo
.himc
, "Context from other thread should not change in main thread\n");
429 if (0) /* FIXME: Causes wine to hang */
431 h1
= ImmAssociateContext(hwnd
,otherHimc
);
432 ok(h1
== NULL
, "Should fail to be able to Associate a default context from a different thread\n");
433 h1
= ImmGetContext(hwnd
);
434 ok(h1
== himc
, "Context for window should remain unchanged\n");
435 ImmReleaseContext(hwnd
,h1
);
440 rc
= ImmSetOpenStatus(himc
, TRUE
);
441 ok(rc
!= 0, "ImmSetOpenStatus failed\n");
442 rc
= ImmGetOpenStatus(himc
);
443 ok(rc
!= 0, "ImmGetOpenStatus failed\n");
444 rc
= ImmSetOpenStatus(himc
, FALSE
);
445 ok(rc
!= 0, "ImmSetOpenStatus failed\n");
446 rc
= ImmGetOpenStatus(himc
);
447 ok(rc
== 0, "ImmGetOpenStatus failed\n");
449 rc
= ImmSetOpenStatus(otherHimc
, TRUE
);
450 todo_wine
ok(rc
== 0, "ImmSetOpenStatus should fail\n");
451 rc
= ImmGetOpenStatus(otherHimc
);
452 todo_wine
ok(rc
== 0, "ImmGetOpenStatus failed\n");
453 rc
= ImmSetOpenStatus(otherHimc
, FALSE
);
454 todo_wine
ok(rc
== 0, "ImmSetOpenStatus should fail\n");
455 rc
= ImmGetOpenStatus(otherHimc
);
456 ok(rc
== 0, "ImmGetOpenStatus failed\n");
458 /* CompositionFont */
459 rc
= ImmGetCompositionFont(himc
, &lf
);
460 ok(rc
!= 0, "ImmGetCompositionFont failed\n");
461 rc
= ImmSetCompositionFont(himc
, &lf
);
462 ok(rc
!= 0, "ImmSetCompositionFont failed\n");
464 rc
= ImmGetCompositionFont(otherHimc
, &lf
);
465 ok(rc
!= 0 || broken(rc
== 0), "ImmGetCompositionFont failed\n");
466 rc
= ImmSetCompositionFont(otherHimc
, &lf
);
467 todo_wine
ok(rc
== 0, "ImmSetCompositionFont should fail\n");
469 /* CompositionWindow */
470 rc
= ImmSetCompositionWindow(himc
, &cf
);
471 ok(rc
!= 0, "ImmSetCompositionWindow failed\n");
472 rc
= ImmGetCompositionWindow(himc
, &cf
);
473 ok(rc
!= 0, "ImmGetCompositionWindow failed\n");
475 rc
= ImmSetCompositionWindow(otherHimc
, &cf
);
476 todo_wine
ok(rc
== 0, "ImmSetCompositionWindow should fail\n");
477 rc
= ImmGetCompositionWindow(otherHimc
, &cf
);
478 ok(rc
!= 0 || broken(rc
== 0), "ImmGetCompositionWindow failed\n");
480 /* ConversionStatus */
481 rc
= ImmGetConversionStatus(himc
, &status
, &sentence
);
482 ok(rc
!= 0, "ImmGetConversionStatus failed\n");
483 rc
= ImmSetConversionStatus(himc
, status
, sentence
);
484 ok(rc
!= 0, "ImmSetConversionStatus failed\n");
486 rc
= ImmGetConversionStatus(otherHimc
, &status
, &sentence
);
487 ok(rc
!= 0 || broken(rc
== 0), "ImmGetConversionStatus failed\n");
488 rc
= ImmSetConversionStatus(otherHimc
, status
, sentence
);
489 todo_wine
ok(rc
== 0, "ImmSetConversionStatus should fail\n");
491 /* StatusWindowPos */
492 rc
= ImmSetStatusWindowPos(himc
, &pt
);
493 ok(rc
!= 0, "ImmSetStatusWindowPos failed\n");
494 rc
= ImmGetStatusWindowPos(himc
, &pt
);
495 ok(rc
!= 0, "ImmGetStatusWindowPos failed\n");
497 rc
= ImmSetStatusWindowPos(otherHimc
, &pt
);
498 todo_wine
ok(rc
== 0, "ImmSetStatusWindowPos should fail\n");
499 rc
= ImmGetStatusWindowPos(otherHimc
, &pt
);
500 ok(rc
!= 0 || broken(rc
== 0), "ImmGetStatusWindowPos failed\n");
502 ImmReleaseContext(threadinfo
.hwnd
,otherHimc
);
503 ImmReleaseContext(hwnd
,himc
);
505 DestroyWindow(threadinfo
.hwnd
);
506 TerminateThread(hThread
, 1);
508 himc
= ImmGetContext(GetDesktopWindow());
509 todo_wine
ok(himc
== NULL
, "Should not be able to get himc from other process window\n");
512 static void test_ImmIsUIMessage(void)
520 static const struct test tests
[] =
522 { WM_MOUSEMOVE
, FALSE
},
523 { WM_IME_STARTCOMPOSITION
, TRUE
},
524 { WM_IME_ENDCOMPOSITION
, TRUE
},
525 { WM_IME_COMPOSITION
, TRUE
},
526 { WM_IME_SETCONTEXT
, TRUE
},
527 { WM_IME_NOTIFY
, TRUE
},
528 { WM_IME_CONTROL
, FALSE
},
529 { WM_IME_COMPOSITIONFULL
, TRUE
},
530 { WM_IME_SELECT
, TRUE
},
531 { WM_IME_CHAR
, FALSE
},
532 { 0x287 /* FIXME */, TRUE
},
533 { WM_IME_REQUEST
, FALSE
},
534 { WM_IME_KEYDOWN
, FALSE
},
535 { WM_IME_KEYUP
, FALSE
},
536 { 0, FALSE
} /* mark the end */
539 const struct test
*test
;
542 if (!pImmIsUIMessageA
) return;
544 for (test
= tests
; test
->msg
; test
++)
546 msg_spy_flush_msgs();
547 ret
= pImmIsUIMessageA(NULL
, test
->msg
, 0, 0);
548 ok(ret
== test
->ret
, "ImmIsUIMessageA returned %x for %x\n", ret
, test
->msg
);
549 ok(!msg_spy_find_msg(test
->msg
), "Windows does not send 0x%x for NULL hwnd\n", test
->msg
);
551 ret
= pImmIsUIMessageA(hwnd
, test
->msg
, 0, 0);
552 ok(ret
== test
->ret
, "ImmIsUIMessageA returned %x for %x\n", ret
, test
->msg
);
554 ok(msg_spy_find_msg(test
->msg
) != NULL
, "Windows does send 0x%x\n", test
->msg
);
556 ok(!msg_spy_find_msg(test
->msg
), "Windows does not send 0x%x\n", test
->msg
);
564 test_ImmGetCompositionString();
565 test_ImmSetCompositionString();
567 test_ImmAssociateContextEx();
569 test_ImmIsUIMessage();