1 /*
2   Simple DirectMedia Layer
3   Copyright (C) 1997-2018 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)(UINT_PTR)0xE0060404)
299 #define CHT_HKL_NEW_PHONETIC    ((HKL)(UINT_PTR)0xE0080404)
300 #define CHT_HKL_NEW_CHANG_JIE   ((HKL)(UINT_PTR)0xE0090404)
301 #define CHT_HKL_NEW_QUICK       ((HKL)(UINT_PTR)0xE00A0404)
302 #define CHT_HKL_HK_CANTONESE    ((HKL)(UINT_PTR)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)(UINT_PTR)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 static SDL_bool UILess_SetupSinks(SDL_VideoData *videodata);
335 static void UILess_ReleaseSinks(SDL_VideoData *videodata);
336 static void UILess_EnableUIUpdates(SDL_VideoData *videodata);
337 static void UILess_DisableUIUpdates(SDL_VideoData *videodata);
338 
339 static void
IME_Init(SDL_VideoData * videodata,HWND hwnd)340 IME_Init(SDL_VideoData *videodata, HWND hwnd)
341 {
342     if (videodata->ime_initialized)
343         return;
344 
345     videodata->ime_hwnd_main = hwnd;
346     if (SUCCEEDED(WIN_CoInitialize())) {
347         videodata->ime_com_initialized = SDL_TRUE;
348         CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (LPVOID *)&videodata->ime_threadmgr);
349     }
350     videodata->ime_initialized = SDL_TRUE;
351     videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
352     if (!videodata->ime_himm32) {
353         videodata->ime_available = SDL_FALSE;
354         SDL_ClearError();
355         return;
356     }
357     videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC");
358     videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC");
359     videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC");
360     videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC");
361 
362     IME_SetWindow(videodata, hwnd);
363     videodata->ime_himc = ImmGetContext(hwnd);
364     ImmReleaseContext(hwnd, videodata->ime_himc);
365     if (!videodata->ime_himc) {
366         videodata->ime_available = SDL_FALSE;
367         IME_Disable(videodata, hwnd);
368         return;
369     }
370     videodata->ime_available = SDL_TRUE;
371     IME_UpdateInputLocale(videodata);
372     IME_SetupAPI(videodata);
373     videodata->ime_uiless = UILess_SetupSinks(videodata);
374     IME_UpdateInputLocale(videodata);
375     IME_Disable(videodata, hwnd);
376 }
377 
378 static void
IME_Enable(SDL_VideoData * videodata,HWND hwnd)379 IME_Enable(SDL_VideoData *videodata, HWND hwnd)
380 {
381     if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
382         return;
383 
384     if (!videodata->ime_available) {
385         IME_Disable(videodata, hwnd);
386         return;
387     }
388     if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
389         ImmAssociateContext(videodata->ime_hwnd_current, videodata->ime_himc);
390 
391     videodata->ime_enabled = SDL_TRUE;
392     IME_UpdateInputLocale(videodata);
393     UILess_EnableUIUpdates(videodata);
394 }
395 
396 static void
IME_Disable(SDL_VideoData * videodata,HWND hwnd)397 IME_Disable(SDL_VideoData *videodata, HWND hwnd)
398 {
399     if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
400         return;
401 
402     IME_ClearComposition(videodata);
403     if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
404         ImmAssociateContext(videodata->ime_hwnd_current, (HIMC)0);
405 
406     videodata->ime_enabled = SDL_FALSE;
407     UILess_DisableUIUpdates(videodata);
408 }
409 
410 static void
IME_Quit(SDL_VideoData * videodata)411 IME_Quit(SDL_VideoData *videodata)
412 {
413     if (!videodata->ime_initialized)
414         return;
415 
416     UILess_ReleaseSinks(videodata);
417     if (videodata->ime_hwnd_main)
418         ImmAssociateContext(videodata->ime_hwnd_main, videodata->ime_himc);
419 
420     videodata->ime_hwnd_main = 0;
421     videodata->ime_himc = 0;
422     if (videodata->ime_himm32) {
423         SDL_UnloadObject(videodata->ime_himm32);
424         videodata->ime_himm32 = 0;
425     }
426     if (videodata->ime_threadmgr) {
427         videodata->ime_threadmgr->lpVtbl->Release(videodata->ime_threadmgr);
428         videodata->ime_threadmgr = 0;
429     }
430     if (videodata->ime_com_initialized) {
431         WIN_CoUninitialize();
432         videodata->ime_com_initialized = SDL_FALSE;
433     }
434     IME_DestroyTextures(videodata);
435     videodata->ime_initialized = SDL_FALSE;
436 }
437 
438 static void
IME_GetReadingString(SDL_VideoData * videodata,HWND hwnd)439 IME_GetReadingString(SDL_VideoData *videodata, HWND hwnd)
440 {
441     DWORD id = 0;
442     HIMC himc = 0;
443     WCHAR buffer[16];
444     WCHAR *s = buffer;
445     DWORD len = 0;
446     INT err = 0;
447     BOOL vertical = FALSE;
448     UINT maxuilen = 0;
449 
450     if (videodata->ime_uiless)
451         return;
452 
453     videodata->ime_readingstring[0] = 0;
454 
455     id = IME_GetId(videodata, 0);
456     if (!id)
457         return;
458 
459     himc = ImmGetContext(hwnd);
460     if (!himc)
461         return;
462 
463     if (videodata->GetReadingString) {
464         len = videodata->GetReadingString(himc, 0, 0, &err, &vertical, &maxuilen);
465         if (len) {
466             if (len > SDL_arraysize(buffer))
467                 len = SDL_arraysize(buffer);
468 
469             len = videodata->GetReadingString(himc, len, s, &err, &vertical, &maxuilen);
470         }
471         SDL_wcslcpy(videodata->ime_readingstring, s, len);
472     }
473     else {
474         LPINPUTCONTEXT2 lpimc = videodata->ImmLockIMC(himc);
475         LPBYTE p = 0;
476         s = 0;
477         switch (id)
478         {
479         case IMEID_CHT_VER42:
480         case IMEID_CHT_VER43:
481         case IMEID_CHT_VER44:
482             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 24);
483             if (!p)
484                 break;
485 
486             len = *(DWORD *)(p + 7*4 + 32*4);
487             s = (WCHAR *)(p + 56);
488             break;
489         case IMEID_CHT_VER51:
490         case IMEID_CHT_VER52:
491         case IMEID_CHS_VER53:
492             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 4);
493             if (!p)
494                 break;
495 
496             p = *(LPBYTE *)((LPBYTE)p + 1*4 + 5*4);
497             if (!p)
498                 break;
499 
500             len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
501             s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
502             break;
503         case IMEID_CHS_VER41:
504             {
505                 int offset = (IME_GetId(videodata, 1) >= 0x00000002) ? 8 : 7;
506                 p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + offset * 4);
507                 if (!p)
508                     break;
509 
510                 len = *(DWORD *)(p + 7*4 + 16*2*4);
511                 s = (WCHAR *)(p + 6*4 + 16*2*1);
512             }
513             break;
514         case IMEID_CHS_VER42:
515             p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 1*4 + 1*4 + 6*4);
516             if (!p)
517                 break;
518 
519             len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
520             s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
521             break;
522         }
523         if (s) {
524             size_t size = SDL_min((size_t)(len + 1), SDL_arraysize(videodata->ime_readingstring));
525             SDL_wcslcpy(videodata->ime_readingstring, s, size);
526         }
527 
528         videodata->ImmUnlockIMCC(lpimc->hPrivate);
529         videodata->ImmUnlockIMC(himc);
530     }
531     ImmReleaseContext(hwnd, himc);
532     IME_SendEditingEvent(videodata);
533 }
534 
535 static void
IME_InputLangChanged(SDL_VideoData * videodata)536 IME_InputLangChanged(SDL_VideoData *videodata)
537 {
538     UINT lang = PRIMLANG();
539     IME_UpdateInputLocale(videodata);
540     if (!videodata->ime_uiless)
541         videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1;
542 
543     IME_SetupAPI(videodata);
544     if (lang != PRIMLANG()) {
545         IME_ClearComposition(videodata);
546     }
547 }
548 
549 static DWORD
IME_GetId(SDL_VideoData * videodata,UINT uIndex)550 IME_GetId(SDL_VideoData *videodata, UINT uIndex)
551 {
552     static HKL hklprev = 0;
553     static DWORD dwRet[2] = {0};
554     DWORD dwVerSize = 0;
555     DWORD dwVerHandle = 0;
556     LPVOID lpVerBuffer = 0;
557     LPVOID lpVerData = 0;
558     UINT cbVerData = 0;
559     char szTemp[256];
560     HKL hkl = 0;
561     DWORD dwLang = 0;
562     if (uIndex >= sizeof(dwRet) / sizeof(dwRet[0]))
563         return 0;
564 
565     hkl = videodata->ime_hkl;
566     if (hklprev == hkl)
567         return dwRet[uIndex];
568 
569     hklprev = hkl;
570     dwLang = ((DWORD_PTR)hkl & 0xffff);
571     if (videodata->ime_uiless && LANG() == LANG_CHT) {
572         dwRet[0] = IMEID_CHT_VER_VISTA;
573         dwRet[1] = 0;
574         return dwRet[0];
575     }
576     if (hkl != CHT_HKL_NEW_PHONETIC
577         && hkl != CHT_HKL_NEW_CHANG_JIE
578         && hkl != CHT_HKL_NEW_QUICK
579         && hkl != CHT_HKL_HK_CANTONESE
580         && hkl != CHS_HKL) {
581         dwRet[0] = dwRet[1] = 0;
582         return dwRet[uIndex];
583     }
584     if (ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1) <= 0) {
585         dwRet[0] = dwRet[1] = 0;
586         return dwRet[uIndex];
587     }
588     if (!videodata->GetReadingString) {
589         #define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
590         if (CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME1, -1) != 2
591             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME2, -1) != 2
592             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME3, -1) != 2
593             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME1, -1) != 2
594             && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME2, -1) != 2) {
595             dwRet[0] = dwRet[1] = 0;
596             return dwRet[uIndex];
597         }
598         #undef LCID_INVARIANT
599         dwVerSize = GetFileVersionInfoSizeA(szTemp, &dwVerHandle);
600         if (dwVerSize) {
601             lpVerBuffer = SDL_malloc(dwVerSize);
602             if (lpVerBuffer) {
603                 if (GetFileVersionInfoA(szTemp, dwVerHandle, dwVerSize, lpVerBuffer)) {
604                     if (VerQueryValueA(lpVerBuffer, "\\", &lpVerData, &cbVerData)) {
605                         #define pVerFixedInfo   ((VS_FIXEDFILEINFO FAR*)lpVerData)
606                         DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
607                         dwVer = (dwVer & 0x00ff0000) << 8 | (dwVer & 0x000000ff) << 16;
608                         if ((videodata->GetReadingString) ||
609                             ((dwLang == LANG_CHT) && (
610                             dwVer == MAKEIMEVERSION(4, 2) ||
611                             dwVer == MAKEIMEVERSION(4, 3) ||
612                             dwVer == MAKEIMEVERSION(4, 4) ||
613                             dwVer == MAKEIMEVERSION(5, 0) ||
614                             dwVer == MAKEIMEVERSION(5, 1) ||
615                             dwVer == MAKEIMEVERSION(5, 2) ||
616                             dwVer == MAKEIMEVERSION(6, 0)))
617                             ||
618                             ((dwLang == LANG_CHS) && (
619                             dwVer == MAKEIMEVERSION(4, 1) ||
620                             dwVer == MAKEIMEVERSION(4, 2) ||
621                             dwVer == MAKEIMEVERSION(5, 3)))) {
622                             dwRet[0] = dwVer | dwLang;
623                             dwRet[1] = pVerFixedInfo->dwFileVersionLS;
624                             SDL_free(lpVerBuffer);
625                             return dwRet[0];
626                         }
627                         #undef pVerFixedInfo
628                     }
629                 }
630             }
631             SDL_free(lpVerBuffer);
632         }
633     }
634     dwRet[0] = dwRet[1] = 0;
635     return dwRet[uIndex];
636 }
637 
638 static void
IME_SetupAPI(SDL_VideoData * videodata)639 IME_SetupAPI(SDL_VideoData *videodata)
640 {
641     char ime_file[MAX_PATH + 1];
642     void* hime = 0;
643     HKL hkl = 0;
644     videodata->GetReadingString = 0;
645     videodata->ShowReadingWindow = 0;
646     if (videodata->ime_uiless)
647         return;
648 
649     hkl = videodata->ime_hkl;
650     if (ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1) <= 0)
651         return;
652 
653     hime = SDL_LoadObject(ime_file);
654     if (!hime)
655         return;
656 
657     videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT))
658         SDL_LoadFunction(hime, "GetReadingString");
659     videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL))
660         SDL_LoadFunction(hime, "ShowReadingWindow");
661 
662     if (videodata->ShowReadingWindow) {
663         HIMC himc = ImmGetContext(videodata->ime_hwnd_current);
664         if (himc) {
665             videodata->ShowReadingWindow(himc, FALSE);
666             ImmReleaseContext(videodata->ime_hwnd_current, himc);
667         }
668     }
669 }
670 
671 static void
IME_SetWindow(SDL_VideoData * videodata,HWND hwnd)672 IME_SetWindow(SDL_VideoData* videodata, HWND hwnd)
673 {
674     videodata->ime_hwnd_current = hwnd;
675     if (videodata->ime_threadmgr) {
676         struct ITfDocumentMgr *document_mgr = 0;
677         if (SUCCEEDED(videodata->ime_threadmgr->lpVtbl->AssociateFocus(videodata->ime_threadmgr, hwnd, NULL, &document_mgr))) {
678             if (document_mgr)
679                 document_mgr->lpVtbl->Release(document_mgr);
680         }
681     }
682 }
683 
684 static void
IME_UpdateInputLocale(SDL_VideoData * videodata)685 IME_UpdateInputLocale(SDL_VideoData *videodata)
686 {
687     static HKL hklprev = 0;
688     videodata->ime_hkl = GetKeyboardLayout(0);
689     if (hklprev == videodata->ime_hkl)
690         return;
691 
692     hklprev = videodata->ime_hkl;
693     switch (PRIMLANG()) {
694     case LANG_CHINESE:
695         videodata->ime_candvertical = SDL_TRUE;
696         if (SUBLANG() == SUBLANG_CHINESE_SIMPLIFIED)
697             videodata->ime_candvertical = SDL_FALSE;
698 
699         break;
700     case LANG_JAPANESE:
701         videodata->ime_candvertical = SDL_TRUE;
702         break;
703     case LANG_KOREAN:
704         videodata->ime_candvertical = SDL_FALSE;
705         break;
706     }
707 }
708 
709 static void
IME_ClearComposition(SDL_VideoData * videodata)710 IME_ClearComposition(SDL_VideoData *videodata)
711 {
712     HIMC himc = 0;
713     if (!videodata->ime_initialized)
714         return;
715 
716     himc = ImmGetContext(videodata->ime_hwnd_current);
717     if (!himc)
718         return;
719 
720     ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
721     if (videodata->ime_uiless)
722         ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
723 
724     ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0);
725     ImmReleaseContext(videodata->ime_hwnd_current, himc);
726     SDL_SendEditingText("", 0, 0);
727 }
728 
729 static void
IME_GetCompositionString(SDL_VideoData * videodata,HIMC himc,DWORD string)730 IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
731 {
732     LONG length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, sizeof(videodata->ime_composition) - sizeof(videodata->ime_composition[0]));
733     if (length < 0)
734         length = 0;
735 
736     length /= sizeof(videodata->ime_composition[0]);
737     videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
738     if (videodata->ime_cursor < SDL_arraysize(videodata->ime_composition) && videodata->ime_composition[videodata->ime_cursor] == 0x3000) {
739         int i;
740         for (i = videodata->ime_cursor + 1; i < length; ++i)
741             videodata->ime_composition[i - 1] = videodata->ime_composition[i];
742 
743         --length;
744     }
745     videodata->ime_composition[length] = 0;
746 }
747 
748 static void
IME_SendInputEvent(SDL_VideoData * videodata)749 IME_SendInputEvent(SDL_VideoData *videodata)
750 {
751     char *s = 0;
752     s = WIN_StringToUTF8(videodata->ime_composition);
753     SDL_SendKeyboardText(s);
754     SDL_free(s);
755 
756     videodata->ime_composition[0] = 0;
757     videodata->ime_readingstring[0] = 0;
758     videodata->ime_cursor = 0;
759 }
760 
761 static void
IME_SendEditingEvent(SDL_VideoData * videodata)762 IME_SendEditingEvent(SDL_VideoData *videodata)
763 {
764     char *s = 0;
765     WCHAR buffer[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
766     const size_t size = SDL_arraysize(buffer);
767     buffer[0] = 0;
768     if (videodata->ime_readingstring[0]) {
769         size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);
770         SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
771         SDL_wcslcat(buffer, videodata->ime_readingstring, size);
772         SDL_wcslcat(buffer, &videodata->ime_composition[len], size);
773     }
774     else {
775         SDL_wcslcpy(buffer, videodata->ime_composition, size);
776     }
777     s = WIN_StringToUTF8(buffer);
778     SDL_SendEditingText(s, videodata->ime_cursor + (int)SDL_wcslen(videodata->ime_readingstring), 0);
779     SDL_free(s);
780 }
781 
782 static void
IME_AddCandidate(SDL_VideoData * videodata,UINT i,LPCWSTR candidate)783 IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate)
784 {
785     LPWSTR dst = videodata->ime_candidates[i];
786     *dst++ = (WCHAR)(TEXT('0') + ((i + videodata->ime_candlistindexbase) % 10));
787     if (videodata->ime_candvertical)
788         *dst++ = TEXT(' ');
789 
790     while (*candidate && (SDL_arraysize(videodata->ime_candidates[i]) > (dst - videodata->ime_candidates[i])))
791         *dst++ = *candidate++;
792 
793     *dst = (WCHAR)'\0';
794 }
795 
796 static void
IME_GetCandidateList(HIMC himc,SDL_VideoData * videodata)797 IME_GetCandidateList(HIMC himc, SDL_VideoData *videodata)
798 {
799     LPCANDIDATELIST cand_list = 0;
800     DWORD size = ImmGetCandidateListW(himc, 0, 0, 0);
801     if (size) {
802         cand_list = (LPCANDIDATELIST)SDL_malloc(size);
803         if (cand_list) {
804             size = ImmGetCandidateListW(himc, 0, cand_list, size);
805             if (size) {
806                 UINT i, j;
807                 UINT page_start = 0;
808                 videodata->ime_candsel = cand_list->dwSelection;
809                 videodata->ime_candcount = cand_list->dwCount;
810 
811                 if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) {
812                     const UINT maxcandchar = 18;
813                     size_t cchars = 0;
814 
815                     for (i = 0; i < videodata->ime_candcount; ++i) {
816                         size_t len = SDL_wcslen((LPWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i])) + 1;
817                         if (len + cchars > maxcandchar) {
818                             if (i > cand_list->dwSelection)
819                                 break;
820 
821                             page_start = i;
822                             cchars = len;
823                         }
824                         else {
825                             cchars += len;
826                         }
827                     }
828                     videodata->ime_candpgsize = i - page_start;
829                 } else {
830                     videodata->ime_candpgsize = SDL_min(cand_list->dwPageSize, MAX_CANDLIST);
831                     if (videodata->ime_candpgsize > 0) {
832                         page_start = (cand_list->dwSelection / videodata->ime_candpgsize) * videodata->ime_candpgsize;
833                     } else {
834                         page_start = 0;
835                     }
836                 }
837                 SDL_memset(&videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
838                 for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < (int)videodata->ime_candpgsize; i++, j++) {
839                     LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]);
840                     IME_AddCandidate(videodata, j, candidate);
841                 }
842                 if (PRIMLANG() == LANG_KOREAN || (PRIMLANG() == LANG_CHT && !IME_GetId(videodata, 0)))
843                     videodata->ime_candsel = -1;
844 
845             }
846             SDL_free(cand_list);
847         }
848     }
849 }
850 
851 static void
IME_ShowCandidateList(SDL_VideoData * videodata)852 IME_ShowCandidateList(SDL_VideoData *videodata)
853 {
854     videodata->ime_dirty = SDL_TRUE;
855     videodata->ime_candlist = SDL_TRUE;
856     IME_DestroyTextures(videodata);
857     IME_SendEditingEvent(videodata);
858 }
859 
860 static void
IME_HideCandidateList(SDL_VideoData * videodata)861 IME_HideCandidateList(SDL_VideoData *videodata)
862 {
863     videodata->ime_dirty = SDL_FALSE;
864     videodata->ime_candlist = SDL_FALSE;
865     IME_DestroyTextures(videodata);
866     IME_SendEditingEvent(videodata);
867 }
868 
869 SDL_bool
IME_HandleMessage(HWND hwnd,UINT msg,WPARAM wParam,LPARAM * lParam,SDL_VideoData * videodata)870 IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
871 {
872     SDL_bool trap = SDL_FALSE;
873     HIMC himc = 0;
874     if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled)
875         return SDL_FALSE;
876 
877     switch (msg) {
878     case WM_INPUTLANGCHANGE:
879         IME_InputLangChanged(videodata);
880         break;
881     case WM_IME_SETCONTEXT:
882         *lParam = 0;
883         break;
884     case WM_IME_STARTCOMPOSITION:
885         trap = SDL_TRUE;
886         break;
887     case WM_IME_COMPOSITION:
888         trap = SDL_TRUE;
889         himc = ImmGetContext(hwnd);
890         if (*lParam & GCS_RESULTSTR) {
891             IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
892             IME_SendInputEvent(videodata);
893         }
894         if (*lParam & GCS_COMPSTR) {
895             if (!videodata->ime_uiless)
896                 videodata->ime_readingstring[0] = 0;
897 
898             IME_GetCompositionString(videodata, himc, GCS_COMPSTR);
899             IME_SendEditingEvent(videodata);
900         }
901         ImmReleaseContext(hwnd, himc);
902         break;
903     case WM_IME_ENDCOMPOSITION:
904         videodata->ime_composition[0] = 0;
905         videodata->ime_readingstring[0] = 0;
906         videodata->ime_cursor = 0;
907         SDL_SendEditingText("", 0, 0);
908         break;
909     case WM_IME_NOTIFY:
910         switch (wParam) {
911         case IMN_SETCONVERSIONMODE:
912         case IMN_SETOPENSTATUS:
913             IME_UpdateInputLocale(videodata);
914             break;
915         case IMN_OPENCANDIDATE:
916         case IMN_CHANGECANDIDATE:
917             if (videodata->ime_uiless)
918                 break;
919 
920             trap = SDL_TRUE;
921             IME_ShowCandidateList(videodata);
922             himc = ImmGetContext(hwnd);
923             if (!himc)
924                 break;
925 
926             IME_GetCandidateList(himc, videodata);
927             ImmReleaseContext(hwnd, himc);
928             break;
929         case IMN_CLOSECANDIDATE:
930             trap = SDL_TRUE;
931             IME_HideCandidateList(videodata);
932             break;
933         case IMN_PRIVATE:
934             {
935                 DWORD dwId = IME_GetId(videodata, 0);
936                 IME_GetReadingString(videodata, hwnd);
937                 switch (dwId)
938                 {
939                 case IMEID_CHT_VER42:
940                 case IMEID_CHT_VER43:
941                 case IMEID_CHT_VER44:
942                 case IMEID_CHS_VER41:
943                 case IMEID_CHS_VER42:
944                     if (*lParam == 1 || *lParam == 2)
945                         trap = SDL_TRUE;
946 
947                     break;
948                 case IMEID_CHT_VER50:
949                 case IMEID_CHT_VER51:
950                 case IMEID_CHT_VER52:
951                 case IMEID_CHT_VER60:
952                 case IMEID_CHS_VER53:
953                     if (*lParam == 16
954                         || *lParam == 17
955                         || *lParam == 26
956                         || *lParam == 27
957                         || *lParam == 28)
958                         trap = SDL_TRUE;
959                     break;
960                 }
961             }
962             break;
963         default:
964             trap = SDL_TRUE;
965             break;
966         }
967         break;
968     }
969     return trap;
970 }
971 
972 static void
IME_CloseCandidateList(SDL_VideoData * videodata)973 IME_CloseCandidateList(SDL_VideoData *videodata)
974 {
975     IME_HideCandidateList(videodata);
976     videodata->ime_candcount = 0;
977     SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
978 }
979 
980 static void
UILess_GetCandidateList(SDL_VideoData * videodata,ITfCandidateListUIElement * pcandlist)981 UILess_GetCandidateList(SDL_VideoData *videodata, ITfCandidateListUIElement *pcandlist)
982 {
983     UINT selection = 0;
984     UINT count = 0;
985     UINT page = 0;
986     UINT pgcount = 0;
987     DWORD pgstart = 0;
988     DWORD pgsize = 0;
989     UINT i, j;
990     pcandlist->lpVtbl->GetSelection(pcandlist, &selection);
991     pcandlist->lpVtbl->GetCount(pcandlist, &count);
992     pcandlist->lpVtbl->GetCurrentPage(pcandlist, &page);
993 
994     videodata->ime_candsel = selection;
995     videodata->ime_candcount = count;
996     IME_ShowCandidateList(videodata);
997 
998     pcandlist->lpVtbl->GetPageIndex(pcandlist, 0, 0, &pgcount);
999     if (pgcount > 0) {
1000         UINT *idxlist = SDL_malloc(sizeof(UINT) * pgcount);
1001         if (idxlist) {
1002             pcandlist->lpVtbl->GetPageIndex(pcandlist, idxlist, pgcount, &pgcount);
1003             pgstart = idxlist[page];
1004             if (page < pgcount - 1)
1005                 pgsize = SDL_min(count, idxlist[page + 1]) - pgstart;
1006             else
1007                 pgsize = count - pgstart;
1008 
1009             SDL_free(idxlist);
1010         }
1011     }
1012     videodata->ime_candpgsize = SDL_min(pgsize, MAX_CANDLIST);
1013     videodata->ime_candsel = videodata->ime_candsel - pgstart;
1014 
1015     SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
1016     for (i = pgstart, j = 0; (DWORD)i < count && j < videodata->ime_candpgsize; i++, j++) {
1017         BSTR bstr;
1018         if (SUCCEEDED(pcandlist->lpVtbl->GetString(pcandlist, i, &bstr))) {
1019             if (bstr) {
1020                 IME_AddCandidate(videodata, j, bstr);
1021                 SysFreeString(bstr);
1022             }
1023         }
1024     }
1025     if (PRIMLANG() == LANG_KOREAN)
1026         videodata->ime_candsel = -1;
1027 }
1028 
TSFSink_AddRef(TSFSink * sink)1029 STDMETHODIMP_(ULONG) TSFSink_AddRef(TSFSink *sink)
1030 {
1031     return ++sink->refcount;
1032 }
1033 
TSFSink_Release(TSFSink * sink)1034 STDMETHODIMP_(ULONG) TSFSink_Release(TSFSink *sink)
1035 {
1036     --sink->refcount;
1037     if (sink->refcount == 0) {
1038         SDL_free(sink);
1039         return 0;
1040     }
1041     return sink->refcount;
1042 }
1043 
UIElementSink_QueryInterface(TSFSink * sink,REFIID riid,PVOID * ppv)1044 STDMETHODIMP UIElementSink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
1045 {
1046     if (!ppv)
1047         return E_INVALIDARG;
1048 
1049     *ppv = 0;
1050     if (WIN_IsEqualIID(riid, &IID_IUnknown))
1051         *ppv = (IUnknown *)sink;
1052     else if (WIN_IsEqualIID(riid, &IID_ITfUIElementSink))
1053         *ppv = (ITfUIElementSink *)sink;
1054 
1055     if (*ppv) {
1056         TSFSink_AddRef(sink);
1057         return S_OK;
1058     }
1059     return E_NOINTERFACE;
1060 }
1061 
UILess_GetUIElement(SDL_VideoData * videodata,DWORD dwUIElementId)1062 ITfUIElement *UILess_GetUIElement(SDL_VideoData *videodata, DWORD dwUIElementId)
1063 {
1064     ITfUIElementMgr *puiem = 0;
1065     ITfUIElement *pelem = 0;
1066     ITfThreadMgrEx *threadmgrex = videodata->ime_threadmgrex;
1067 
1068     if (SUCCEEDED(threadmgrex->lpVtbl->QueryInterface(threadmgrex, &IID_ITfUIElementMgr, (LPVOID *)&puiem))) {
1069         puiem->lpVtbl->GetUIElement(puiem, dwUIElementId, &pelem);
1070         puiem->lpVtbl->Release(puiem);
1071     }
1072     return pelem;
1073 }
1074 
UIElementSink_BeginUIElement(TSFSink * sink,DWORD dwUIElementId,BOOL * pbShow)1075 STDMETHODIMP UIElementSink_BeginUIElement(TSFSink *sink, DWORD dwUIElementId, BOOL *pbShow)
1076 {
1077     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1078     ITfReadingInformationUIElement *preading = 0;
1079     ITfCandidateListUIElement *pcandlist = 0;
1080     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1081     if (!element)
1082         return E_INVALIDARG;
1083 
1084     *pbShow = FALSE;
1085     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1086         BSTR bstr;
1087         if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
1088             SysFreeString(bstr);
1089         }
1090         preading->lpVtbl->Release(preading);
1091     }
1092     else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1093         videodata->ime_candref++;
1094         UILess_GetCandidateList(videodata, pcandlist);
1095         pcandlist->lpVtbl->Release(pcandlist);
1096     }
1097     return S_OK;
1098 }
1099 
UIElementSink_UpdateUIElement(TSFSink * sink,DWORD dwUIElementId)1100 STDMETHODIMP UIElementSink_UpdateUIElement(TSFSink *sink, DWORD dwUIElementId)
1101 {
1102     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1103     ITfReadingInformationUIElement *preading = 0;
1104     ITfCandidateListUIElement *pcandlist = 0;
1105     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1106     if (!element)
1107         return E_INVALIDARG;
1108 
1109     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1110         BSTR bstr;
1111         if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
1112             WCHAR *s = (WCHAR *)bstr;
1113             SDL_wcslcpy(videodata->ime_readingstring, s, SDL_arraysize(videodata->ime_readingstring));
1114             IME_SendEditingEvent(videodata);
1115             SysFreeString(bstr);
1116         }
1117         preading->lpVtbl->Release(preading);
1118     }
1119     else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1120         UILess_GetCandidateList(videodata, pcandlist);
1121         pcandlist->lpVtbl->Release(pcandlist);
1122     }
1123     return S_OK;
1124 }
1125 
UIElementSink_EndUIElement(TSFSink * sink,DWORD dwUIElementId)1126 STDMETHODIMP UIElementSink_EndUIElement(TSFSink *sink, DWORD dwUIElementId)
1127 {
1128     ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
1129     ITfReadingInformationUIElement *preading = 0;
1130     ITfCandidateListUIElement *pcandlist = 0;
1131     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1132     if (!element)
1133         return E_INVALIDARG;
1134 
1135     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
1136         videodata->ime_readingstring[0] = 0;
1137         IME_SendEditingEvent(videodata);
1138         preading->lpVtbl->Release(preading);
1139     }
1140     if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
1141         videodata->ime_candref--;
1142         if (videodata->ime_candref == 0)
1143             IME_CloseCandidateList(videodata);
1144 
1145         pcandlist->lpVtbl->Release(pcandlist);
1146     }
1147     return S_OK;
1148 }
1149 
IPPASink_QueryInterface(TSFSink * sink,REFIID riid,PVOID * ppv)1150 STDMETHODIMP IPPASink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
1151 {
1152     if (!ppv)
1153         return E_INVALIDARG;
1154 
1155     *ppv = 0;
1156     if (WIN_IsEqualIID(riid, &IID_IUnknown))
1157         *ppv = (IUnknown *)sink;
1158     else if (WIN_IsEqualIID(riid, &IID_ITfInputProcessorProfileActivationSink))
1159         *ppv = (ITfInputProcessorProfileActivationSink *)sink;
1160 
1161     if (*ppv) {
1162         TSFSink_AddRef(sink);
1163         return S_OK;
1164     }
1165     return E_NOINTERFACE;
1166 }
1167 
IPPASink_OnActivated(TSFSink * sink,DWORD dwProfileType,LANGID langid,REFCLSID clsid,REFGUID catid,REFGUID guidProfile,HKL hkl,DWORD dwFlags)1168 STDMETHODIMP IPPASink_OnActivated(TSFSink *sink, DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags)
1169 {
1170     static const GUID TF_PROFILE_DAYI = { 0x037B2C25, 0x480C, 0x4D7F, { 0xB0, 0x27, 0xD6, 0xCA, 0x6B, 0x69, 0x78, 0x8A } };
1171     SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
1172     videodata->ime_candlistindexbase = WIN_IsEqualGUID(&TF_PROFILE_DAYI, guidProfile) ? 0 : 1;
1173     if (WIN_IsEqualIID(catid, &GUID_TFCAT_TIP_KEYBOARD) && (dwFlags & TF_IPSINK_FLAG_ACTIVE))
1174         IME_InputLangChanged((SDL_VideoData *)sink->data);
1175 
1176     IME_HideCandidateList(videodata);
1177     return S_OK;
1178 }
1179 
1180 static void *vtUIElementSink[] = {
1181     (void *)(UIElementSink_QueryInterface),
1182     (void *)(TSFSink_AddRef),
1183     (void *)(TSFSink_Release),
1184     (void *)(UIElementSink_BeginUIElement),
1185     (void *)(UIElementSink_UpdateUIElement),
1186     (void *)(UIElementSink_EndUIElement)
1187 };
1188 
1189 static void *vtIPPASink[] = {
1190     (void *)(IPPASink_QueryInterface),
1191     (void *)(TSFSink_AddRef),
1192     (void *)(TSFSink_Release),
1193     (void *)(IPPASink_OnActivated)
1194 };
1195 
1196 static void
UILess_EnableUIUpdates(SDL_VideoData * videodata)1197 UILess_EnableUIUpdates(SDL_VideoData *videodata)
1198 {
1199     ITfSource *source = 0;
1200     if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie != TF_INVALID_COOKIE)
1201         return;
1202 
1203     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1204         source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie);
1205         source->lpVtbl->Release(source);
1206     }
1207 }
1208 
1209 static void
UILess_DisableUIUpdates(SDL_VideoData * videodata)1210 UILess_DisableUIUpdates(SDL_VideoData *videodata)
1211 {
1212     ITfSource *source = 0;
1213     if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie == TF_INVALID_COOKIE)
1214         return;
1215 
1216     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1217         source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
1218         videodata->ime_uielemsinkcookie = TF_INVALID_COOKIE;
1219         source->lpVtbl->Release(source);
1220     }
1221 }
1222 
1223 static SDL_bool
UILess_SetupSinks(SDL_VideoData * videodata)1224 UILess_SetupSinks(SDL_VideoData *videodata)
1225 {
1226     TfClientId clientid = 0;
1227     SDL_bool result = SDL_FALSE;
1228     ITfSource *source = 0;
1229     if (FAILED(CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgrEx, (LPVOID *)&videodata->ime_threadmgrex)))
1230         return SDL_FALSE;
1231 
1232     if (FAILED(videodata->ime_threadmgrex->lpVtbl->ActivateEx(videodata->ime_threadmgrex, &clientid, TF_TMAE_UIELEMENTENABLEDONLY)))
1233         return SDL_FALSE;
1234 
1235     videodata->ime_uielemsink = SDL_malloc(sizeof(TSFSink));
1236     videodata->ime_ippasink = SDL_malloc(sizeof(TSFSink));
1237 
1238     videodata->ime_uielemsink->lpVtbl = vtUIElementSink;
1239     videodata->ime_uielemsink->refcount = 1;
1240     videodata->ime_uielemsink->data = videodata;
1241 
1242     videodata->ime_ippasink->lpVtbl = vtIPPASink;
1243     videodata->ime_ippasink->refcount = 1;
1244     videodata->ime_ippasink->data = videodata;
1245 
1246     if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1247         if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie))) {
1248             if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfInputProcessorProfileActivationSink, (IUnknown *)videodata->ime_ippasink, &videodata->ime_alpnsinkcookie))) {
1249                 result = SDL_TRUE;
1250             }
1251         }
1252         source->lpVtbl->Release(source);
1253     }
1254     return result;
1255 }
1256 
1257 #define SAFE_RELEASE(p)                             \
1258 {                                                   \
1259     if (p) {                                        \
1260         (p)->lpVtbl->Release((p));                  \
1261         (p) = 0;                                    \
1262     }                                               \
1263 }
1264 
1265 static void
UILess_ReleaseSinks(SDL_VideoData * videodata)1266 UILess_ReleaseSinks(SDL_VideoData *videodata)
1267 {
1268     ITfSource *source = 0;
1269     if (videodata->ime_threadmgrex && SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
1270         source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
1271         source->lpVtbl->UnadviseSink(source, videodata->ime_alpnsinkcookie);
1272         SAFE_RELEASE(source);
1273         videodata->ime_threadmgrex->lpVtbl->Deactivate(videodata->ime_threadmgrex);
1274         SAFE_RELEASE(videodata->ime_threadmgrex);
1275         TSFSink_Release(videodata->ime_uielemsink);
1276         videodata->ime_uielemsink = 0;
1277         TSFSink_Release(videodata->ime_ippasink);
1278         videodata->ime_ippasink = 0;
1279     }
1280 }
1281 
1282 static void *
StartDrawToBitmap(HDC hdc,HBITMAP * hhbm,int width,int height)1283 StartDrawToBitmap(HDC hdc, HBITMAP *hhbm, int width, int height)
1284 {
1285     BITMAPINFO info;
1286     BITMAPINFOHEADER *infoHeader = &info.bmiHeader;
1287     BYTE *bits = NULL;
1288     if (hhbm) {
1289         SDL_zero(info);
1290         infoHeader->biSize = sizeof(BITMAPINFOHEADER);
1291         infoHeader->biWidth = width;
1292         infoHeader->biHeight = -1 * SDL_abs(height);
1293         infoHeader->biPlanes = 1;
1294         infoHeader->biBitCount = 32;
1295         infoHeader->biCompression = BI_RGB;
1296         *hhbm = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void **)&bits, 0, 0);
1297         if (*hhbm)
1298             SelectObject(hdc, *hhbm);
1299     }
1300     return bits;
1301 }
1302 
1303 static void
StopDrawToBitmap(HDC hdc,HBITMAP * hhbm)1304 StopDrawToBitmap(HDC hdc, HBITMAP *hhbm)
1305 {
1306     if (hhbm && *hhbm) {
1307         DeleteObject(*hhbm);
1308         *hhbm = NULL;
1309     }
1310 }
1311 
1312 /* This draws only within the specified area and fills the entire region. */
1313 static void
DrawRect(HDC hdc,int left,int top,int right,int bottom,int pensize)1314 DrawRect(HDC hdc, int left, int top, int right, int bottom, int pensize)
1315 {
1316     /* The case of no pen (PenSize = 0) is automatically taken care of. */
1317     const int penadjust = (int)SDL_floor(pensize / 2.0f - 0.5f);
1318     left += pensize / 2;
1319     top += pensize / 2;
1320     right -= penadjust;
1321     bottom -= penadjust;
1322     Rectangle(hdc, left, top, right, bottom);
1323 }
1324 
1325 static void
IME_DestroyTextures(SDL_VideoData * videodata)1326 IME_DestroyTextures(SDL_VideoData *videodata)
1327 {
1328 }
1329 
1330 #define SDL_swap(a,b) { \
1331     int c = (a);        \
1332     (a) = (b);          \
1333     (b) = c;            \
1334     }
1335 
1336 static void
IME_PositionCandidateList(SDL_VideoData * videodata,SIZE size)1337 IME_PositionCandidateList(SDL_VideoData *videodata, SIZE size)
1338 {
1339     int left, top, right, bottom;
1340     SDL_bool ok = SDL_FALSE;
1341     int winw = videodata->ime_winwidth;
1342     int winh = videodata->ime_winheight;
1343 
1344     /* Bottom */
1345     left = videodata->ime_rect.x;
1346     top = videodata->ime_rect.y + videodata->ime_rect.h;
1347     right = left + size.cx;
1348     bottom = top + size.cy;
1349     if (right >= winw) {
1350         left -= right - winw;
1351         right = winw;
1352     }
1353     if (bottom < winh)
1354         ok = SDL_TRUE;
1355 
1356     /* Top */
1357     if (!ok) {
1358         left = videodata->ime_rect.x;
1359         top = videodata->ime_rect.y - size.cy;
1360         right = left + size.cx;
1361         bottom = videodata->ime_rect.y;
1362         if (right >= winw) {
1363             left -= right - winw;
1364             right = winw;
1365         }
1366         if (top >= 0)
1367             ok = SDL_TRUE;
1368     }
1369 
1370     /* Right */
1371     if (!ok) {
1372         left = videodata->ime_rect.x + size.cx;
1373         top = 0;
1374         right = left + size.cx;
1375         bottom = size.cy;
1376         if (right < winw)
1377             ok = SDL_TRUE;
1378     }
1379 
1380     /* Left */
1381     if (!ok) {
1382         left = videodata->ime_rect.x - size.cx;
1383         top = 0;
1384         right = videodata->ime_rect.x;
1385         bottom = size.cy;
1386         if (right >= 0)
1387             ok = SDL_TRUE;
1388     }
1389 
1390     /* Window too small, show at (0,0) */
1391     if (!ok) {
1392         left = 0;
1393         top = 0;
1394         right = size.cx;
1395         bottom = size.cy;
1396     }
1397 
1398     videodata->ime_candlistrect.x = left;
1399     videodata->ime_candlistrect.y = top;
1400     videodata->ime_candlistrect.w = right - left;
1401     videodata->ime_candlistrect.h = bottom - top;
1402 }
1403 
1404 static void
IME_RenderCandidateList(SDL_VideoData * videodata,HDC hdc)1405 IME_RenderCandidateList(SDL_VideoData *videodata, HDC hdc)
1406 {
1407     int i, j;
1408     SIZE size = {0};
1409     SIZE candsizes[MAX_CANDLIST];
1410     SIZE maxcandsize = {0};
1411     HBITMAP hbm = NULL;
1412     const int candcount = SDL_min(SDL_min(MAX_CANDLIST, videodata->ime_candcount), videodata->ime_candpgsize);
1413     SDL_bool vertical = videodata->ime_candvertical;
1414 
1415     const int listborder = 1;
1416     const int listpadding = 0;
1417     const int listbordercolor = RGB(0xB4, 0xC7, 0xAA);
1418     const int listfillcolor = RGB(255, 255, 255);
1419 
1420     const int candborder = 1;
1421     const int candpadding = 0;
1422     const int candmargin = 1;
1423     const COLORREF candbordercolor = RGB(255, 255, 255);
1424     const COLORREF candfillcolor = RGB(255, 255, 255);
1425     const COLORREF candtextcolor = RGB(0, 0, 0);
1426     const COLORREF selbordercolor = RGB(0x84, 0xAC, 0xDD);
1427     const COLORREF selfillcolor = RGB(0xD2, 0xE6, 0xFF);
1428     const COLORREF seltextcolor = RGB(0, 0, 0);
1429     const int horzcandspacing = 5;
1430 
1431     HPEN listpen = listborder != 0 ? CreatePen(PS_SOLID, listborder, listbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1432     HBRUSH listbrush = CreateSolidBrush(listfillcolor);
1433     HPEN candpen = candborder != 0 ? CreatePen(PS_SOLID, candborder, candbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1434     HBRUSH candbrush = CreateSolidBrush(candfillcolor);
1435     HPEN selpen = candborder != 0 ? CreatePen(PS_DOT, candborder, selbordercolor) : (HPEN)GetStockObject(NULL_PEN);
1436     HBRUSH selbrush = CreateSolidBrush(selfillcolor);
1437     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"));
1438 
1439     SetBkMode(hdc, TRANSPARENT);
1440     SelectObject(hdc, font);
1441 
1442     for (i = 0; i < candcount; ++i) {
1443         const WCHAR *s = videodata->ime_candidates[i];
1444         if (!*s)
1445             break;
1446 
1447         GetTextExtentPoint32W(hdc, s, (int)SDL_wcslen(s), &candsizes[i]);
1448         maxcandsize.cx = SDL_max(maxcandsize.cx, candsizes[i].cx);
1449         maxcandsize.cy = SDL_max(maxcandsize.cy, candsizes[i].cy);
1450 
1451     }
1452     if (vertical) {
1453         size.cx =
1454             (listborder * 2) +
1455             (listpadding * 2) +
1456             (candmargin * 2) +
1457             (candborder * 2) +
1458             (candpadding * 2) +
1459             (maxcandsize.cx)
1460             ;
1461         size.cy =
1462             (listborder * 2) +
1463             (listpadding * 2) +
1464             ((candcount + 1) * candmargin) +
1465             (candcount * candborder * 2) +
1466             (candcount * candpadding * 2) +
1467             (candcount * maxcandsize.cy)
1468             ;
1469     }
1470     else {
1471         size.cx =
1472             (listborder * 2) +
1473             (listpadding * 2) +
1474             ((candcount + 1) * candmargin) +
1475             (candcount * candborder * 2) +
1476             (candcount * candpadding * 2) +
1477             ((candcount - 1) * horzcandspacing);
1478         ;
1479 
1480         for (i = 0; i < candcount; ++i)
1481             size.cx += candsizes[i].cx;
1482 
1483         size.cy =
1484             (listborder * 2) +
1485             (listpadding * 2) +
1486             (candmargin * 2) +
1487             (candborder * 2) +
1488             (candpadding * 2) +
1489             (maxcandsize.cy)
1490             ;
1491     }
1492 
1493     StartDrawToBitmap(hdc, &hbm, size.cx, size.cy);
1494 
1495     SelectObject(hdc, listpen);
1496     SelectObject(hdc, listbrush);
1497     DrawRect(hdc, 0, 0, size.cx, size.cy, listborder);
1498 
1499     SelectObject(hdc, candpen);
1500     SelectObject(hdc, candbrush);
1501     SetTextColor(hdc, candtextcolor);
1502     SetBkMode(hdc, TRANSPARENT);
1503 
1504     for (i = 0; i < candcount; ++i) {
1505         const WCHAR *s = videodata->ime_candidates[i];
1506         int left, top, right, bottom;
1507         if (!*s)
1508             break;
1509 
1510         if (vertical) {
1511             left = listborder + listpadding + candmargin;
1512             top = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * maxcandsize.cy);
1513             right = size.cx - listborder - listpadding - candmargin;
1514             bottom = top + maxcandsize.cy + (candpadding * 2) + (candborder * 2);
1515         }
1516         else {
1517             left = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * horzcandspacing);
1518 
1519             for (j = 0; j < i; ++j)
1520                 left += candsizes[j].cx;
1521 
1522             top = listborder + listpadding + candmargin;
1523             right = left + candsizes[i].cx + (candpadding * 2) + (candborder * 2);
1524             bottom = size.cy - listborder - listpadding - candmargin;
1525         }
1526 
1527         if (i == videodata->ime_candsel) {
1528             SelectObject(hdc, selpen);
1529             SelectObject(hdc, selbrush);
1530             SetTextColor(hdc, seltextcolor);
1531         }
1532         else {
1533             SelectObject(hdc, candpen);
1534             SelectObject(hdc, candbrush);
1535             SetTextColor(hdc, candtextcolor);
1536         }
1537 
1538         DrawRect(hdc, left, top, right, bottom, candborder);
1539         ExtTextOutW(hdc, left + candborder + candpadding, top + candborder + candpadding, 0, NULL, s, (int)SDL_wcslen(s), NULL);
1540     }
1541     StopDrawToBitmap(hdc, &hbm);
1542 
1543     DeleteObject(listpen);
1544     DeleteObject(listbrush);
1545     DeleteObject(candpen);
1546     DeleteObject(candbrush);
1547     DeleteObject(selpen);
1548     DeleteObject(selbrush);
1549     DeleteObject(font);
1550 
1551     IME_PositionCandidateList(videodata, size);
1552 }
1553 
1554 static void
IME_Render(SDL_VideoData * videodata)1555 IME_Render(SDL_VideoData *videodata)
1556 {
1557     HDC hdc = CreateCompatibleDC(NULL);
1558 
1559     if (videodata->ime_candlist)
1560         IME_RenderCandidateList(videodata, hdc);
1561 
1562     DeleteDC(hdc);
1563 
1564     videodata->ime_dirty = SDL_FALSE;
1565 }
1566 
IME_Present(SDL_VideoData * videodata)1567 void IME_Present(SDL_VideoData *videodata)
1568 {
1569     if (videodata->ime_dirty)
1570         IME_Render(videodata);
1571 
1572     /* FIXME: Need to show the IME bitmap */
1573 }
1574 
1575 #endif /* SDL_DISABLE_WINDOWS_IME */
1576 
1577 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
1578 
1579 /* vi: set ts=4 sw=4 expandtab: */
1580