1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2016 Sam Lantinga <slouken@libsdl.org>
4 
5   This software is provided 'as-is', without any express or implied
6   warranty.  In no event will the authors be held liable for any damages
7   arising from the use of this software.
8 
9   Permission is granted to anyone to use this software for any purpose,
10   including commercial applications, and to alter it and redistribute it
11   freely, subject to the following restrictions:
12 
13   1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17   2. Altered source versions must be plainly marked as such, and must not be
18      misrepresented as being the original software.
19   3. This notice may not be removed or altered from any source distribution.
20 */
21 #include "../../SDL_internal.h"
22 
23 #if SDL_VIDEO_DRIVER_WINDOWS
24 
25 #include "SDL_windowsvideo.h"
26 
27 #include "../../events/SDL_keyboard_c.h"
28 #include "../../events/scancodes_windows.h"
29 
30 #include <imm.h>
31 #include <oleauto.h>
32 
33 #ifndef SDL_DISABLE_WINDOWS_IME
34 static void IME_Init(SDL_VideoData *videodata, HWND hwnd);
35 static void IME_Enable(SDL_VideoData *videodata, HWND hwnd);
36 static void IME_Disable(SDL_VideoData *videodata, HWND hwnd);
37 static void IME_Quit(SDL_VideoData *videodata);
38 #endif /* !SDL_DISABLE_WINDOWS_IME */
39 
40 #ifndef MAPVK_VK_TO_VSC
41 #define MAPVK_VK_TO_VSC     0
42 #endif
43 #ifndef MAPVK_VSC_TO_VK
44 #define MAPVK_VSC_TO_VK     1
45 #endif
46 #ifndef MAPVK_VK_TO_CHAR
47 #define MAPVK_VK_TO_CHAR    2
48 #endif
49 
50 /* Alphabetic scancodes for PC keyboards */
51 void
WIN_InitKeyboard(_THIS)52 WIN_InitKeyboard(_THIS)
53 {
54     SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
55 
56     data->ime_com_initialized = SDL_FALSE;
57     data->ime_threadmgr = 0;
58     data->ime_initialized = SDL_FALSE;
59     data->ime_enabled = SDL_FALSE;
60     data->ime_available = SDL_FALSE;
61     data->ime_hwnd_main = 0;
62     data->ime_hwnd_current = 0;
63     data->ime_himc = 0;
64     data->ime_composition[0] = 0;
65     data->ime_readingstring[0] = 0;
66     data->ime_cursor = 0;
67 
68     data->ime_candlist = SDL_FALSE;
69     SDL_memset(data->ime_candidates, 0, sizeof(data->ime_candidates));
70     data->ime_candcount = 0;
71     data->ime_candref = 0;
72     data->ime_candsel = 0;
73     data->ime_candpgsize = 0;
74     data->ime_candlistindexbase = 0;
75     data->ime_candvertical = SDL_TRUE;
76 
77     data->ime_dirty = SDL_FALSE;
78     SDL_memset(&data->ime_rect, 0, sizeof(data->ime_rect));
79     SDL_memset(&data->ime_candlistrect, 0, sizeof(data->ime_candlistrect));
80     data->ime_winwidth = 0;
81     data->ime_winheight = 0;
82 
83     data->ime_hkl = 0;
84     data->ime_himm32 = 0;
85     data->GetReadingString = 0;
86     data->ShowReadingWindow = 0;
87     data->ImmLockIMC = 0;
88     data->ImmUnlockIMC = 0;
89     data->ImmLockIMCC = 0;
90     data->ImmUnlockIMCC = 0;
91     data->ime_uiless = SDL_FALSE;
92     data->ime_threadmgrex = 0;
93     data->ime_uielemsinkcookie = TF_INVALID_COOKIE;
94     data->ime_alpnsinkcookie = TF_INVALID_COOKIE;
95     data->ime_openmodesinkcookie = TF_INVALID_COOKIE;
96     data->ime_convmodesinkcookie = TF_INVALID_COOKIE;
97     data->ime_uielemsink = 0;
98     data->ime_ippasink = 0;
99 
100     WIN_UpdateKeymap();
101 
102     SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
103     SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
104     SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
105 
106     /* Are system caps/num/scroll lock active? Set our state to match. */
107     SDL_ToggleModState(KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) != 0);
108     SDL_ToggleModState(KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) != 0);
109 }
110 
111 void
WIN_UpdateKeymap()112 WIN_UpdateKeymap()
113 {
114     int i;
115     SDL_Scancode scancode;
116     SDL_Keycode keymap[SDL_NUM_SCANCODES];
117 
118     SDL_GetDefaultKeymap(keymap);
119 
120     for (i = 0; i < SDL_arraysize(windows_scancode_table); i++) {
121         int vk;
122         /* Make sure this scancode is a valid character scancode */
123         scancode = windows_scancode_table[i];
124         if (scancode == SDL_SCANCODE_UNKNOWN ) {
125             continue;
126         }
127 
128         /* If this key is one of the non-mappable keys, ignore it */
129         /* Not mapping numbers fixes the French layout, giving numeric keycodes for the number keys, which is the expected behavior */
130         if ((keymap[scancode] & SDLK_SCANCODE_MASK) ||
131             /*  scancode == SDL_SCANCODE_GRAVE || */ /* Uncomment this line to re-enable the behavior of not mapping the "`"(grave) key to the users actual keyboard layout */
132             (scancode >= SDL_SCANCODE_1 && scancode <= SDL_SCANCODE_0) ) {
133             continue;
134         }
135 
136         vk =  MapVirtualKey(i, MAPVK_VSC_TO_VK);
137         if ( vk ) {
138             int ch = (MapVirtualKey( vk, MAPVK_VK_TO_CHAR ) & 0x7FFF);
139             if ( ch ) {
140                 if ( ch >= 'A' && ch <= 'Z' ) {
141                     keymap[scancode] =  SDLK_a + ( ch - 'A' );
142                 } else {
143                     keymap[scancode] = ch;
144                 }
145             }
146         }
147     }
148 
149     SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES);
150 }
151 
152 void
WIN_QuitKeyboard(_THIS)153 WIN_QuitKeyboard(_THIS)
154 {
155 #ifndef SDL_DISABLE_WINDOWS_IME
156     IME_Quit((SDL_VideoData *)_this->driverdata);
157 #endif
158 }
159 
160 void
WIN_ResetDeadKeys()161 WIN_ResetDeadKeys()
162 {
163     /*
164     if a deadkey has been typed, but not the next character (which the deadkey might modify),
165     this tries to undo the effect pressing the deadkey.
166     see: http://archives.miloush.net/michkap/archive/2006/09/10/748775.html
167     */
168     BYTE keyboardState[256];
169     WCHAR buffer[16];
170     int keycode, scancode, result, i;
171 
172     GetKeyboardState(keyboardState);
173 
174     keycode = VK_SPACE;
175     scancode = MapVirtualKey(keycode, MAPVK_VK_TO_VSC);
176     if (scancode == 0) {
177         /* the keyboard doesn't have this key */
178         return;
179     }
180 
181     for (i = 0; i < 5; i++) {
182         result = ToUnicode(keycode, scancode, keyboardState, (LPWSTR)buffer, 16, 0);
183         if (result > 0) {
184             /* success */
185             return;
186         }
187     }
188 }
189 
190 void
WIN_StartTextInput(_THIS)191 WIN_StartTextInput(_THIS)
192 {
193 #ifndef SDL_DISABLE_WINDOWS_IME
194     SDL_Window *window;
195 #endif
196 
197     WIN_ResetDeadKeys();
198 
199 #ifndef SDL_DISABLE_WINDOWS_IME
200     window = SDL_GetKeyboardFocus();
201     if (window) {
202         HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
203         SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
204         SDL_GetWindowSize(window, &videodata->ime_winwidth, &videodata->ime_winheight);
205         IME_Init(videodata, hwnd);
206         IME_Enable(videodata, hwnd);
207     }
208 #endif /* !SDL_DISABLE_WINDOWS_IME */
209 }
210 
211 void
WIN_StopTextInput(_THIS)212 WIN_StopTextInput(_THIS)
213 {
214 #ifndef SDL_DISABLE_WINDOWS_IME
215     SDL_Window *window;
216 #endif
217 
218     WIN_ResetDeadKeys();
219 
220 #ifndef SDL_DISABLE_WINDOWS_IME
221     window = SDL_GetKeyboardFocus();
222     if (window) {
223         HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
224         SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
225         IME_Init(videodata, hwnd);
226         IME_Disable(videodata, hwnd);
227     }
228 #endif /* !SDL_DISABLE_WINDOWS_IME */
229 }
230 
231 void
WIN_SetTextInputRect(_THIS,SDL_Rect * rect)232 WIN_SetTextInputRect(_THIS, SDL_Rect *rect)
233 {
234     SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
235     HIMC himc = 0;
236 
237     if (!rect) {
238         SDL_InvalidParamError("rect");
239         return;
240     }
241 
242     videodata->ime_rect = *rect;
243 
244     himc = ImmGetContext(videodata->ime_hwnd_current);
245     if (himc)
246     {
247         COMPOSITIONFORM cf;
248         cf.ptCurrentPos.x = videodata->ime_rect.x;
249         cf.ptCurrentPos.y = videodata->ime_rect.y;
250         cf.dwStyle = CFS_FORCE_POSITION;
251         ImmSetCompositionWindow(himc, &cf);
252         ImmReleaseContext(videodata->ime_hwnd_current, himc);
253     }
254 }
255 
256 #ifdef SDL_DISABLE_WINDOWS_IME
257 
258 
259 SDL_bool
IME_HandleMessage(HWND hwnd,UINT msg,WPARAM wParam,LPARAM * lParam,SDL_VideoData * videodata)260 IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
261 {
262     return SDL_FALSE;
263 }
264 
IME_Present(SDL_VideoData * videodata)265 void IME_Present(SDL_VideoData *videodata)
266 {
267 }
268 
269 #else
270 
271 #ifdef _SDL_msctf_h
272 #define USE_INIT_GUID
273 #elif defined(__GNUC__)
274 #define USE_INIT_GUID
275 #endif
276 #ifdef USE_INIT_GUID
277 #undef DEFINE_GUID
278 #define DEFINE_GUID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const GUID n = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}}
279 DEFINE_GUID(IID_ITfInputProcessorProfileActivationSink,        0x71C6E74E,0x0F28,0x11D8,0xA8,0x2A,0x00,0x06,0x5B,0x84,0x43,0x5C);
280 DEFINE_GUID(IID_ITfUIElementSink,                              0xEA1EA136,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
281 DEFINE_GUID(GUID_TFCAT_TIP_KEYBOARD,                           0x34745C63,0xB2F0,0x4784,0x8B,0x67,0x5E,0x12,0xC8,0x70,0x1A,0x31);
282 DEFINE_GUID(IID_ITfSource,                                     0x4EA48A35,0x60AE,0x446F,0x8F,0xD6,0xE6,0xA8,0xD8,0x24,0x59,0xF7);
283 DEFINE_GUID(IID_ITfUIElementMgr,                               0xEA1EA135,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
284 DEFINE_GUID(IID_ITfCandidateListUIElement,                     0xEA1EA138,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
285 DEFINE_GUID(IID_ITfReadingInformationUIElement,                0xEA1EA139,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
286 DEFINE_GUID(IID_ITfThreadMgr,                                  0xAA80E801,0x2021,0x11D2,0x93,0xE0,0x00,0x60,0xB0,0x67,0xB8,0x6E);
287 DEFINE_GUID(CLSID_TF_ThreadMgr,                                0x529A9E6B,0x6587,0x4F23,0xAB,0x9E,0x9C,0x7D,0x68,0x3E,0x3C,0x50);
288 DEFINE_GUID(IID_ITfThreadMgrEx,                                0x3E90ADE3,0x7594,0x4CB0,0xBB,0x58,0x69,0x62,0x8F,0x5F,0x45,0x8C);
289 #endif
290 
291 #define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)
292 #define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
293 
294 #define MAKEIMEVERSION(major,minor) ((DWORD) (((BYTE)(major) << 24) | ((BYTE)(minor) << 16) ))
295 #define IMEID_VER(id) ((id) & 0xffff0000)
296 #define IMEID_LANG(id) ((id) & 0x0000ffff)
297 
298 #define CHT_HKL_DAYI            ((HKL)0xE0060404)
299 #define CHT_HKL_NEW_PHONETIC    ((HKL)0xE0080404)
300 #define CHT_HKL_NEW_CHANG_JIE   ((HKL)0xE0090404)
301 #define CHT_HKL_NEW_QUICK       ((HKL)0xE00A0404)
302 #define CHT_HKL_HK_CANTONESE    ((HKL)0xE00B0404)
303 #define CHT_IMEFILENAME1        "TINTLGNT.IME"
304 #define CHT_IMEFILENAME2        "CINTLGNT.IME"
305 #define CHT_IMEFILENAME3        "MSTCIPHA.IME"
306 #define IMEID_CHT_VER42         (LANG_CHT | MAKEIMEVERSION(4, 2))
307 #define IMEID_CHT_VER43         (LANG_CHT | MAKEIMEVERSION(4, 3))
308 #define IMEID_CHT_VER44         (LANG_CHT | MAKEIMEVERSION(4, 4))
309 #define IMEID_CHT_VER50         (LANG_CHT | MAKEIMEVERSION(5, 0))
310 #define IMEID_CHT_VER51         (LANG_CHT | MAKEIMEVERSION(5, 1))
311 #define IMEID_CHT_VER52         (LANG_CHT | MAKEIMEVERSION(5, 2))
312 #define IMEID_CHT_VER60         (LANG_CHT | MAKEIMEVERSION(6, 0))
313 #define IMEID_CHT_VER_VISTA     (LANG_CHT | MAKEIMEVERSION(7, 0))
314 
315 #define CHS_HKL                 ((HKL)0xE00E0804)
316 #define CHS_IMEFILENAME1        "PINTLGNT.IME"
317 #define CHS_IMEFILENAME2        "MSSCIPYA.IME"
318 #define IMEID_CHS_VER41         (LANG_CHS | MAKEIMEVERSION(4, 1))
319 #define IMEID_CHS_VER42         (LANG_CHS | MAKEIMEVERSION(4, 2))
320 #define IMEID_CHS_VER53         (LANG_CHS | MAKEIMEVERSION(5, 3))
321 
322 #define LANG() LOWORD((videodata->ime_hkl))
323 #define PRIMLANG() ((WORD)PRIMARYLANGID(LANG()))
324 #define SUBLANG() SUBLANGID(LANG())
325 
326 static void IME_UpdateInputLocale(SDL_VideoData *videodata);
327 static void IME_ClearComposition(SDL_VideoData *videodata);
328 static void IME_SetWindow(SDL_VideoData* videodata, HWND hwnd);
329 static void IME_SetupAPI(SDL_VideoData *videodata);
330 static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex);
331 static void IME_SendEditingEvent(SDL_VideoData *videodata);
332 static void IME_DestroyTextures(SDL_VideoData *videodata);
333 
334 #define SDL_IsEqualIID(riid1, riid2) SDL_IsEqualGUID(riid1, riid2)
335 #define SDL_IsEqualGUID(rguid1, rguid2) (!SDL_memcmp(rguid1, rguid2, sizeof(GUID)))
336 
337 static SDL_bool UILess_SetupSinks(SDL_VideoData *videodata);
338 static void UILess_ReleaseSinks(SDL_VideoData *videodata);
339 static void UILess_EnableUIUpdates(SDL_VideoData *videodata);
340 static void UILess_DisableUIUpdates(SDL_VideoData *videodata);
341 
342 static void
IME_Init(SDL_VideoData * videodata,HWND hwnd)343 IME_Init(SDL_VideoData *videodata, HWND hwnd)
344 {
345     if (videodata->ime_initialized)
346         return;
347 
348     videodata->ime_hwnd_main = hwnd;
349     if (SUCCEEDED(WIN_CoInitialize())) {
350         videodata->ime_com_initialized = SDL_TRUE;
351         CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (LPVOID *)&videodata->ime_threadmgr);
352     }
353     videodata->ime_initialized = SDL_TRUE;
354     videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
355     if (!videodata->ime_himm32) {
356         videodata->ime_available = SDL_FALSE;
357         return;
358     }
359     videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC");
360     videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC");
361     videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC");
362     videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC");
363 
364     IME_SetWindow(videodata, hwnd);
365     videodata->ime_himc = ImmGetContext(hwnd);
366     ImmReleaseContext(hwnd, videodata->ime_himc);
367     if (!videodata->ime_himc) {
368         videodata->ime_available = SDL_FALSE;
369         IME_Disable(videodata, hwnd);
370         return;
371     }
372     videodata->ime_available = SDL_TRUE;
373     IME_UpdateInputLocale(videodata);
374     IME_SetupAPI(videodata);
375     videodata->ime_uiless = UILess_SetupSinks(videodata);
376     IME_UpdateInputLocale(videodata);
377     IME_Disable(videodata, hwnd);
378 }
379 
380 static void
IME_Enable(SDL_VideoData * videodata,HWND hwnd)381 IME_Enable(SDL_VideoData *videodata, HWND hwnd)
382 {
383     if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
384         return;
385 
386     if (!videodata->ime_available) {
387         IME_Disable(videodata, hwnd);
388         return;
389     }
390     if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
391         ImmAssociateContext(videodata->ime_hwnd_current, videodata->ime_himc);
392 
393     videodata->ime_enabled = SDL_TRUE;
394     IME_UpdateInputLocale(videodata);
395     UILess_EnableUIUpdates(videodata);
396 }
397 
398 static void
IME_Disable(SDL_VideoData * videodata,HWND hwnd)399 IME_Disable(SDL_VideoData *videodata, HWND hwnd)
400 {
401     if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
402         return;
403 
404     IME_ClearComposition(videodata);
405     if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
406         ImmAssociateContext(videodata->ime_hwnd_current, (HIMC)0);
407 
408     videodata->ime_enabled = SDL_FALSE;
409     UILess_DisableUIUpdates(videodata);
410 }
411 
412 static void
IME_Quit(SDL_VideoData * videodata)413 IME_Quit(SDL_VideoData *videodata)
414 {
415     if (!videodata->ime_initialized)
416         return;
417 
418     UILess_ReleaseSinks(videodata);
419     if (videodata->ime_hwnd_main)
420         ImmAssociateContext(videodata->ime_hwnd_main, videodata->ime_himc);
421 
422     videodata->ime_hwnd_main = 0;
423     videodata->ime_himc = 0;
424     if (videodata->ime_himm32) {
425         SDL_UnloadObject(videodata->ime_himm32);
426         videodata->ime_himm32 = 0;
427     }
428     if (videodata->ime_threadmgr) {
429         videodata->ime_threadmgr->lpVtbl->Release(videodata->ime_threadmgr);
430         videodata->ime_threadmgr = 0;
431     }
432     if (videodata->ime_com_initialized) {
433         WIN_CoUninitialize();
434         videodata->ime_com_initialized = SDL_FALSE;
435     }
436     IME_DestroyTextures(videodata);
437     videodata->ime_initialized = SDL_FALSE;
438 }
439 
440 static void
IME_GetReadingString(SDL_VideoData * videodata,HWND hwnd)441 IME_GetReadingString(SDL_VideoData *videodata, HWND hwnd)
442 {
443     DWORD id = 0;
444     HIMC himc = 0;
445     WCHAR buffer[16];
446     WCHAR *s = buffer;
447     DWORD len = 0;
448     INT err = 0;
449     BOOL vertical = FALSE;
450     UINT maxuilen = 0;
451     static OSVERSIONINFOA osversion;
452 
453     if (videodata->ime_uiless)
454         return;
455 
456     videodata->ime_readingstring[0] = 0;
457     if (!osversion.dwOSVersionInfoSize) {
458         osversion.dwOSVersionInfoSize = sizeof(osversion);
459         GetVersionExA(&osversion);
460     }
461     id = IME_GetId(videodata, 0);
462     if (!id)
463         return;
464 
465     himc = ImmGetContext(hwnd);
466     if (!himc)
467         return;
468 
469     if (videodata->GetReadingString) {
470         len = videodata->GetReadingString(himc, 0, 0, &err, &vertical, &maxuilen);
471         if (len) {
472             if (len > SDL_arraysize(buffer))
473                 len = SDL_arraysize(buffer);
474 
475             len = videodata->GetReadingString(himc, len, s, &err, &vertical, &maxuilen);
476         }
477         SDL_wcslcpy(videodata->ime_readingstring, s, len);
478     }
479     else {
480         LPINPUTCONTEXT2 lpimc = videodata->ImmLockIMC(himc);
481         LPBYTE p = 0;
482         s = 0;
483         switch (id)
484         {
485         case IMEID_CHT_VER42:
486         case IMEID_CHT_VER43:
487         case IMEID_CHT_VER44:
488             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 24);
489             if (!p)
490                 break;
491 
492             len = *(DWORD *)(p + 7*4 + 32*4);
493             s = (WCHAR *)(p + 56);
494             break;
495         case IMEID_CHT_VER51:
496         case IMEID_CHT_VER52:
497         case IMEID_CHS_VER53:
498             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 4);
499             if (!p)
500                 break;
501 
502             p = *(LPBYTE *)((LPBYTE)p + 1*4 + 5*4);
503             if (!p)
504                 break;
505 
506             len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
507             s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
508             break;
509         case IMEID_CHS_VER41:
510             {
511                 int offset = (IME_GetId(videodata, 1) >= 0x00000002) ? 8 : 7;
512                 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + offset * 4);
513                 if (!p)
514                     break;
515 
516                 len = *(DWORD *)(p + 7*4 + 16*2*4);
517                 s = (WCHAR *)(p + 6*4 + 16*2*1);
518             }
519             break;
520         case IMEID_CHS_VER42:
521             if (osversion.dwPlatformId != VER_PLATFORM_WIN32_NT)
522                 break;
523 
524             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 1*4 + 1*4 + 6*4);
525             if (!p)
526                 break;
527 
528             len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
529             s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
530             break;
531         }
532         if (s) {
533             size_t size = SDL_min((size_t)(len + 1), SDL_arraysize(videodata->ime_readingstring));
534             SDL_wcslcpy(videodata->ime_readingstring, s, size);
535         }
536 
537         videodata->ImmUnlockIMCC(lpimc->hPrivate);
538         videodata->ImmUnlockIMC(himc);
539     }
540     ImmReleaseContext(hwnd, himc);
541     IME_SendEditingEvent(videodata);
542 }
543 
544 static void
IME_InputLangChanged(SDL_VideoData * videodata)545 IME_InputLangChanged(SDL_VideoData *videodata)
546 {
547     UINT lang = PRIMLANG();
548     IME_UpdateInputLocale(videodata);
549     if (!videodata->ime_uiless)
550         videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1;
551 
552     IME_SetupAPI(videodata);
553     if (lang != PRIMLANG()) {
554         IME_ClearComposition(videodata);
555     }
556 }
557 
558 static DWORD
IME_GetId(SDL_VideoData * videodata,UINT uIndex)559 IME_GetId(SDL_VideoData *videodata, UINT uIndex)
560 {
561     static HKL hklprev = 0;
562     static DWORD dwRet[2] = {0};
563     DWORD dwVerSize = 0;
564     DWORD dwVerHandle = 0;
565     LPVOID lpVerBuffer = 0;
566     LPVOID lpVerData = 0;
567     UINT cbVerData = 0;
568     char szTemp[256];
569     HKL hkl = 0;
570     DWORD dwLang = 0;
571     if (uIndex >= sizeof(dwRet) / sizeof(dwRet[0]))
572         return 0;
573 
574     hkl = videodata->ime_hkl;
575     if (hklprev == hkl)
576         return dwRet[uIndex];
577 
578     hklprev = hkl;
579     dwLang = ((DWORD_PTR)hkl & 0xffff);
580     if (videodata->ime_uiless && LANG() == LANG_CHT) {
581         dwRet[0] = IMEID_CHT_VER_VISTA;
582         dwRet[1] = 0;
583         return dwRet[0];
584     }
585     if (hkl != CHT_HKL_NEW_PHONETIC
586         && hkl != CHT_HKL_NEW_CHANG_JIE
587         && hkl != CHT_HKL_NEW_QUICK
588         && hkl != CHT_HKL_HK_CANTONESE
589         && hkl != CHS_HKL) {
590         dwRet[0] = dwRet[1] = 0;
591         return dwRet[uIndex];
592     }
593     if (ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1) <= 0) {
594         dwRet[0] = dwRet[1] = 0;
595         return dwRet[uIndex];
596     }
597     if (!videodata->GetReadingString) {
598         #define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
599         if (CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME1, -1) != 2
600             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME2, -1) != 2
601             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME3, -1) != 2
602             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME1, -1) != 2
603             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME2, -1) != 2) {
604             dwRet[0] = dwRet[1] = 0;
605             return dwRet[uIndex];
606         }
607         #undef LCID_INVARIANT
608         dwVerSize = GetFileVersionInfoSizeA(szTemp, &dwVerHandle);
609         if (dwVerSize) {
610             lpVerBuffer = SDL_malloc(dwVerSize);
611             if (lpVerBuffer) {
612                 if (GetFileVersionInfoA(szTemp, dwVerHandle, dwVerSize, lpVerBuffer)) {
613                     if (VerQueryValueA(lpVerBuffer, "\\", &lpVerData, &cbVerData)) {
614                         #define pVerFixedInfo   ((VS_FIXEDFILEINFO FAR*)lpVerData)
615                         DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
616                         dwVer = (dwVer & 0x00ff0000) << 8 | (dwVer & 0x000000ff) << 16;
617                         if ((videodata->GetReadingString) ||
618                             ((dwLang == LANG_CHT) && (
619                             dwVer == MAKEIMEVERSION(4, 2) ||
620                             dwVer == MAKEIMEVERSION(4, 3) ||
621                             dwVer == MAKEIMEVERSION(4, 4) ||
622                             dwVer == MAKEIMEVERSION(5, 0) ||
623                             dwVer == MAKEIMEVERSION(5, 1) ||
624                             dwVer == MAKEIMEVERSION(5, 2) ||
625                             dwVer == MAKEIMEVERSION(6, 0)))
626                             ||
627                             ((dwLang == LANG_CHS) && (
628                             dwVer == MAKEIMEVERSION(4, 1) ||
629                             dwVer == MAKEIMEVERSION(4, 2) ||
630                             dwVer == MAKEIMEVERSION(5, 3)))) {
631                             dwRet[0] = dwVer | dwLang;
632                             dwRet[1] = pVerFixedInfo->dwFileVersionLS;
633                             SDL_free(lpVerBuffer);
634                             return dwRet[0];
635                         }
636                         #undef pVerFixedInfo
637                     }
638                 }
639             }
640             SDL_free(lpVerBuffer);
641         }
642     }
643     dwRet[0] = dwRet[1] = 0;
644     return dwRet[uIndex];
645 }
646 
647 static void
IME_SetupAPI(SDL_VideoData * videodata)648 IME_SetupAPI(SDL_VideoData *videodata)
649 {
650     char ime_file[MAX_PATH + 1];
651     void* hime = 0;
652     HKL hkl = 0;
653     videodata->GetReadingString = 0;
654     videodata->ShowReadingWindow = 0;
655     if (videodata->ime_uiless)
656         return;
657 
658     hkl = videodata->ime_hkl;
659     if (ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1) <= 0)
660         return;
661 
662     hime = SDL_LoadObject(ime_file);
663     if (!hime)
664         return;
665 
666     videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT))
667         SDL_LoadFunction(hime, "GetReadingString");
668     videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL))
669         SDL_LoadFunction(hime, "ShowReadingWindow");
670 
671     if (videodata->ShowReadingWindow) {
672         HIMC himc = ImmGetContext(videodata->ime_hwnd_current);
673         if (himc) {
674             videodata->ShowReadingWindow(himc, FALSE);
675             ImmReleaseContext(videodata->ime_hwnd_current, himc);
676         }
677     }
678 }
679 
680 static void
IME_SetWindow(SDL_VideoData * videodata,HWND hwnd)681 IME_SetWindow(SDL_VideoData* videodata, HWND hwnd)
682 {
683     videodata->ime_hwnd_current = hwnd;
684     if (videodata->ime_threadmgr) {
685         struct ITfDocumentMgr *document_mgr = 0;
686         if (SUCCEEDED(videodata->ime_threadmgr->lpVtbl->AssociateFocus(videodata->ime_threadmgr, hwnd, NULL, &document_mgr))) {
687             if (document_mgr)
688                 document_mgr->lpVtbl->Release(document_mgr);
689         }
690     }
691 }
692 
693 static void
IME_UpdateInputLocale(SDL_VideoData * videodata)694 IME_UpdateInputLocale(SDL_VideoData *videodata)
695 {
696     static HKL hklprev = 0;
697     videodata->ime_hkl = GetKeyboardLayout(0);
698     if (hklprev == videodata->ime_hkl)
699         return;
700 
701     hklprev = videodata->ime_hkl;
702     switch (PRIMLANG()) {
703     case LANG_CHINESE:
704         videodata->ime_candvertical = SDL_TRUE;
705         if (SUBLANG() == SUBLANG_CHINESE_SIMPLIFIED)
706             videodata->ime_candvertical = SDL_FALSE;
707 
708         break;
709     case LANG_JAPANESE:
710         videodata->ime_candvertical = SDL_TRUE;
711         break;
712     case LANG_KOREAN:
713         videodata->ime_candvertical = SDL_FALSE;
714         break;
715     }
716 }
717 
718 static void
IME_ClearComposition(SDL_VideoData * videodata)719 IME_ClearComposition(SDL_VideoData *videodata)
720 {
721     HIMC himc = 0;
722     if (!videodata->ime_initialized)
723         return;
724 
725     himc = ImmGetContext(videodata->ime_hwnd_current);
726     if (!himc)
727         return;
728 
729     ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
730     if (videodata->ime_uiless)
731         ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
732 
733     ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0);
734     ImmReleaseContext(videodata->ime_hwnd_current, himc);
735     SDL_SendEditingText("", 0, 0);
736 }
737 
738 static void
IME_GetCompositionString(SDL_VideoData * videodata,HIMC himc,DWORD string)739 IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
740 {
741     LONG length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, sizeof(videodata->ime_composition) - sizeof(videodata->ime_composition[0]));
742     if (length < 0)
743         length = 0;
744 
745     length /= sizeof(videodata->ime_composition[0]);
746     videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
747     if (videodata->ime_cursor < SDL_arraysize(videodata->ime_composition) && videodata->ime_composition[videodata->ime_cursor] == 0x3000) {
748         int i;
749         for (i = videodata->ime_cursor + 1; i < length; ++i)
750             videodata->ime_composition[i - 1] = videodata->ime_composition[i];
751 
752         --length;
753     }
754     videodata->ime_composition[length] = 0;
755 }
756 
757 static void
IME_SendInputEvent(SDL_VideoData * videodata)758 IME_SendInputEvent(SDL_VideoData *videodata)
759 {
760     char *s = 0;
761     s = WIN_StringToUTF8(videodata->ime_composition);
762     SDL_SendKeyboardText(s);
763     SDL_free(s);
764 
765     videodata->ime_composition[0] = 0;
766     videodata->ime_readingstring[0] = 0;
767     videodata->ime_cursor = 0;
768 }
769 
770 static void
IME_SendEditingEvent(SDL_VideoData * videodata)771 IME_SendEditingEvent(SDL_VideoData *videodata)
772 {
773     char *s = 0;
774     WCHAR buffer[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
775     const size_t size = SDL_arraysize(buffer);
776     buffer[0] = 0;
777     if (videodata->ime_readingstring[0]) {
778         size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);
779         SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
780         SDL_wcslcat(buffer, videodata->ime_readingstring, size);
781         SDL_wcslcat(buffer, &videodata->ime_composition[len], size);
782     }
783     else {
784         SDL_wcslcpy(buffer, videodata->ime_composition, size);
785     }
786     s = WIN_StringToUTF8(buffer);
787     SDL_SendEditingText(s, videodata->ime_cursor + (int)SDL_wcslen(videodata->ime_readingstring), 0);
788     SDL_free(s);
789 }
790 
791 static void
IME_AddCandidate(SDL_VideoData * videodata,UINT i,LPCWSTR candidate)792 IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate)
793 {
794     LPWSTR dst = videodata->ime_candidates[i];
795     *dst++ = (WCHAR)(TEXT('0') + ((i + videodata->ime_candlistindexbase) % 10));
796     if (videodata->ime_candvertical)
797         *dst++ = TEXT(' ');
798 
799     while (*candidate && (SDL_arraysize(videodata->ime_candidates[i]) > (dst - videodata->ime_candidates[i])))
800         *dst++ = *candidate++;
801 
802     *dst = (WCHAR)'\0';
803 }
804 
805 static void
IME_GetCandidateList(HIMC himc,SDL_VideoData * videodata)806 IME_GetCandidateList(HIMC himc, SDL_VideoData *videodata)
807 {
808     LPCANDIDATELIST cand_list = 0;
809     DWORD size = ImmGetCandidateListW(himc, 0, 0, 0);
810     if (size) {
811         cand_list = (LPCANDIDATELIST)SDL_malloc(size);
812         if (cand_list) {
813             size = ImmGetCandidateListW(himc, 0, cand_list, size);
814             if (size) {
815                 UINT i, j;
816                 UINT page_start = 0;
817                 videodata->ime_candsel = cand_list->dwSelection;
818                 videodata->ime_candcount = cand_list->dwCount;
819 
820                 if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) {
821                     const UINT maxcandchar = 18;
822                     size_t cchars = 0;
823 
824                     for (i = 0; i < videodata->ime_candcount; ++i) {
825                         size_t len = SDL_wcslen((LPWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i])) + 1;
826                         if (len + cchars > maxcandchar) {
827                             if (i > cand_list->dwSelection)
828                                 break;
829 
830                             page_start = i;
831                             cchars = len;
832                         }
833                         else {
834                             cchars += len;
835                         }
836                     }
837                     videodata->ime_candpgsize = i - page_start;
838                 } else {
839                     videodata->ime_candpgsize = SDL_min(cand_list->dwPageSize, MAX_CANDLIST);
840                     page_start = (cand_list->dwSelection / videodata->ime_candpgsize) * videodata->ime_candpgsize;
841                 }
842                 SDL_memset(&videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
843                 for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < (int)videodata->ime_candpgsize; i++, j++) {
844                     LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]);
845                     IME_AddCandidate(videodata, j, candidate);
846                 }
847                 if (PRIMLANG() == LANG_KOREAN || (PRIMLANG() == LANG_CHT && !IME_GetId(videodata, 0)))
848                     videodata->ime_candsel = -1;
849 
850             }
851             SDL_free(cand_list);
852         }
853     }
854 }
855 
856 static void
IME_ShowCandidateList(SDL_VideoData * videodata)857 IME_ShowCandidateList(SDL_VideoData *videodata)
858 {
859     videodata->ime_dirty = SDL_TRUE;
860     videodata->ime_candlist = SDL_TRUE;
861     IME_DestroyTextures(videodata);
862     IME_SendEditingEvent(videodata);
863 }
864 
865 static void
IME_HideCandidateList(SDL_VideoData * videodata)866 IME_HideCandidateList(SDL_VideoData *videodata)
867 {
868     videodata->ime_dirty = SDL_FALSE;
869     videodata->ime_candlist = SDL_FALSE;
870     IME_DestroyTextures(videodata);
871     IME_SendEditingEvent(videodata);
872 }
873 
874 SDL_bool
IME_HandleMessage(HWND hwnd,UINT msg,WPARAM wParam,LPARAM * lParam,SDL_VideoData * videodata)875 IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
876 {
877     SDL_bool trap = SDL_FALSE;
878     HIMC himc = 0;
879     if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled)
880         return SDL_FALSE;
881 
882     switch (msg) {
883     case WM_INPUTLANGCHANGE:
884         IME_InputLangChanged(videodata);
885         break;
886     case WM_IME_SETCONTEXT:
887         *lParam = 0;
888         break;
889     case WM_IME_STARTCOMPOSITION:
890         trap = SDL_TRUE;
891         break;
892     case WM_IME_COMPOSITION:
893         trap = SDL_TRUE;
894         himc = ImmGetContext(hwnd);
895         if (*lParam & GCS_RESULTSTR) {
896             IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
897             IME_SendInputEvent(videodata);
898         }
899         if (*lParam & GCS_COMPSTR) {
900             if (!videodata->ime_uiless)
901                 videodata->ime_readingstring[0] = 0;
902 
903             IME_GetCompositionString(videodata, himc, GCS_COMPSTR);
904             IME_SendEditingEvent(videodata);
905         }
906         ImmReleaseContext(hwnd, himc);
907         break;
908     case WM_IME_ENDCOMPOSITION:
909         videodata->ime_composition[0] = 0;
910         videodata->ime_readingstring[0] = 0;
911         videodata->ime_cursor = 0;
912         SDL_SendEditingText("", 0, 0);
913         break;
914     case WM_IME_NOTIFY:
915         switch (wParam) {
916         case IMN_SETCONVERSIONMODE:
917         case IMN_SETOPENSTATUS:
918             IME_UpdateInputLocale(videodata);
919             break;
920         case IMN_OPENCANDIDATE:
921         case IMN_CHANGECANDIDATE:
922             if (videodata->ime_uiless)
923                 break;
924 
925             trap = SDL_TRUE;
926             IME_ShowCandidateList(videodata);
927             himc = ImmGetContext(hwnd);
928             if (!himc)
929                 break;
930 
931             IME_GetCandidateList(himc, videodata);
932             ImmReleaseContext(hwnd, himc);
933             break;
934         case IMN_CLOSECANDIDATE:
935             trap = SDL_TRUE;
936             IME_HideCandidateList(videodata);
937             break;
938         case IMN_PRIVATE:
939             {
940                 DWORD dwId = IME_GetId(videodata, 0);
941                 IME_GetReadingString(videodata, hwnd);
942                 switch (dwId)
943                 {
944                 case IMEID_CHT_VER42:
945                 case IMEID_CHT_VER43:
946                 case IMEID_CHT_VER44:
947                 case IMEID_CHS_VER41:
948                 case IMEID_CHS_VER42:
949                     if (*lParam == 1 || *lParam == 2)
950                         trap = SDL_TRUE;
951 
952                     break;
953                 case IMEID_CHT_VER50:
954                 case IMEID_CHT_VER51:
955                 case IMEID_CHT_VER52:
956                 case IMEID_CHT_VER60:
957                 case IMEID_CHS_VER53:
958                     if (*lParam == 16
959                         || *lParam == 17
960                         || *lParam == 26
961                         || *lParam == 27
962                         || *lParam == 28)
963                         trap = SDL_TRUE;
964                     break;
965                 }
966             }
967             break;
968         default:
969             trap = SDL_TRUE;
970             break;
971         }
972         break;
973     }
974     return trap;
975 }
976 
977 static void
IME_CloseCandidateList(SDL_VideoData * videodata)978 IME_CloseCandidateList(SDL_VideoData *videodata)
979 {
980     IME_HideCandidateList(videodata);
981     videodata->ime_candcount = 0;
982     SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
983 }
984 
985 static void
UILess_GetCandidateList(SDL_VideoData * videodata,ITfCandidateListUIElement * pcandlist)986 UILess_GetCandidateList(SDL_VideoData *videodata, ITfCandidateListUIElement *pcandlist)
987 {
988     UINT selection = 0;
989     UINT count = 0;
990     UINT page = 0;
991     UINT pgcount = 0;
992     DWORD pgstart = 0;
993     DWORD pgsize = 0;
994     UINT i, j;
995     pcandlist->lpVtbl->GetSelection(pcandlist, &selection);
996     pcandlist->lpVtbl->GetCount(pcandlist, &count);
997     pcandlist->lpVtbl->GetCurrentPage(pcandlist, &page);
998 
999     videodata->ime_candsel = selection;
1000     videodata->ime_candcount = count;
1001     IME_ShowCandidateList(videodata);
1002 
1003     pcandlist->lpVtbl->GetPageIndex(pcandlist, 0, 0, &pgcount);
1004     if (pgcount > 0) {
1005         UINT *idxlist = SDL_malloc(sizeof(UINT) * pgcount);
1006         if (idxlist) {
1007             pcandlist->lpVtbl->GetPageIndex(pcandlist, idxlist, pgcount, &pgcount);
1008             pgstart = idxlist[page];
1009             if (page < pgcount - 1)
1010                 pgsize = SDL_min(count, idxlist[page + 1]) - pgstart;
1011             else
1012                 pgsize = count - pgstart;
1013 
1014             SDL_free(idxlist);
1015         }
1016     }
1017     videodata->ime_candpgsize = SDL_min(pgsize, MAX_CANDLIST);
1018     videodata->ime_candsel = videodata->ime_candsel - pgstart;
1019 
1020     SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
1021     for (i = pgstart, j = 0; (DWORD)i < count && j < videodata->ime_candpgsize; i++, j++) {
1022         BSTR bstr;
1023         if (SUCCEEDED(pcandlist->lpVtbl->GetString(pcandlist, i, &bstr))) {
1024             if (bstr) {
1025                 IME_AddCandidate(videodata, j, bstr);
1026                 SysFreeString(bstr);
1027             }
1028         }
1029     }
1030     if (PRIMLANG() == LANG_KOREAN)
1031         videodata->ime_candsel = -1;
1032 }
1033 
TSFSink_AddRef(TSFSink * sink)1034 STDMETHODIMP_(ULONG) TSFSink_AddRef(TSFSink *sink)
1035 {
1036     return ++sink->refcount;
1037 }
1038 
TSFSink_Release(TSFSink * sink)1039 STDMETHODIMP_(ULONG)TSFSink_Release(TSFSink *sink)
1040 {
1041     --sink->refcount;
1042     if (sink->refcount == 0) {
1043         SDL_free(sink);
1044         return 0;
1045     }
1046     return sink->refcount;
1047 }
1048 
UIElementSink_QueryInterface(TSFSink * sink,REFIID riid,PVOID * ppv)1049 STDMETHODIMP UIElementSink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
1050 {
1051     if (!ppv)
1052         return E_INVALIDARG;
1053 
1054     *ppv = 0;
1055     if (SDL_IsEqualIID(riid, &IID_IUnknown))
1056         *ppv = (IUnknown *)sink;
1057     else if (SDL_IsEqualIID(riid, &IID_ITfUIElementSink))
1058         *ppv = (ITfUIElementSink *)sink;
1059 
1060     if (*ppv) {
1061         TSFSink_AddRef(sink);
1062         return S_OK;
1063     }
1064     return E_NOINTERFACE;
1065 }
1066 
UILess_GetUIElement(SDL_VideoData * videodata,DWORD dwUIElementId)1067 ITfUIElement *UILess_GetUIElement(SDL_VideoData *videodata, DWORD dwUIElementId)
1068 {
1069     ITfUIElementMgr *puiem = 0;
1070     ITfUIElement *pelem = 0;
1071     ITfThreadMgrEx *threadmgrex = videodata->ime_threadmgrex;
1072 
1073     if (SUCCEEDED(threadmgrex->lpVtbl->QueryInterface(threadmgrex, &IID_ITfUIElementMgr, (LPVOID *)&puiem))) {
1074         puiem->lpVtbl->GetUIElement(puiem, dwUIElementId, &pelem);
1075         puiem->lpVtbl->Release(puiem);
1076     }
1077     return pelem;
1078 }
1079 
UIElementSink_BeginUIElement(TSFSink * sink,DWORD dwUIElementId,BOOL * pbShow)1080 STDMETHODIMP UIElementSink_BeginUIElement(TSFSink *sink, DWORD dwUIElementId, BOOL *pbShow)
1081 {
1082     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1083     ITfReadingInformationUIElement *preading = 0;
1084     ITfCandidateListUIElement *pcandlist = 0;
1085     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1086     if (!element)
1087         return E_INVALIDARG;
1088 
1089     *pbShow = FALSE;
1090     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1091         BSTR bstr;
1092         if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
1093             SysFreeString(bstr);
1094         }
1095         preading->lpVtbl->Release(preading);
1096     }
1097     else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1098         videodata->ime_candref++;
1099         UILess_GetCandidateList(videodata, pcandlist);
1100         pcandlist->lpVtbl->Release(pcandlist);
1101     }
1102     return S_OK;
1103 }
1104 
UIElementSink_UpdateUIElement(TSFSink * sink,DWORD dwUIElementId)1105 STDMETHODIMP UIElementSink_UpdateUIElement(TSFSink *sink, DWORD dwUIElementId)
1106 {
1107     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1108     ITfReadingInformationUIElement *preading = 0;
1109     ITfCandidateListUIElement *pcandlist = 0;
1110     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1111     if (!element)
1112         return E_INVALIDARG;
1113 
1114     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1115         BSTR bstr;
1116         if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
1117             WCHAR *s = (WCHAR *)bstr;
1118             SDL_wcslcpy(videodata->ime_readingstring, s, SDL_arraysize(videodata->ime_readingstring));
1119             IME_SendEditingEvent(videodata);
1120             SysFreeString(bstr);
1121         }
1122         preading->lpVtbl->Release(preading);
1123     }
1124     else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1125         UILess_GetCandidateList(videodata, pcandlist);
1126         pcandlist->lpVtbl->Release(pcandlist);
1127     }
1128     return S_OK;
1129 }
1130 
UIElementSink_EndUIElement(TSFSink * sink,DWORD dwUIElementId)1131 STDMETHODIMP UIElementSink_EndUIElement(TSFSink *sink, DWORD dwUIElementId)
1132 {
1133     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1134     ITfReadingInformationUIElement *preading = 0;
1135     ITfCandidateListUIElement *pcandlist = 0;
1136     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1137     if (!element)
1138         return E_INVALIDARG;
1139 
1140     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1141         videodata->ime_readingstring[0] = 0;
1142         IME_SendEditingEvent(videodata);
1143         preading->lpVtbl->Release(preading);
1144     }
1145     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1146         videodata->ime_candref--;
1147         if (videodata->ime_candref == 0)
1148             IME_CloseCandidateList(videodata);
1149 
1150         pcandlist->lpVtbl->Release(pcandlist);
1151     }
1152     return S_OK;
1153 }
1154 
IPPASink_QueryInterface(TSFSink * sink,REFIID riid,PVOID * ppv)1155 STDMETHODIMP IPPASink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
1156 {
1157     if (!ppv)
1158         return E_INVALIDARG;
1159 
1160     *ppv = 0;
1161     if (SDL_IsEqualIID(riid, &IID_IUnknown))
1162         *ppv = (IUnknown *)sink;
1163     else if (SDL_IsEqualIID(riid, &IID_ITfInputProcessorProfileActivationSink))
1164         *ppv = (ITfInputProcessorProfileActivationSink *)sink;
1165 
1166     if (*ppv) {
1167         TSFSink_AddRef(sink);
1168         return S_OK;
1169     }
1170     return E_NOINTERFACE;
1171 }
1172 
IPPASink_OnActivated(TSFSink * sink,DWORD dwProfileType,LANGID langid,REFCLSID clsid,REFGUID catid,REFGUID guidProfile,HKL hkl,DWORD dwFlags)1173 STDMETHODIMP IPPASink_OnActivated(TSFSink *sink, DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags)
1174 {
1175     static const GUID TF_PROFILE_DAYI = { 0x037B2C25, 0x480C, 0x4D7F, { 0xB0, 0x27, 0xD6, 0xCA, 0x6B, 0x69, 0x78, 0x8A } };
1176     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1177     videodata->ime_candlistindexbase = SDL_IsEqualGUID(&TF_PROFILE_DAYI, guidProfile) ? 0 : 1;
1178     if (SDL_IsEqualIID(catid, &GUID_TFCAT_TIP_KEYBOARD) && (dwFlags & TF_IPSINK_FLAG_ACTIVE))
1179         IME_InputLangChanged((SDL_VideoData *)sink->data);
1180 
1181     IME_HideCandidateList(videodata);
1182     return S_OK;
1183 }
1184 
1185 static void *vtUIElementSink[] = {
1186     (void *)(UIElementSink_QueryInterface),
1187     (void *)(TSFSink_AddRef),
1188     (void *)(TSFSink_Release),
1189     (void *)(UIElementSink_BeginUIElement),
1190     (void *)(UIElementSink_UpdateUIElement),
1191     (void *)(UIElementSink_EndUIElement)
1192 };
1193 
1194 static void *vtIPPASink[] = {
1195     (void *)(IPPASink_QueryInterface),
1196     (void *)(TSFSink_AddRef),
1197     (void *)(TSFSink_Release),
1198     (void *)(IPPASink_OnActivated)
1199 };
1200 
1201 static void
UILess_EnableUIUpdates(SDL_VideoData * videodata)1202 UILess_EnableUIUpdates(SDL_VideoData *videodata)
1203 {
1204     ITfSource *source = 0;
1205     if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie != TF_INVALID_COOKIE)
1206         return;
1207 
1208     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1209         source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie);
1210         source->lpVtbl->Release(source);
1211     }
1212 }
1213 
1214 static void
UILess_DisableUIUpdates(SDL_VideoData * videodata)1215 UILess_DisableUIUpdates(SDL_VideoData *videodata)
1216 {
1217     ITfSource *source = 0;
1218     if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie == TF_INVALID_COOKIE)
1219         return;
1220 
1221     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1222         source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
1223         videodata->ime_uielemsinkcookie = TF_INVALID_COOKIE;
1224         source->lpVtbl->Release(source);
1225     }
1226 }
1227 
1228 static SDL_bool
UILess_SetupSinks(SDL_VideoData * videodata)1229 UILess_SetupSinks(SDL_VideoData *videodata)
1230 {
1231     TfClientId clientid = 0;
1232     SDL_bool result = SDL_FALSE;
1233     ITfSource *source = 0;
1234     if (FAILED(CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgrEx, (LPVOID *)&videodata->ime_threadmgrex)))
1235         return SDL_FALSE;
1236 
1237     if (FAILED(videodata->ime_threadmgrex->lpVtbl->ActivateEx(videodata->ime_threadmgrex, &clientid, TF_TMAE_UIELEMENTENABLEDONLY)))
1238         return SDL_FALSE;
1239 
1240     videodata->ime_uielemsink = SDL_malloc(sizeof(TSFSink));
1241     videodata->ime_ippasink = SDL_malloc(sizeof(TSFSink));
1242 
1243     videodata->ime_uielemsink->lpVtbl = vtUIElementSink;
1244     videodata->ime_uielemsink->refcount = 1;
1245     videodata->ime_uielemsink->data = videodata;
1246 
1247     videodata->ime_ippasink->lpVtbl = vtIPPASink;
1248     videodata->ime_ippasink->refcount = 1;
1249     videodata->ime_ippasink->data = videodata;
1250 
1251     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1252         if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie))) {
1253             if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfInputProcessorProfileActivationSink, (IUnknown *)videodata->ime_ippasink, &videodata->ime_alpnsinkcookie))) {
1254                 result = SDL_TRUE;
1255             }
1256         }
1257         source->lpVtbl->Release(source);
1258     }
1259     return result;
1260 }
1261 
1262 #define SAFE_RELEASE(p)                             \
1263 {                                                   \
1264     if (p) {                                        \
1265         (p)->lpVtbl->Release((p));                  \
1266         (p) = 0;                                    \
1267     }                                               \
1268 }
1269 
1270 static void
UILess_ReleaseSinks(SDL_VideoData * videodata)1271 UILess_ReleaseSinks(SDL_VideoData *videodata)
1272 {
1273     ITfSource *source = 0;
1274     if (videodata->ime_threadmgrex && SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1275         source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
1276         source->lpVtbl->UnadviseSink(source, videodata->ime_alpnsinkcookie);
1277         SAFE_RELEASE(source);
1278         videodata->ime_threadmgrex->lpVtbl->Deactivate(videodata->ime_threadmgrex);
1279         SAFE_RELEASE(videodata->ime_threadmgrex);
1280         TSFSink_Release(videodata->ime_uielemsink);
1281         videodata->ime_uielemsink = 0;
1282         TSFSink_Release(videodata->ime_ippasink);
1283         videodata->ime_ippasink = 0;
1284     }
1285 }
1286 
1287 static void *
StartDrawToBitmap(HDC hdc,HBITMAP * hhbm,int width,int height)1288 StartDrawToBitmap(HDC hdc, HBITMAP *hhbm, int width, int height)
1289 {
1290     BITMAPINFO info;
1291     BITMAPINFOHEADER *infoHeader = &info.bmiHeader;
1292     BYTE *bits = NULL;
1293     if (hhbm) {
1294         SDL_zero(info);
1295         infoHeader->biSize = sizeof(BITMAPINFOHEADER);
1296         infoHeader->biWidth = width;
1297         infoHeader->biHeight = -1 * SDL_abs(height);
1298         infoHeader->biPlanes = 1;
1299         infoHeader->biBitCount = 32;
1300         infoHeader->biCompression = BI_RGB;
1301         *hhbm = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void **)&bits, 0, 0);
1302         if (*hhbm)
1303             SelectObject(hdc, *hhbm);
1304     }
1305     return bits;
1306 }
1307 
1308 static void
StopDrawToBitmap(HDC hdc,HBITMAP * hhbm)1309 StopDrawToBitmap(HDC hdc, HBITMAP *hhbm)
1310 {
1311     if (hhbm && *hhbm) {
1312         DeleteObject(*hhbm);
1313         *hhbm = NULL;
1314     }
1315 }
1316 
1317 /* This draws only within the specified area and fills the entire region. */
1318 static void
DrawRect(HDC hdc,int left,int top,int right,int bottom,int pensize)1319 DrawRect(HDC hdc, int left, int top, int right, int bottom, int pensize)
1320 {
1321     /* The case of no pen (PenSize = 0) is automatically taken care of. */
1322     const int penadjust = (int)SDL_floor(pensize / 2.0f - 0.5f);
1323     left += pensize / 2;
1324     top += pensize / 2;
1325     right -= penadjust;
1326     bottom -= penadjust;
1327     Rectangle(hdc, left, top, right, bottom);
1328 }
1329 
1330 static void
IME_DestroyTextures(SDL_VideoData * videodata)1331 IME_DestroyTextures(SDL_VideoData *videodata)
1332 {
1333 }
1334 
1335 #define SDL_swap(a,b) { \
1336     int c = (a);        \
1337     (a) = (b);          \
1338     (b) = c;            \
1339     }
1340 
1341 static void
IME_PositionCandidateList(SDL_VideoData * videodata,SIZE size)1342 IME_PositionCandidateList(SDL_VideoData *videodata, SIZE size)
1343 {
1344     int left, top, right, bottom;
1345     SDL_bool ok = SDL_FALSE;
1346     int winw = videodata->ime_winwidth;
1347     int winh = videodata->ime_winheight;
1348 
1349     /* Bottom */
1350     left = videodata->ime_rect.x;
1351     top = videodata->ime_rect.y + videodata->ime_rect.h;
1352     right = left + size.cx;
1353     bottom = top + size.cy;
1354     if (right >= winw) {
1355         left -= right - winw;
1356         right = winw;
1357     }
1358     if (bottom < winh)
1359         ok = SDL_TRUE;
1360 
1361     /* Top */
1362     if (!ok) {
1363         left = videodata->ime_rect.x;
1364         top = videodata->ime_rect.y - size.cy;
1365         right = left + size.cx;
1366         bottom = videodata->ime_rect.y;
1367         if (right >= winw) {
1368             left -= right - winw;
1369             right = winw;
1370         }
1371         if (top >= 0)
1372             ok = SDL_TRUE;
1373     }
1374 
1375     /* Right */
1376     if (!ok) {
1377         left = videodata->ime_rect.x + size.cx;
1378         top = 0;
1379         right = left + size.cx;
1380         bottom = size.cy;
1381         if (right < winw)
1382             ok = SDL_TRUE;
1383     }
1384 
1385     /* Left */
1386     if (!ok) {
1387         left = videodata->ime_rect.x - size.cx;
1388         top = 0;
1389         right = videodata->ime_rect.x;
1390         bottom = size.cy;
1391         if (right >= 0)
1392             ok = SDL_TRUE;
1393     }
1394 
1395     /* Window too small, show at (0,0) */
1396     if (!ok) {
1397         left = 0;
1398         top = 0;
1399         right = size.cx;
1400         bottom = size.cy;
1401     }
1402 
1403     videodata->ime_candlistrect.x = left;
1404     videodata->ime_candlistrect.y = top;
1405     videodata->ime_candlistrect.w = right - left;
1406     videodata->ime_candlistrect.h = bottom - top;
1407 }
1408 
1409 static void
IME_RenderCandidateList(SDL_VideoData * videodata,HDC hdc)1410 IME_RenderCandidateList(SDL_VideoData *videodata, HDC hdc)
1411 {
1412     int i, j;
1413     SIZE size = {0};
1414     SIZE candsizes[MAX_CANDLIST];
1415     SIZE maxcandsize = {0};
1416     HBITMAP hbm = NULL;
1417     const int candcount = SDL_min(SDL_min(MAX_CANDLIST, videodata->ime_candcount), videodata->ime_candpgsize);
1418     SDL_bool vertical = videodata->ime_candvertical;
1419 
1420     const int listborder = 1;
1421     const int listpadding = 0;
1422     const int listbordercolor = RGB(0xB4, 0xC7, 0xAA);
1423     const int listfillcolor = RGB(255, 255, 255);
1424 
1425     const int candborder = 1;
1426     const int candpadding = 0;
1427     const int candmargin = 1;
1428     const COLORREF candbordercolor = RGB(255, 255, 255);
1429     const COLORREF candfillcolor = RGB(255, 255, 255);
1430     const COLORREF candtextcolor = RGB(0, 0, 0);
1431     const COLORREF selbordercolor = RGB(0x84, 0xAC, 0xDD);
1432     const COLORREF selfillcolor = RGB(0xD2, 0xE6, 0xFF);
1433     const COLORREF seltextcolor = RGB(0, 0, 0);
1434     const int horzcandspacing = 5;
1435 
1436     HPEN listpen = listborder != 0 ? CreatePen(PS_SOLID, listborder, listbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1437     HBRUSH listbrush = CreateSolidBrush(listfillcolor);
1438     HPEN candpen = candborder != 0 ? CreatePen(PS_SOLID, candborder, candbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1439     HBRUSH candbrush = CreateSolidBrush(candfillcolor);
1440     HPEN selpen = candborder != 0 ? CreatePen(PS_DOT, candborder, selbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1441     HBRUSH selbrush = CreateSolidBrush(selfillcolor);
1442     HFONT font = CreateFont((int)(1 + videodata->ime_rect.h * 0.75f), 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH | FF_SWISS, TEXT("Microsoft Sans Serif"));
1443 
1444     SetBkMode(hdc, TRANSPARENT);
1445     SelectObject(hdc, font);
1446 
1447     for (i = 0; i < candcount; ++i) {
1448         const WCHAR *s = videodata->ime_candidates[i];
1449         if (!*s)
1450             break;
1451 
1452         GetTextExtentPoint32W(hdc, s, (int)SDL_wcslen(s), &candsizes[i]);
1453         maxcandsize.cx = SDL_max(maxcandsize.cx, candsizes[i].cx);
1454         maxcandsize.cy = SDL_max(maxcandsize.cy, candsizes[i].cy);
1455 
1456     }
1457     if (vertical) {
1458         size.cx =
1459             (listborder * 2) +
1460             (listpadding * 2) +
1461             (candmargin * 2) +
1462             (candborder * 2) +
1463             (candpadding * 2) +
1464             (maxcandsize.cx)
1465             ;
1466         size.cy =
1467             (listborder * 2) +
1468             (listpadding * 2) +
1469             ((candcount + 1) * candmargin) +
1470             (candcount * candborder * 2) +
1471             (candcount * candpadding * 2) +
1472             (candcount * maxcandsize.cy)
1473             ;
1474     }
1475     else {
1476         size.cx =
1477             (listborder * 2) +
1478             (listpadding * 2) +
1479             ((candcount + 1) * candmargin) +
1480             (candcount * candborder * 2) +
1481             (candcount * candpadding * 2) +
1482             ((candcount - 1) * horzcandspacing);
1483         ;
1484 
1485         for (i = 0; i < candcount; ++i)
1486             size.cx += candsizes[i].cx;
1487 
1488         size.cy =
1489             (listborder * 2) +
1490             (listpadding * 2) +
1491             (candmargin * 2) +
1492             (candborder * 2) +
1493             (candpadding * 2) +
1494             (maxcandsize.cy)
1495             ;
1496     }
1497 
1498     StartDrawToBitmap(hdc, &hbm, size.cx, size.cy);
1499 
1500     SelectObject(hdc, listpen);
1501     SelectObject(hdc, listbrush);
1502     DrawRect(hdc, 0, 0, size.cx, size.cy, listborder);
1503 
1504     SelectObject(hdc, candpen);
1505     SelectObject(hdc, candbrush);
1506     SetTextColor(hdc, candtextcolor);
1507     SetBkMode(hdc, TRANSPARENT);
1508 
1509     for (i = 0; i < candcount; ++i) {
1510         const WCHAR *s = videodata->ime_candidates[i];
1511         int left, top, right, bottom;
1512         if (!*s)
1513             break;
1514 
1515         if (vertical) {
1516             left = listborder + listpadding + candmargin;
1517             top = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * maxcandsize.cy);
1518             right = size.cx - listborder - listpadding - candmargin;
1519             bottom = top + maxcandsize.cy + (candpadding * 2) + (candborder * 2);
1520         }
1521         else {
1522             left = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * horzcandspacing);
1523 
1524             for (j = 0; j < i; ++j)
1525                 left += candsizes[j].cx;
1526 
1527             top = listborder + listpadding + candmargin;
1528             right = left + candsizes[i].cx + (candpadding * 2) + (candborder * 2);
1529             bottom = size.cy - listborder - listpadding - candmargin;
1530         }
1531 
1532         if (i == videodata->ime_candsel) {
1533             SelectObject(hdc, selpen);
1534             SelectObject(hdc, selbrush);
1535             SetTextColor(hdc, seltextcolor);
1536         }
1537         else {
1538             SelectObject(hdc, candpen);
1539             SelectObject(hdc, candbrush);
1540             SetTextColor(hdc, candtextcolor);
1541         }
1542 
1543         DrawRect(hdc, left, top, right, bottom, candborder);
1544         ExtTextOutW(hdc, left + candborder + candpadding, top + candborder + candpadding, 0, NULL, s, (int)SDL_wcslen(s), NULL);
1545     }
1546     StopDrawToBitmap(hdc, &hbm);
1547 
1548     DeleteObject(listpen);
1549     DeleteObject(listbrush);
1550     DeleteObject(candpen);
1551     DeleteObject(candbrush);
1552     DeleteObject(selpen);
1553     DeleteObject(selbrush);
1554     DeleteObject(font);
1555 
1556     IME_PositionCandidateList(videodata, size);
1557 }
1558 
1559 static void
IME_Render(SDL_VideoData * videodata)1560 IME_Render(SDL_VideoData *videodata)
1561 {
1562     HDC hdc = CreateCompatibleDC(NULL);
1563 
1564     if (videodata->ime_candlist)
1565         IME_RenderCandidateList(videodata, hdc);
1566 
1567     DeleteDC(hdc);
1568 
1569     videodata->ime_dirty = SDL_FALSE;
1570 }
1571 
IME_Present(SDL_VideoData * videodata)1572 void IME_Present(SDL_VideoData *videodata)
1573 {
1574     if (videodata->ime_dirty)
1575         IME_Render(videodata);
1576 
1577     /* FIXME: Need to show the IME bitmap */
1578 }
1579 
1580 #endif /* SDL_DISABLE_WINDOWS_IME */
1581 
1582 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
1583 
1584 /* vi: set ts=4 sw=4 expandtab: */
1585