text: /*
    Simple DirectMedia Layer
    Copyright (C) 1997-2012 Sam Lantinga <slouken@libsdl.org>
  
    This software is provided 'as-is', without any express or implied
    warranty.  In no event will the authors be held liable for any damages
    arising from the use of this software.
  
    Permission is granted to anyone to use this software for any purpose,
    including commercial applications, and to alter it and redistribute it
    freely, subject to the following restrictions:
  
    1. The origin of this software must not be misrepresented; you must not
       claim that you wrote the original software. If you use this software
       in a product, an acknowledgment in the product documentation would be
       appreciated but is not required.
    2. Altered source versions must be plainly marked as such, and must not be
       misrepresented as being the original software.
    3. This notice may not be removed or altered from any source distribution.
  */
  #include "SDL_config.h"
  
  #if SDL_VIDEO_DRIVER_WINDOWS
  
  #ifdef _WIN32_WCE
  #define SDL_DISABLE_WINDOWS_IME
  #endif
  
  #include "SDL_windowsvideo.h"
  
  #include "../../events/SDL_keyboard_c.h"
  #include "../../events/scancodes_windows.h"
  
  #include <imm.h>
  #include <oleauto.h>
  
  #ifndef SDL_DISABLE_WINDOWS_IME
  static void IME_Init(SDL_VideoData *videodata, HWND hwnd);
  static void IME_Enable(SDL_VideoData *videodata, HWND hwnd);
  static void IME_Disable(SDL_VideoData *videodata, HWND hwnd);
  static void IME_Quit(SDL_VideoData *videodata);
  #endif /* !SDL_DISABLE_WINDOWS_IME */
  
  #ifndef MAPVK_VK_TO_VSC
  #define MAPVK_VK_TO_VSC     0
  #endif
  #ifndef MAPVK_VSC_TO_VK
  #define MAPVK_VSC_TO_VK     1
  #endif
  #ifndef MAPVK_VK_TO_CHAR
  #define MAPVK_VK_TO_CHAR    2
  #endif
  
  /* Alphabetic scancodes for PC keyboards */
  BYTE alpha_scancodes[26] = {
      30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24,
      25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44
  };
  
  BYTE keypad_scancodes[10] = {
      82, 79, 80, 81, 75, 76, 77, 71, 72, 73
  };
  
  void
  WIN_InitKeyboard(_THIS)
  {
      SDL_VideoData *data = (SDL_VideoData *) _this->driverdata;
      int i;
  
      /* Make sure the alpha scancodes are correct.  T isn't usually remapped */
      if (MapVirtualKey('T', MAPVK_VK_TO_VSC) != alpha_scancodes['T' - 'A']) {
  #if 0
          printf
              ("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");
  #endif
          for (i = 0; i < SDL_arraysize(alpha_scancodes); ++i) {
              alpha_scancodes[i] = MapVirtualKey('A' + i, MAPVK_VK_TO_VSC);
  #if 0
              printf("%d = %d\n", i, alpha_scancodes[i]);
  #endif
          }
      }
      if (MapVirtualKey(VK_NUMPAD0, MAPVK_VK_TO_VSC) != keypad_scancodes[0]) {
  #if 0
          printf
              ("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");
  #endif
          for (i = 0; i < SDL_arraysize(keypad_scancodes); ++i) {
              keypad_scancodes[i] =
                  MapVirtualKey(VK_NUMPAD0 + i, MAPVK_VK_TO_VSC);
  #if 0
              printf("%d = %d\n", i, keypad_scancodes[i]);
  #endif
          }
      }
  
      data->key_layout = windows_scancode_table;
  
      data->ime_com_initialized = SDL_FALSE;
      data->ime_threadmgr = 0;
      data->ime_initialized = SDL_FALSE;
      data->ime_enabled = SDL_FALSE;
      data->ime_available = SDL_FALSE;
      data->ime_hwnd_main = 0;
      data->ime_hwnd_current = 0;
      data->ime_himc = 0;
      data->ime_composition[0] = 0;
      data->ime_readingstring[0] = 0;
      data->ime_cursor = 0;
  
      data->ime_candlist = SDL_FALSE;
      SDL_memset(data->ime_candidates, 0, sizeof(data->ime_candidates));
      data->ime_candcount = 0;
      data->ime_candref = 0;
      data->ime_candsel = 0;
      data->ime_candpgsize = 0;
      data->ime_candlistindexbase = 0;
      data->ime_candvertical = SDL_TRUE;
  
      data->ime_dirty = SDL_FALSE;
      SDL_memset(&data->ime_rect, 0, sizeof(data->ime_rect));
      SDL_memset(&data->ime_candlistrect, 0, sizeof(data->ime_candlistrect));
      data->ime_winwidth = 0;
      data->ime_winheight = 0;
  
      data->ime_hkl = 0;
      data->ime_himm32 = 0;
      data->GetReadingString = 0;
      data->ShowReadingWindow = 0;
      data->ImmLockIMC = 0;
      data->ImmUnlockIMC = 0;
      data->ImmLockIMCC = 0;
      data->ImmUnlockIMCC = 0;
      data->ime_uiless = SDL_FALSE;
      data->ime_threadmgrex = 0;
      data->ime_uielemsinkcookie = TF_INVALID_COOKIE;
      data->ime_alpnsinkcookie = TF_INVALID_COOKIE;
      data->ime_openmodesinkcookie = TF_INVALID_COOKIE;
      data->ime_convmodesinkcookie = TF_INVALID_COOKIE;
      data->ime_uielemsink = 0;
      data->ime_ippasink = 0;
  
      WIN_UpdateKeymap();
  
      SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
      SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
      SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
  }
  
  void
  WIN_UpdateKeymap()
  {
      int i;
      SDL_Scancode scancode;
      SDL_Keycode keymap[SDL_NUM_SCANCODES];
  
      SDL_GetDefaultKeymap(keymap);
  
      for (i = 0; i < SDL_arraysize(windows_scancode_table); i++) {
  
          /* Make sure this scancode is a valid character scancode */
          scancode = windows_scancode_table[i];
          if (scancode == SDL_SCANCODE_UNKNOWN || keymap[scancode] >= 127) {
              continue;
          }
  
          /* Alphabetic keys are handled specially, since Windows remaps them */
          if (i >= 'A' && i <= 'Z') {
              BYTE vsc = alpha_scancodes[i - 'A'];
              keymap[scancode] = MapVirtualKey(vsc, MAPVK_VSC_TO_VK) + 0x20;
          } else {
              keymap[scancode] = (MapVirtualKey(i, MAPVK_VK_TO_CHAR) & 0x7FFF);
          }
      }
      SDL_SetKeymap(0, keymap, SDL_NUM_SCANCODES);
  }
  
  void
  WIN_QuitKeyboard(_THIS)
  {
  #ifndef SDL_DISABLE_WINDOWS_IME
      IME_Quit((SDL_VideoData *)_this->driverdata);
  #endif
  }
  
  void
  WIN_StartTextInput(_THIS)
  {
  #ifndef SDL_DISABLE_WINDOWS_IME
      SDL_Window *window = SDL_GetKeyboardFocus();
      if (window) {
          HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
          SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
          SDL_GetWindowSize(window, &videodata->ime_winwidth, &videodata->ime_winheight);
          IME_Init(videodata, hwnd);
          IME_Enable(videodata, hwnd);
      }
  #endif /* !SDL_DISABLE_WINDOWS_IME */
  }
  
  void
  WIN_StopTextInput(_THIS)
  {
  #ifndef SDL_DISABLE_WINDOWS_IME
      SDL_Window *window = SDL_GetKeyboardFocus();
      if (window) {
          HWND hwnd = ((SDL_WindowData *) window->driverdata)->hwnd;
          SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
          IME_Init(videodata, hwnd);
          IME_Disable(videodata, hwnd);
      }
  #endif /* !SDL_DISABLE_WINDOWS_IME */
  }
  
  void
  WIN_SetTextInputRect(_THIS, SDL_Rect *rect)
  {
      SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
      videodata->ime_rect = *rect;
  }
  
  #ifdef SDL_DISABLE_WINDOWS_IME
  
  
  SDL_bool
  IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
  {
      return SDL_FALSE;
  }
  
  void IME_Present(SDL_VideoData *videodata)
  {
  }
  
  #else
  
  #ifdef __GNUC__
  #undef DEFINE_GUID
  #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}}
  DEFINE_GUID(IID_ITfInputProcessorProfileActivationSink,        0x71C6E74E,0x0F28,0x11D8,0xA8,0x2A,0x00,0x06,0x5B,0x84,0x43,0x5C);
  DEFINE_GUID(IID_ITfUIElementSink,                              0xEA1EA136,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
  DEFINE_GUID(GUID_TFCAT_TIP_KEYBOARD,                           0x34745C63,0xB2F0,0x4784,0x8B,0x67,0x5E,0x12,0xC8,0x70,0x1A,0x31);
  DEFINE_GUID(IID_ITfSource,                                     0x4EA48A35,0x60AE,0x446F,0x8F,0xD6,0xE6,0xA8,0xD8,0x24,0x59,0xF7);
  DEFINE_GUID(IID_ITfUIElementMgr,                               0xEA1EA135,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
  DEFINE_GUID(IID_ITfCandidateListUIElement,                     0xEA1EA138,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
  DEFINE_GUID(IID_ITfReadingInformationUIElement,                0xEA1EA139,0x19DF,0x11D7,0xA6,0xD2,0x00,0x06,0x5B,0x84,0x43,0x5C);
  DEFINE_GUID(IID_ITfThreadMgr,                                  0xAA80E801,0x2021,0x11D2,0x93,0xE0,0x00,0x60,0xB0,0x67,0xB8,0x6E);
  DEFINE_GUID(CLSID_TF_ThreadMgr,                                0x529A9E6B,0x6587,0x4F23,0xAB,0x9E,0x9C,0x7D,0x68,0x3E,0x3C,0x50);
  DEFINE_GUID(IID_ITfThreadMgrEx,                                0x3E90ADE3,0x7594,0x4CB0,0xBB,0x58,0x69,0x62,0x8F,0x5F,0x45,0x8C);
  #endif
  
  #define LANG_CHT MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_TRADITIONAL)
  #define LANG_CHS MAKELANGID(LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED)
  
  #define MAKEIMEVERSION(major,minor) ((DWORD) (((BYTE)(major) << 24) | ((BYTE)(minor) << 16) ))
  #define IMEID_VER(id) ((id) & 0xffff0000)
  #define IMEID_LANG(id) ((id) & 0x0000ffff)
  
  #define CHT_HKL_DAYI            ((HKL)0xE0060404)
  #define CHT_HKL_NEW_PHONETIC    ((HKL)0xE0080404)
  #define CHT_HKL_NEW_CHANG_JIE   ((HKL)0xE0090404)
  #define CHT_HKL_NEW_QUICK       ((HKL)0xE00A0404)
  #define CHT_HKL_HK_CANTONESE    ((HKL)0xE00B0404)
  #define CHT_IMEFILENAME1        "TINTLGNT.IME"
  #define CHT_IMEFILENAME2        "CINTLGNT.IME"
  #define CHT_IMEFILENAME3        "MSTCIPHA.IME"
  #define IMEID_CHT_VER42         (LANG_CHT | MAKEIMEVERSION(4, 2))
  #define IMEID_CHT_VER43         (LANG_CHT | MAKEIMEVERSION(4, 3))
  #define IMEID_CHT_VER44         (LANG_CHT | MAKEIMEVERSION(4, 4))
  #define IMEID_CHT_VER50         (LANG_CHT | MAKEIMEVERSION(5, 0))
  #define IMEID_CHT_VER51         (LANG_CHT | MAKEIMEVERSION(5, 1))
  #define IMEID_CHT_VER52         (LANG_CHT | MAKEIMEVERSION(5, 2))
  #define IMEID_CHT_VER60         (LANG_CHT | MAKEIMEVERSION(6, 0))
  #define IMEID_CHT_VER_VISTA     (LANG_CHT | MAKEIMEVERSION(7, 0))
  
  #define CHS_HKL                 ((HKL)0xE00E0804)
  #define CHS_IMEFILENAME1        "PINTLGNT.IME"
  #define CHS_IMEFILENAME2        "MSSCIPYA.IME"
  #define IMEID_CHS_VER41         (LANG_CHS | MAKEIMEVERSION(4, 1))
  #define IMEID_CHS_VER42         (LANG_CHS | MAKEIMEVERSION(4, 2))
  #define IMEID_CHS_VER53         (LANG_CHS | MAKEIMEVERSION(5, 3))
  
  #define LANG() LOWORD((videodata->ime_hkl))
  #define PRIMLANG() ((WORD)PRIMARYLANGID(LANG()))
  #define SUBLANG() SUBLANGID(LANG())
  
  static void IME_UpdateInputLocale(SDL_VideoData *videodata);
  static void IME_ClearComposition(SDL_VideoData *videodata);
  static void IME_SetWindow(SDL_VideoData* videodata, HWND hwnd);
  static void IME_SetupAPI(SDL_VideoData *videodata);
  static DWORD IME_GetId(SDL_VideoData *videodata, UINT uIndex);
  static void IME_SendEditingEvent(SDL_VideoData *videodata);
  static void IME_DestroyTextures(SDL_VideoData *videodata);
  
  #define SDL_IsEqualIID(riid1, riid2) SDL_IsEqualGUID(riid1, riid2)
  #define SDL_IsEqualGUID(rguid1, rguid2) (!SDL_memcmp(rguid1, rguid2, sizeof(GUID)))
  
  static SDL_bool UILess_SetupSinks(SDL_VideoData *videodata);
  static void UILess_ReleaseSinks(SDL_VideoData *videodata);
  static void UILess_EnableUIUpdates(SDL_VideoData *videodata);
  static void UILess_DisableUIUpdates(SDL_VideoData *videodata);
  
  static void
  IME_Init(SDL_VideoData *videodata, HWND hwnd)
  {
      if (videodata->ime_initialized)
          return;
  
      videodata->ime_hwnd_main = hwnd;
      if (SUCCEEDED(WIN_CoInitialize())) {
          videodata->ime_com_initialized = SDL_TRUE;
          CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, (LPVOID *)&videodata->ime_threadmgr);
      }
      videodata->ime_initialized = SDL_TRUE;
      videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
      if (!videodata->ime_himm32) {
          videodata->ime_available = SDL_FALSE;
          return;
      }
      videodata->ImmLockIMC = (LPINPUTCONTEXT2 (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMC");
      videodata->ImmUnlockIMC = (BOOL (WINAPI *)(HIMC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMC");
      videodata->ImmLockIMCC = (LPVOID (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmLockIMCC");
      videodata->ImmUnlockIMCC = (BOOL (WINAPI *)(HIMCC))SDL_LoadFunction(videodata->ime_himm32, "ImmUnlockIMCC");
  
      IME_SetWindow(videodata, hwnd);
      videodata->ime_himc = ImmGetContext(hwnd);
      ImmReleaseContext(hwnd, videodata->ime_himc);
      if (!videodata->ime_himc) {
          videodata->ime_available = SDL_FALSE;
          IME_Disable(videodata, hwnd);
          return;
      }
      videodata->ime_available = SDL_TRUE;
      IME_UpdateInputLocale(videodata);
      IME_SetupAPI(videodata);
      videodata->ime_uiless = UILess_SetupSinks(videodata);
      IME_UpdateInputLocale(videodata);
      IME_Disable(videodata, hwnd);
  }
  
  static void
  IME_Enable(SDL_VideoData *videodata, HWND hwnd)
  {
      if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
          return;
  
      if (!videodata->ime_available) {
          IME_Disable(videodata, hwnd);
          return;
      }
      if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
          ImmAssociateContext(videodata->ime_hwnd_current, videodata->ime_himc);
  
      videodata->ime_enabled = SDL_TRUE;
      IME_UpdateInputLocale(videodata);
      UILess_EnableUIUpdates(videodata);
  }
  
  static void
  IME_Disable(SDL_VideoData *videodata, HWND hwnd)
  {
      if (!videodata->ime_initialized || !videodata->ime_hwnd_current)
          return;
  
      IME_ClearComposition(videodata);
      if (videodata->ime_hwnd_current == videodata->ime_hwnd_main)
          ImmAssociateContext(videodata->ime_hwnd_current, (HIMC)0);
  
      videodata->ime_enabled = SDL_FALSE;
      UILess_DisableUIUpdates(videodata);
  }
  
  static void
  IME_Quit(SDL_VideoData *videodata)
  {
      if (!videodata->ime_initialized)
          return;
  
      UILess_ReleaseSinks(videodata);
      if (videodata->ime_hwnd_main)
          ImmAssociateContext(videodata->ime_hwnd_main, videodata->ime_himc);
  
      videodata->ime_hwnd_main = 0;
      videodata->ime_himc = 0;
      if (videodata->ime_himm32) {
          SDL_UnloadObject(videodata->ime_himm32);
          videodata->ime_himm32 = 0;
      }
      if (videodata->ime_threadmgr) {
          videodata->ime_threadmgr->lpVtbl->Release(videodata->ime_threadmgr);
          videodata->ime_threadmgr = 0;
      }
      if (videodata->ime_com_initialized) {
          WIN_CoUninitialize();
          videodata->ime_com_initialized = SDL_FALSE;
      }
      IME_DestroyTextures(videodata);
      videodata->ime_initialized = SDL_FALSE;
  }
  
  static void
  IME_GetReadingString(SDL_VideoData *videodata, HWND hwnd)
  {
      DWORD id = 0;
      HIMC himc = 0;
      WCHAR buffer[16];
      WCHAR *s = buffer;
      DWORD len = 0;
      INT err = 0;
      BOOL vertical = FALSE;
      UINT maxuilen = 0;
      static OSVERSIONINFOA osversion = {0};
      if (videodata->ime_uiless)
          return;
  
      videodata->ime_readingstring[0] = 0;
      if (!osversion.dwOSVersionInfoSize) {
          osversion.dwOSVersionInfoSize = sizeof(osversion);
          GetVersionExA(&osversion);
      }
      id = IME_GetId(videodata, 0);
      if (!id)
          return;
  
      himc = ImmGetContext(hwnd);
      if (!himc)
          return;
  
      if (videodata->GetReadingString) {
          len = videodata->GetReadingString(himc, 0, 0, &err, &vertical, &maxuilen);
          if (len) {
              if (len > SDL_arraysize(buffer))
                  len = SDL_arraysize(buffer);
  
              len = videodata->GetReadingString(himc, len, s, &err, &vertical, &maxuilen);
          }
          SDL_wcslcpy(videodata->ime_readingstring, s, len);
      }
      else {
          LPINPUTCONTEXT2 lpimc = videodata->ImmLockIMC(himc);
          LPBYTE p = 0;
          s = 0;
          switch (id)
          {
          case IMEID_CHT_VER42:
          case IMEID_CHT_VER43:
          case IMEID_CHT_VER44:
              p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 24);
              if (!p)
                  break;
  
              len = *(DWORD *)(p + 7*4 + 32*4);
              s = (WCHAR *)(p + 56);
              break;
          case IMEID_CHT_VER51:
          case IMEID_CHT_VER52:
          case IMEID_CHS_VER53:
              p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 4);
              if (!p)
                  break;
  
              p = *(LPBYTE *)((LPBYTE)p + 1*4 + 5*4);
              if (!p)
                  break;
  
              len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
              s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
              break;
          case IMEID_CHS_VER41:
              {
                  int offset = (IME_GetId(videodata, 1) >= 0x00000002) ? 8 : 7;
                  p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + offset * 4);
                  if (!p)
                      break;
  
                  len = *(DWORD *)(p + 7*4 + 16*2*4);
                  s = (WCHAR *)(p + 6*4 + 16*2*1);
              }
              break;
          case IMEID_CHS_VER42:
              if (osversion.dwPlatformId != VER_PLATFORM_WIN32_NT)
                  break;
  
              p = *(LPBYTE *)((LPBYTE)videodata->ImmLockIMCC(lpimc->hPrivate) + 1*4 + 1*4 + 6*4);
              if (!p)
                  break;
  
              len = *(DWORD *)(p + 1*4 + (16*2+2*4) + 5*4 + 16*2);
              s = (WCHAR *)(p + 1*4 + (16*2+2*4) + 5*4);
              break;
          }
          if (s)
              SDL_wcslcpy(videodata->ime_readingstring, s, len + 1);
  
          videodata->ImmUnlockIMCC(lpimc->hPrivate);
          videodata->ImmUnlockIMC(himc);
      }
      ImmReleaseContext(hwnd, himc);
      IME_SendEditingEvent(videodata);
  }
  
  static void
  IME_InputLangChanged(SDL_VideoData *videodata)
  {
      UINT lang = PRIMLANG();
      IME_UpdateInputLocale(videodata);
      if (!videodata->ime_uiless)
          videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1;
  
      IME_SetupAPI(videodata);
      if (lang != PRIMLANG()) {
          IME_ClearComposition(videodata);
      }
  }
  
  static DWORD
  IME_GetId(SDL_VideoData *videodata, UINT uIndex)
  {
      static HKL hklprev = 0;
      static DWORD dwRet[2] = {0};
      DWORD dwVerSize = 0;
      DWORD dwVerHandle = 0;
      LPVOID lpVerBuffer = 0;
      LPVOID lpVerData = 0;
      UINT cbVerData = 0;
      char szTemp[256];
      HKL hkl = 0;
      DWORD dwLang = 0;
      if (uIndex >= sizeof(dwRet) / sizeof(dwRet[0]))
          return 0;
  
      hkl = videodata->ime_hkl;
      if (hklprev == hkl)
          return dwRet[uIndex];
  
      hklprev = hkl;
      dwLang = ((DWORD_PTR)hkl & 0xffff);
      if (videodata->ime_uiless && LANG() == LANG_CHT) {
          dwRet[0] = IMEID_CHT_VER_VISTA;
          dwRet[1] = 0;
          return dwRet[0];
      }
      if (hkl != CHT_HKL_NEW_PHONETIC
          && hkl != CHT_HKL_NEW_CHANG_JIE
          && hkl != CHT_HKL_NEW_QUICK
          && hkl != CHT_HKL_HK_CANTONESE
          && hkl != CHS_HKL) {
          dwRet[0] = dwRet[1] = 0;
          return dwRet[uIndex];
      }
      if (ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1) <= 0) {
          dwRet[0] = dwRet[1] = 0;
          return dwRet[uIndex];
      }
      if (!videodata->GetReadingString) {
          #define LCID_INVARIANT MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT)
          if (CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME1, -1) != 2
              && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME2, -1) != 2
              && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHT_IMEFILENAME3, -1) != 2
              && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME1, -1) != 2
              && CompareStringA(LCID_INVARIANT, NORM_IGNORECASE, szTemp, -1, CHS_IMEFILENAME2, -1) != 2) {
              dwRet[0] = dwRet[1] = 0;
              return dwRet[uIndex];
          }
          #undef LCID_INVARIANT
          dwVerSize = GetFileVersionInfoSizeA(szTemp, &dwVerHandle);
          if (dwVerSize) {
              lpVerBuffer = SDL_malloc(dwVerSize);
              if (lpVerBuffer) {
                  if (GetFileVersionInfoA(szTemp, dwVerHandle, dwVerSize, lpVerBuffer)) {
                      if (VerQueryValueA(lpVerBuffer, "\\", &lpVerData, &cbVerData)) {
                          #define pVerFixedInfo   ((VS_FIXEDFILEINFO FAR*)lpVerData)
                          DWORD dwVer = pVerFixedInfo->dwFileVersionMS;
                          dwVer = (dwVer & 0x00ff0000) << 8 | (dwVer & 0x000000ff) << 16;
                          if (videodata->GetReadingString ||
                              dwLang == LANG_CHT && (
                              dwVer == MAKEIMEVERSION(4, 2) ||
                              dwVer == MAKEIMEVERSION(4, 3) ||
                              dwVer == MAKEIMEVERSION(4, 4) ||
                              dwVer == MAKEIMEVERSION(5, 0) ||
                              dwVer == MAKEIMEVERSION(5, 1) ||
                              dwVer == MAKEIMEVERSION(5, 2) ||
                              dwVer == MAKEIMEVERSION(6, 0))
                              ||
                              dwLang == LANG_CHS && (
                              dwVer == MAKEIMEVERSION(4, 1) ||
                              dwVer == MAKEIMEVERSION(4, 2) ||
                              dwVer == MAKEIMEVERSION(5, 3))) {
                              dwRet[0] = dwVer | dwLang;
                              dwRet[1] = pVerFixedInfo->dwFileVersionLS;
                              SDL_free(lpVerBuffer);
                              return dwRet[0];
                          }
                          #undef pVerFixedInfo
                      }
                  }
              }
              SDL_free(lpVerBuffer);
          }
      }
      dwRet[0] = dwRet[1] = 0;
      return dwRet[uIndex];
  }
  
  static void
  IME_SetupAPI(SDL_VideoData *videodata)
  {
      char ime_file[MAX_PATH + 1];
      void* hime = 0;
      HKL hkl = 0;
      videodata->GetReadingString = 0;
      videodata->ShowReadingWindow = 0;
      if (videodata->ime_uiless)
          return;
  
      hkl = videodata->ime_hkl;
      if (ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1) <= 0)
          return;
  
      hime = SDL_LoadObject(ime_file);
      if (!hime)
          return;
  
      videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT))
          SDL_LoadFunction(hime, "GetReadingString");
      videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL))
          SDL_LoadFunction(hime, "ShowReadingWindow");
  
      if (videodata->ShowReadingWindow) {
          HIMC himc = ImmGetContext(videodata->ime_hwnd_current);
          if (himc) {
              videodata->ShowReadingWindow(himc, FALSE);
              ImmReleaseContext(videodata->ime_hwnd_current, himc);
          }
      }
  }
  
  static void
  IME_SetWindow(SDL_VideoData* videodata, HWND hwnd)
  {
      videodata->ime_hwnd_current = hwnd;
      if (videodata->ime_threadmgr) {
          struct ITfDocumentMgr *document_mgr = 0;
          if (SUCCEEDED(videodata->ime_threadmgr->lpVtbl->AssociateFocus(videodata->ime_threadmgr, hwnd, NULL, &document_mgr))) {
              if (document_mgr)
                  document_mgr->lpVtbl->Release(document_mgr);
          }
      }
  }
  
  static void
  IME_UpdateInputLocale(SDL_VideoData *videodata)
  {
      static HKL hklprev = 0;
      videodata->ime_hkl = GetKeyboardLayout(0);
      if (hklprev == videodata->ime_hkl)
          return;
  
      hklprev = videodata->ime_hkl;
      switch (PRIMLANG()) {
      case LANG_CHINESE:
          videodata->ime_candvertical = SDL_TRUE;
          if (SUBLANG() == SUBLANG_CHINESE_SIMPLIFIED)
              videodata->ime_candvertical = SDL_FALSE;
  
          break;
      case LANG_JAPANESE:
          videodata->ime_candvertical = SDL_TRUE;
          break;
      case LANG_KOREAN:
          videodata->ime_candvertical = SDL_FALSE;
          break;
      }
  }
  
  static void
  IME_ClearComposition(SDL_VideoData *videodata)
  {
      HIMC himc = 0;
      if (!videodata->ime_initialized)
          return;
  
      himc = ImmGetContext(videodata->ime_hwnd_current);
      if (!himc)
          return;
  
      ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
      if (videodata->ime_uiless)
          ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
  
      ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0);
      ImmReleaseContext(videodata->ime_hwnd_current, himc);
      SDL_SendEditingText("", 0, 0);
  }
  
  static void
  IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
  {
      LONG length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, sizeof(videodata->ime_composition));
      if (length < 0)
          length = 0;
  
      length /= sizeof(videodata->ime_composition[0]);
      videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
      if (videodata->ime_composition[videodata->ime_cursor] == 0x3000) {
          int i;
          for (i = videodata->ime_cursor + 1; i < length; ++i)
              videodata->ime_composition[i - 1] = videodata->ime_composition[i];
  
          --length;
      }
      videodata->ime_composition[length] = 0;
  }
  
  static void
  IME_SendInputEvent(SDL_VideoData *videodata)
  {
      char *s = 0;
      s = WIN_StringToUTF8(videodata->ime_composition);
      SDL_SendKeyboardText(s);
      SDL_free(s);
  
      videodata->ime_composition[0] = 0;
      videodata->ime_readingstring[0] = 0;
      videodata->ime_cursor = 0;
  }
  
  static void
  IME_SendEditingEvent(SDL_VideoData *videodata)
  {
      char *s = 0;
      WCHAR buffer[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
      buffer[0] = 0;
      if (videodata->ime_readingstring[0]) {
          size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);
          SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
          SDL_wcslcat(buffer, videodata->ime_readingstring, sizeof(buffer));
          SDL_wcslcat(buffer, &videodata->ime_composition[len], sizeof(buffer) - len);
      }
      else {
          SDL_wcslcpy(buffer, videodata->ime_composition, sizeof(videodata->ime_composition));
      }
      s = WIN_StringToUTF8(buffer);
      SDL_SendEditingText(s, videodata->ime_cursor + SDL_wcslen(videodata->ime_readingstring), 0);
      SDL_free(s);
  }
  
  static void
  IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate)
  {
      LPWSTR dst = videodata->ime_candidates[i];
      *dst++ = (WCHAR)(TEXT('0') + ((i + videodata->ime_candlistindexbase) % 10));
      if (videodata->ime_candvertical)
          *dst++ = TEXT(' ');
  
      while (*candidate && (SDL_arraysize(videodata->ime_candidates[i]) > (dst - videodata->ime_candidates[i])))
          *dst++ = *candidate++;
  
      *dst = (WCHAR)'\0';
  }
  
  static void
  IME_GetCandidateList(HIMC himc, SDL_VideoData *videodata)
  {
      LPCANDIDATELIST cand_list = 0;
      DWORD size = ImmGetCandidateListW(himc, 0, 0, 0);
      if (size) {
          cand_list = (LPCANDIDATELIST)SDL_malloc(size);
          if (cand_list) {
              size = ImmGetCandidateListW(himc, 0, cand_list, size);
              if (size) {
                  int i = 0;
                  int j = 0;
                  int page_start = 0;
                  videodata->ime_candsel = cand_list->dwSelection;
                  videodata->ime_candcount = cand_list->dwCount;
  
                  if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) {
                      const UINT maxcandchar = 18;
                      UINT i = 0;
                      UINT cchars = 0;
  
                      for (; i < videodata->ime_candcount; ++i) {
                          UINT len = SDL_wcslen((LPWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i])) + 1;
                          if (len + cchars > maxcandchar) {
                              if (i > cand_list->dwSelection)
                                  break;
  
                              page_start = i;
                              cchars = len;
                          }
                          else {
                              cchars += len;
                          }
                      }
                      videodata->ime_candpgsize = i - page_start;
                  }
                  else {
                      videodata->ime_candpgsize = SDL_min(cand_list->dwPageSize, MAX_CANDLIST);
                      page_start = (cand_list->dwSelection / videodata->ime_candpgsize) * videodata->ime_candpgsize;
                  }
                  SDL_memset(&videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
                  for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < (int)videodata->ime_candpgsize; i++, j++) {
                      LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]);
                      IME_AddCandidate(videodata, j, candidate);
                  }
                  if (PRIMLANG() == LANG_KOREAN || (PRIMLANG() == LANG_CHT && !IME_GetId(videodata, 0)))
                      videodata->ime_candsel = -1;
  
              }
              SDL_free(cand_list);
          }
      }
  }
  
  static void
  IME_ShowCandidateList(SDL_VideoData *videodata)
  {
      videodata->ime_dirty = SDL_TRUE;
      videodata->ime_candlist = SDL_TRUE;
      IME_DestroyTextures(videodata);
      IME_SendEditingEvent(videodata);
  }
  
  static void
  IME_HideCandidateList(SDL_VideoData *videodata)
  {
      videodata->ime_dirty = SDL_FALSE;
      videodata->ime_candlist = SDL_FALSE;
      IME_DestroyTextures(videodata);
      IME_SendEditingEvent(videodata);
  }
  
  SDL_bool
  IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
  {
      SDL_bool trap = SDL_FALSE;
      HIMC himc = 0;
      if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled)
          return SDL_FALSE;
  
      switch (msg) {
      case WM_INPUTLANGCHANGE:
          IME_InputLangChanged(videodata);
          break;
      case WM_IME_SETCONTEXT:
          *lParam = 0;
          break;
      case WM_IME_STARTCOMPOSITION:
          trap = SDL_TRUE;
          break;
      case WM_IME_COMPOSITION:
          trap = SDL_TRUE;
          himc = ImmGetContext(hwnd);
          if (*lParam & GCS_RESULTSTR) {
              IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
              IME_SendInputEvent(videodata);
          }
          if (*lParam & GCS_COMPSTR) {
              if (!videodata->ime_uiless)
                  videodata->ime_readingstring[0] = 0;
  
              IME_GetCompositionString(videodata, himc, GCS_COMPSTR);
              IME_SendEditingEvent(videodata);
          }
          ImmReleaseContext(hwnd, himc);
          break;
      case WM_IME_ENDCOMPOSITION:
          videodata->ime_composition[0] = 0;
          videodata->ime_readingstring[0] = 0;
          videodata->ime_cursor = 0;
          SDL_SendEditingText("", 0, 0);
          break;
      case WM_IME_NOTIFY:
          switch (wParam) {
          case IMN_SETCONVERSIONMODE:
          case IMN_SETOPENSTATUS:
              IME_UpdateInputLocale(videodata);
              break;
          case IMN_OPENCANDIDATE:
          case IMN_CHANGECANDIDATE:
              if (videodata->ime_uiless)
                  break;
  
              trap = SDL_TRUE;
              IME_ShowCandidateList(videodata);
              himc = ImmGetContext(hwnd);
              if (!himc)
                  break;
  
              IME_GetCandidateList(himc, videodata);
              ImmReleaseContext(hwnd, himc);
              break;
          case IMN_CLOSECANDIDATE:
              trap = SDL_TRUE;
              IME_HideCandidateList(videodata);
              break;
          case IMN_PRIVATE:
              {
                  DWORD dwId = IME_GetId(videodata, 0);
                  IME_GetReadingString(videodata, hwnd);
                  switch (dwId)
                  {
                  case IMEID_CHT_VER42:
                  case IMEID_CHT_VER43:
                  case IMEID_CHT_VER44:
                  case IMEID_CHS_VER41:
                  case IMEID_CHS_VER42:
                      if (*lParam == 1 || *lParam == 2)
                          trap = SDL_TRUE;
  
                      break;
                  case IMEID_CHT_VER50:
                  case IMEID_CHT_VER51:
                  case IMEID_CHT_VER52:
                  case IMEID_CHT_VER60:
                  case IMEID_CHS_VER53:
                      if (*lParam == 16
                          || *lParam == 17
                          || *lParam == 26
                          || *lParam == 27
                          || *lParam == 28)
                          trap = SDL_TRUE;
                      break;
                  }
              }
              break;
          default:
              trap = SDL_TRUE;
              break;
          }
          break;
      }
      return trap;
  }
  
  static void
  IME_CloseCandidateList(SDL_VideoData *videodata)
  {
      IME_HideCandidateList(videodata);
      videodata->ime_candcount = 0;
      SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
  }
  
  static void
  UILess_GetCandidateList(SDL_VideoData *videodata, ITfCandidateListUIElement *pcandlist)
  {
      UINT selection = 0;
      UINT count = 0;
      UINT page = 0;
      UINT pgcount = 0;
      DWORD pgstart = 0;
      DWORD pgsize = 0;
      UINT i, j;
      pcandlist->lpVtbl->GetSelection(pcandlist, &selection);
      pcandlist->lpVtbl->GetCount(pcandlist, &count);
      pcandlist->lpVtbl->GetCurrentPage(pcandlist, &page);
  
      videodata->ime_candsel = selection;
      videodata->ime_candcount = count;
      IME_ShowCandidateList(videodata);
  
      pcandlist->lpVtbl->GetPageIndex(pcandlist, 0, 0, &pgcount);
      if (pgcount > 0) {
          UINT *idxlist = SDL_malloc(sizeof(UINT) * pgcount);
          if (idxlist) {
              pcandlist->lpVtbl->GetPageIndex(pcandlist, idxlist, pgcount, &pgcount);
              pgstart = idxlist[page];
              if (page < pgcount - 1)
                  pgsize = SDL_min(count, idxlist[page + 1]) - pgstart;
              else
                  pgsize = count - pgstart;
  
              SDL_free(idxlist);
          }
      }
      videodata->ime_candpgsize = SDL_min(pgsize, MAX_CANDLIST);
      videodata->ime_candsel = videodata->ime_candsel - pgstart;
  
      SDL_memset(videodata->ime_candidates, 0, sizeof(videodata->ime_candidates));
      for (i = pgstart, j = 0; (DWORD)i < count && j < videodata->ime_candpgsize; i++, j++) {
          BSTR bstr;
          if (SUCCEEDED(pcandlist->lpVtbl->GetString(pcandlist, i, &bstr))) {
              if (bstr) {
                  IME_AddCandidate(videodata, j, bstr);
                  SysFreeString(bstr);
              }
          }
      }
      if (PRIMLANG() == LANG_KOREAN)
          videodata->ime_candsel = -1;
  }
  
  STDMETHODIMP_(ULONG) TSFSink_AddRef(TSFSink *sink)
  {
      return ++sink->refcount;
  }
  
  STDMETHODIMP_(ULONG)TSFSink_Release(TSFSink *sink)
  {
      --sink->refcount;
      if (sink->refcount == 0) {
          SDL_free(sink);
          return 0;
      }
      return sink->refcount;
  }
  
  STDMETHODIMP UIElementSink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
  {
      if (!ppv)
          return E_INVALIDARG;
  
      *ppv = 0;
      if (SDL_IsEqualIID(riid, &IID_IUnknown))
          *ppv = (IUnknown *)sink;
      else if (SDL_IsEqualIID(riid, &IID_ITfUIElementSink))
          *ppv = (ITfUIElementSink *)sink;
  
      if (*ppv) {
          TSFSink_AddRef(sink);
          return S_OK;
      }
      return E_NOINTERFACE;
  }
  
  ITfUIElement *UILess_GetUIElement(SDL_VideoData *videodata, DWORD dwUIElementId)
  {
      ITfUIElementMgr *puiem = 0;
      ITfUIElement *pelem = 0;
      ITfThreadMgrEx *threadmgrex = videodata->ime_threadmgrex;
  
      if (SUCCEEDED(threadmgrex->lpVtbl->QueryInterface(threadmgrex, &IID_ITfUIElementMgr, (LPVOID *)&puiem))) {
          puiem->lpVtbl->GetUIElement(puiem, dwUIElementId, &pelem);
          puiem->lpVtbl->Release(puiem);
      }
      return pelem;
  }
  
  STDMETHODIMP UIElementSink_BeginUIElement(TSFSink *sink, DWORD dwUIElementId, BOOL *pbShow)
  {
      ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
      ITfReadingInformationUIElement *preading = 0;
      ITfCandidateListUIElement *pcandlist = 0;
      SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
      if (!element)
          return E_INVALIDARG;
  
      *pbShow = FALSE;
      if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
          BSTR bstr;
          if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
              WCHAR *s = (WCHAR *)bstr;
              SysFreeString(bstr);
          }
          preading->lpVtbl->Release(preading);
      }
      else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist)))	{
          videodata->ime_candref++;
          UILess_GetCandidateList(videodata, pcandlist);
          pcandlist->lpVtbl->Release(pcandlist);
      }
      return S_OK;
  }
  
  STDMETHODIMP UIElementSink_UpdateUIElement(TSFSink *sink, DWORD dwUIElementId)
  {
      ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
      ITfReadingInformationUIElement *preading = 0;
      ITfCandidateListUIElement *pcandlist = 0;
      SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
      if (!element)
          return E_INVALIDARG;
  
      if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
          BSTR bstr;
          if (SUCCEEDED(preading->lpVtbl->GetString(preading, &bstr)) && bstr) {
              WCHAR *s = (WCHAR *)bstr;
              SDL_wcslcpy(videodata->ime_readingstring, s, sizeof(videodata->ime_readingstring));
              IME_SendEditingEvent(videodata);
              SysFreeString(bstr);
          }
          preading->lpVtbl->Release(preading);
      }
      else if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist)))	{
          UILess_GetCandidateList(videodata, pcandlist);
          pcandlist->lpVtbl->Release(pcandlist);
      }
      return S_OK;
  }
  
  STDMETHODIMP UIElementSink_EndUIElement(TSFSink *sink, DWORD dwUIElementId)
  {
      ITfUIElement *element = UILess_GetUIElement((SDL_VideoData *)sink->data, dwUIElementId);
      ITfReadingInformationUIElement *preading = 0;
      ITfCandidateListUIElement *pcandlist = 0;
      SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
      if (!element)
          return E_INVALIDARG;
  
      if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfReadingInformationUIElement, (LPVOID *)&preading))) {
          videodata->ime_readingstring[0] = 0;
          IME_SendEditingEvent(videodata);
          preading->lpVtbl->Release(preading);
      }
      if (SUCCEEDED(element->lpVtbl->QueryInterface(element, &IID_ITfCandidateListUIElement, (LPVOID *)&pcandlist))) {
          videodata->ime_candref--;
          if (videodata->ime_candref == 0)
              IME_CloseCandidateList(videodata);
  
          pcandlist->lpVtbl->Release(pcandlist);
      }
      return S_OK;
  }
  
  STDMETHODIMP IPPASink_QueryInterface(TSFSink *sink, REFIID riid, PVOID *ppv)
  {
      if (!ppv)
          return E_INVALIDARG;
  
      *ppv = 0;
      if (SDL_IsEqualIID(riid, &IID_IUnknown))
          *ppv = (IUnknown *)sink;
      else if (SDL_IsEqualIID(riid, &IID_ITfInputProcessorProfileActivationSink))
          *ppv = (ITfInputProcessorProfileActivationSink *)sink;
  
      if (*ppv) {
          TSFSink_AddRef(sink);
          return S_OK;
      }
      return E_NOINTERFACE;
  }
  
  STDMETHODIMP IPPASink_OnActivated(TSFSink *sink, DWORD dwProfileType, LANGID langid, REFCLSID clsid, REFGUID catid, REFGUID guidProfile, HKL hkl, DWORD dwFlags)
  {
      static GUID TF_PROFILE_DAYI = {0x037B2C25, 0x480C, 0x4D7F, 0xB0, 0x27, 0xD6, 0xCA, 0x6B, 0x69, 0x78, 0x8A};
      SDL_VideoData *videodata = (SDL_VideoData *)sink->data;
      videodata->ime_candlistindexbase = SDL_IsEqualGUID(&TF_PROFILE_DAYI, guidProfile) ? 0 : 1;
      if (SDL_IsEqualIID(catid, &GUID_TFCAT_TIP_KEYBOARD) && (dwFlags & TF_IPSINK_FLAG_ACTIVE))
          IME_InputLangChanged((SDL_VideoData *)sink->data);
  
      IME_HideCandidateList(videodata);
      return S_OK;
  }
  
  static void *vtUIElementSink[] = {
      (void *)(UIElementSink_QueryInterface),
      (void *)(TSFSink_AddRef),
      (void *)(TSFSink_Release),
      (void *)(UIElementSink_BeginUIElement),
      (void *)(UIElementSink_UpdateUIElement),
      (void *)(UIElementSink_EndUIElement)
  };
  
  static void *vtIPPASink[] = {
      (void *)(IPPASink_QueryInterface),
      (void *)(TSFSink_AddRef),
      (void *)(TSFSink_Release),
      (void *)(IPPASink_OnActivated)
  };
  
  static void
  UILess_EnableUIUpdates(SDL_VideoData *videodata)
  {
      ITfSource *source = 0;
      if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie != TF_INVALID_COOKIE)
          return;
  
      if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
          source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie);
          source->lpVtbl->Release(source);
      }
  }
  
  static void
  UILess_DisableUIUpdates(SDL_VideoData *videodata)
  {
      ITfSource *source = 0;
      if (!videodata->ime_threadmgrex || videodata->ime_uielemsinkcookie == TF_INVALID_COOKIE)
          return;
  
      if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
          source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
          videodata->ime_uielemsinkcookie = TF_INVALID_COOKIE;
          source->lpVtbl->Release(source);
      }
  }
  
  static SDL_bool
  UILess_SetupSinks(SDL_VideoData *videodata)
  {
      TfClientId clientid = 0;
      SDL_bool result = SDL_FALSE;
      ITfSource *source = 0;
      if (FAILED(CoCreateInstance(&CLSID_TF_ThreadMgr, NULL, CLSCTX_INPROC_SERVER, &IID_ITfThreadMgrEx, (LPVOID *)&videodata->ime_threadmgrex)))
          return SDL_FALSE;
  
      if (FAILED(videodata->ime_threadmgrex->lpVtbl->ActivateEx(videodata->ime_threadmgrex, &clientid, TF_TMAE_UIELEMENTENABLEDONLY)))
          return SDL_FALSE;
  
      videodata->ime_uielemsink = SDL_malloc(sizeof(TSFSink));
      videodata->ime_ippasink = SDL_malloc(sizeof(TSFSink));
  
      videodata->ime_uielemsink->lpVtbl = vtUIElementSink;
      videodata->ime_uielemsink->refcount = 1;
      videodata->ime_uielemsink->data = videodata;
  
      videodata->ime_ippasink->lpVtbl = vtIPPASink;
      videodata->ime_ippasink->refcount = 1;
      videodata->ime_ippasink->data = videodata;
  
      if (SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
          if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfUIElementSink, (IUnknown *)videodata->ime_uielemsink, &videodata->ime_uielemsinkcookie))) {
              if (SUCCEEDED(source->lpVtbl->AdviseSink(source, &IID_ITfInputProcessorProfileActivationSink, (IUnknown *)videodata->ime_ippasink, &videodata->ime_alpnsinkcookie))) {
                  result = SDL_TRUE;
              }
          }
          source->lpVtbl->Release(source);
      }
      return result;
  }
  
  #define SAFE_RELEASE(p)                             \
  {                                                   \
      if (p) {                                        \
          (p)->lpVtbl->Release((p));                  \
          (p) = 0;                                    \
      }                                               \
  }
  
  static void
  UILess_ReleaseSinks(SDL_VideoData *videodata)
  {
      ITfSource *source = 0;
      if (videodata->ime_threadmgrex && SUCCEEDED(videodata->ime_threadmgrex->lpVtbl->QueryInterface(videodata->ime_threadmgrex, &IID_ITfSource, (LPVOID *)&source))) {
          source->lpVtbl->UnadviseSink(source, videodata->ime_uielemsinkcookie);
          source->lpVtbl->UnadviseSink(source, videodata->ime_alpnsinkcookie);
          SAFE_RELEASE(source);
          videodata->ime_threadmgrex->lpVtbl->Deactivate(videodata->ime_threadmgrex);
          SAFE_RELEASE(videodata->ime_threadmgrex);
          TSFSink_Release(videodata->ime_uielemsink);
          videodata->ime_uielemsink = 0;
          TSFSink_Release(videodata->ime_ippasink);
          videodata->ime_ippasink = 0;
      }
  }
  
  static void *
  StartDrawToBitmap(HDC hdc, HBITMAP *hhbm, int width, int height)
  {
      BITMAPINFO info;
      BITMAPINFOHEADER *infoHeader = &info.bmiHeader;
      BYTE *bits = NULL;
      if (hhbm) {
          SDL_zero(info);
          infoHeader->biSize = sizeof(BITMAPINFOHEADER);
          infoHeader->biWidth = width;
          infoHeader->biHeight = -1 * SDL_abs(height);
          infoHeader->biPlanes = 1;
          infoHeader->biBitCount = 32;
          infoHeader->biCompression = BI_RGB;
          *hhbm = CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void **)&bits, 0, 0);
          if (*hhbm)
              SelectObject(hdc, *hhbm);
      }
      return bits;
  }
  
  static void
  StopDrawToBitmap(HDC hdc, HBITMAP *hhbm)
  {
      if (hhbm && *hhbm) {
          DeleteObject(*hhbm);
          *hhbm = NULL;
      }
  }
  
  /* This draws only within the specified area and fills the entire region. */
  static void
  DrawRect(HDC hdc, int left, int top, int right, int bottom, int pensize)
  {
      /* The case of no pen (PenSize = 0) is automatically taken care of. */
      const int penadjust = (int)SDL_floor(pensize / 2.0f - 0.5f);
      left += pensize / 2;
      top += pensize / 2;
      right -= penadjust;
      bottom -= penadjust;
      Rectangle(hdc, left, top, right, bottom);
  }
  
  static void
  IME_DestroyTextures(SDL_VideoData *videodata)
  {
  }
  
  #define SDL_swap(a,b) { \
      int c = (a);        \
      (a) = (b);          \
      (b) = c;            \
      }
  
  static void
  IME_PositionCandidateList(SDL_VideoData *videodata, SIZE size)
  {
      int left, top, right, bottom;
      SDL_bool ok = SDL_FALSE;
      int winw = videodata->ime_winwidth;
      int winh = videodata->ime_winheight;
  
      /* Bottom */
      left = videodata->ime_rect.x;
      top = videodata->ime_rect.y + videodata->ime_rect.h;
      right = left + size.cx;
      bottom = top + size.cy;
      if (right >= winw) {
          left -= right - winw;
          right = winw;
      }
      if (bottom < winh)
          ok = SDL_TRUE;
  
      /* Top */
      if (!ok) {
          left = videodata->ime_rect.x;
          top = videodata->ime_rect.y - size.cy;
          right = left + size.cx;
          bottom = videodata->ime_rect.y;
          if (right >= winw) {
              left -= right - winw;
              right = winw;
          }
          if (top >= 0)
              ok = SDL_TRUE;
      }
  
      /* Right */
      if (!ok) {
          left = videodata->ime_rect.x + size.cx;
          top = 0;
          right = left + size.cx;
          bottom = size.cy;
          if (right < winw)
              ok = SDL_TRUE;
      }
  
      /* Left */
      if (!ok) {
          left = videodata->ime_rect.x - size.cx;
          top = 0;
          right = videodata->ime_rect.x;
          bottom = size.cy;
          if (right >= 0)
              ok = SDL_TRUE;
      }
  
      /* Window too small, show at (0,0) */
      if (!ok) {
          left = 0;
          top = 0;
          right = size.cx;
          bottom = size.cy;
      }
  
      videodata->ime_candlistrect.x = left;
      videodata->ime_candlistrect.y = top;
      videodata->ime_candlistrect.w = right - left;
      videodata->ime_candlistrect.h = bottom - top;
  }
  
  static void
  IME_RenderCandidateList(SDL_VideoData *videodata, HDC hdc)
  {
      int i, j;
      SIZE size = {0};
      SIZE candsizes[MAX_CANDLIST];
      SIZE maxcandsize = {0};
      HBITMAP hbm = NULL;
      BYTE *bits = NULL;
      const int candcount = SDL_min(SDL_min(MAX_CANDLIST, videodata->ime_candcount), videodata->ime_candpgsize);
      SDL_bool vertical = videodata->ime_candvertical;
  
      const int listborder = 1;
      const int listpadding = 0;
      const int listbordercolor = RGB(0xB4, 0xC7, 0xAA);
      const int listfillcolor = RGB(255, 255, 255);
  
      const int candborder = 1;
      const int candpadding = 0;
      const int candmargin = 1;
      const COLORREF candbordercolor = RGB(255, 255, 255);
      const COLORREF candfillcolor = RGB(255, 255, 255);
      const COLORREF candtextcolor = RGB(0, 0, 0);
      const COLORREF selbordercolor = RGB(0x84, 0xAC, 0xDD);
      const COLORREF selfillcolor = RGB(0xD2, 0xE6, 0xFF);
      const COLORREF seltextcolor = RGB(0, 0, 0);
      const int horzcandspacing = 5;
  
      HPEN listpen = listborder != 0 ? CreatePen(PS_SOLID, listborder, listbordercolor) : (HPEN)GetStockObject(NULL_PEN);
      HBRUSH listbrush = CreateSolidBrush(listfillcolor);
      HPEN candpen = candborder != 0 ? CreatePen(PS_SOLID, candborder, candbordercolor) : (HPEN)GetStockObject(NULL_PEN);
      HBRUSH candbrush = CreateSolidBrush(candfillcolor);
      HPEN selpen = candborder != 0 ? CreatePen(PS_DOT, candborder, selbordercolor) : (HPEN)GetStockObject(NULL_PEN);
      HBRUSH selbrush = CreateSolidBrush(selfillcolor);
      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"));
  
      SetBkMode(hdc, TRANSPARENT);
      SelectObject(hdc, font);
  
      for (i = 0; i < candcount; ++i) {
          const WCHAR *s = videodata->ime_candidates[i];
          if (!*s)
              break;
  
          GetTextExtentPoint32W(hdc, s, SDL_wcslen(s), &candsizes[i]);
          maxcandsize.cx = SDL_max(maxcandsize.cx, candsizes[i].cx);
          maxcandsize.cy = SDL_max(maxcandsize.cy, candsizes[i].cy);
  
      }
      if (vertical) {
          size.cx =
              (listborder * 2) +
              (listpadding * 2) +
              (candmargin * 2) +
              (candborder * 2) +
              (candpadding * 2) +
              (maxcandsize.cx)
              ;
          size.cy =
              (listborder * 2) +
              (listpadding * 2) +
              ((candcount + 1) * candmargin) +
              (candcount * candborder * 2) +
              (candcount * candpadding * 2) +
              (candcount * maxcandsize.cy)
              ;
      }
      else {
          size.cx =
              (listborder * 2) +
              (listpadding * 2) +
              ((candcount + 1) * candmargin) +
              (candcount * candborder * 2) +
              (candcount * candpadding * 2) +
              ((candcount - 1) * horzcandspacing);
          ;
  
          for (i = 0; i < candcount; ++i)
              size.cx += candsizes[i].cx;
  
          size.cy =
              (listborder * 2) +
              (listpadding * 2) +
              (candmargin * 2) +
              (candborder * 2) +
              (candpadding * 2) +
              (maxcandsize.cy)
              ;
      }
  
      bits = StartDrawToBitmap(hdc, &hbm, size.cx, size.cy);
  
      SelectObject(hdc, listpen);
      SelectObject(hdc, listbrush);
      DrawRect(hdc, 0, 0, size.cx, size.cy, listborder);
  
      SelectObject(hdc, candpen);
      SelectObject(hdc, candbrush);
      SetTextColor(hdc, candtextcolor);
      SetBkMode(hdc, TRANSPARENT);
  
      for (i = 0; i < candcount; ++i) {
          const WCHAR *s = videodata->ime_candidates[i];
          int left, top, right, bottom;
          if (!*s)
              break;
  
          if (vertical) {
              left = listborder + listpadding + candmargin;
              top = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * maxcandsize.cy);
              right = size.cx - listborder - listpadding - candmargin;
              bottom = top + maxcandsize.cy + (candpadding * 2) + (candborder * 2);
          }
          else {
              left = listborder + listpadding + (i * candborder * 2) + (i * candpadding * 2) + ((i + 1) * candmargin) + (i * horzcandspacing);
  
              for (j = 0; j < i; ++j)
                  left += candsizes[j].cx;
  
              top = listborder + listpadding + candmargin;
              right = left + candsizes[i].cx + (candpadding * 2) + (candborder * 2);
              bottom = size.cy - listborder - listpadding - candmargin;
          }
  
          if (i == videodata->ime_candsel) {
              SelectObject(hdc, selpen);
              SelectObject(hdc, selbrush);
              SetTextColor(hdc, seltextcolor);
          }
          else {
              SelectObject(hdc, candpen);
              SelectObject(hdc, candbrush);
              SetTextColor(hdc, candtextcolor);
          }
  
          DrawRect(hdc, left, top, right, bottom, candborder);
          ExtTextOutW(hdc, left + candborder + candpadding, top + candborder + candpadding, 0, NULL, s, SDL_wcslen(s), NULL);
      }
      StopDrawToBitmap(hdc, &hbm);
  
      DeleteObject(listpen);
      DeleteObject(listbrush);
      DeleteObject(candpen);
      DeleteObject(candbrush);
      DeleteObject(selpen);
      DeleteObject(selbrush);
      DeleteObject(font);
  
      IME_PositionCandidateList(videodata, size);
  }
  
  static void
  IME_Render(SDL_VideoData *videodata)
  {
      HDC hdc = CreateCompatibleDC(NULL);
  
      if (videodata->ime_candlist)
          IME_RenderCandidateList(videodata, hdc);
  
      DeleteDC(hdc);
  
      videodata->ime_dirty = SDL_FALSE;
  }
  
  void IME_Present(SDL_VideoData *videodata)
  {
      if (videodata->ime_dirty)
          IME_Render(videodata);
  
      // FIXME: Need to show the IME bitmap
  }
  
  #endif /* SDL_DISABLE_WINDOWS_IME */
  
  #endif /* SDL_VIDEO_DRIVER_WINDOWS */
  
  /* vi: set ts=4 sw=4 expandtab: */
  