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