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