#include "SDL_internal.h"
#if defined(SDL_VIDEO_DRIVER_WINDOWS) && !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
#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
#if 0#else
#define SDL_DebugIMELog(...)
#endif
static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window);
static void IME_Enable(SDL_VideoData *videodata, HWND hwnd);
static void IME_Disable(SDL_VideoData *videodata, HWND hwnd);
static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor);
static void IME_ClearComposition(SDL_VideoData *videodata);
static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd);
static void IME_Quit(SDL_VideoData *videodata);
#else
static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor);
#endif
#ifndef MAPVK_VK_TO_VSC
#define MAPVK_VK_TO_VSC 0
#endif
#ifndef MAPVK_VSC_TO_VK
#define MAPVK_VSC_TO_VK 1
#endif
static struct WIN_KeymapCache
{
HKL keyboard_layout;
SDL_Keymap *keymap;
} keymap_cache[4];
static int keymap_cache_size;
static SDL_Keymap *WIN_GetCachedKeymap(HKL layout)
{
SDL_Keymap *keymap = NULL;
for (int i = 0; i < keymap_cache_size; ++i) {
if (keymap_cache[i].keyboard_layout == layout) {
keymap = keymap_cache[i].keymap;
if (i) {
SDL_memmove(keymap_cache + 1, keymap_cache, sizeof(struct WIN_KeymapCache) * i);
keymap_cache[0].keyboard_layout = layout;
keymap_cache[0].keymap = keymap;
}
break;
}
}
return keymap;
}
static void WIN_CacheKeymap(HKL layout, SDL_Keymap *keymap)
{
if (keymap_cache_size == SDL_arraysize(keymap_cache)) {
SDL_DestroyKeymap(keymap_cache[--keymap_cache_size].keymap);
}
if (keymap_cache_size) {
SDL_memmove(keymap_cache + 1, keymap_cache, sizeof(struct WIN_KeymapCache) * keymap_cache_size);
}
keymap_cache[0].keyboard_layout = layout;
keymap_cache[0].keymap = keymap;
++keymap_cache_size;
}
static SDL_Keymap *WIN_BuildKeymap()
{
SDL_Scancode scancode;
BYTE keyboardState[256] = { 0 };
WCHAR buffer[16];
const SDL_Keymod mods[] = {
SDL_KMOD_NONE,
SDL_KMOD_SHIFT,
SDL_KMOD_CAPS,
(SDL_KMOD_SHIFT | SDL_KMOD_CAPS),
SDL_KMOD_MODE,
(SDL_KMOD_MODE | SDL_KMOD_SHIFT),
(SDL_KMOD_MODE | SDL_KMOD_CAPS),
(SDL_KMOD_MODE | SDL_KMOD_SHIFT | SDL_KMOD_CAPS)
};
WIN_ResetDeadKeys();
SDL_Keymap *keymap = SDL_CreateKeymap(false);
if (!keymap) {
return NULL;
}
for (int m = 0; m < SDL_arraysize(mods); ++m) {
for (int i = 0; i < SDL_arraysize(windows_scancode_table); i++) {
int vk, sc, result;
Uint32 *ch = 0;
scancode = windows_scancode_table[i];
if (scancode == SDL_SCANCODE_UNKNOWN ||
scancode == SDL_SCANCODE_DELETE ||
(SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & SDLK_SCANCODE_MASK)) {
if (mods[m] == SDL_KMOD_NONE &&
(scancode == SDL_SCANCODE_CAPSLOCK ||
scancode == SDL_SCANCODE_BACKSPACE)) {
vk = LOBYTE(MapVirtualKey(i, MAPVK_VSC_TO_VK));
if (vk == VK_CAPITAL) {
SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_CAPSLOCK);
} else if (vk == VK_BACK) {
SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_BACKSPACE);
}
}
continue;
}
sc = MAKEWORD(i & 0x7f, (i & 0x80) ? 0xe0 : 0x00);
vk = LOBYTE(MapVirtualKey(sc, MAPVK_VSC_TO_VK));
if (!vk) {
continue;
}
keyboardState[VK_SHIFT] = (mods[m] & SDL_KMOD_SHIFT) ? 0x80 : 0x00;
keyboardState[VK_CAPITAL] = (mods[m] & SDL_KMOD_CAPS) ? 0x01 : 0x00;
keyboardState[VK_CONTROL] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00;
keyboardState[VK_MENU] = (mods[m] & SDL_KMOD_MODE) ? 0x80 : 0x00;
result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0);
buffer[SDL_abs(result)] = 0;
ch = (Uint32 *)SDL_iconv_string("UTF-32LE", "UTF-16LE", (const char *)buffer, (SDL_abs(result) + 1) * sizeof(WCHAR));
if (ch) {
SDL_SetKeymapEntry(keymap, scancode, mods[m], ch[1] == 0 ? ch[0] : 0xfffd);
SDL_free(ch);
} else {
if (!(mods[m] & SDL_KMOD_MODE)) {
SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_UNKNOWN);
}
}
if (result < 0) {
WIN_ResetDeadKeys();
}
}
}
return keymap;
}
void WIN_UpdateKeymap(bool send_event)
{
HKL layout = GetKeyboardLayout(0);
SDL_Keymap *keymap = WIN_GetCachedKeymap(layout);
if (!keymap) {
keymap = WIN_BuildKeymap();
if (keymap) {
WIN_CacheKeymap(layout, keymap);
}
}
SDL_SetKeymap(keymap, send_event);
}
void WIN_InitKeyboard(SDL_VideoDevice *_this)
{
#ifndef SDL_DISABLE_WINDOWS_IME
SDL_VideoData *data = _this->internal;
data->ime_candlistindexbase = 1;
data->ime_composition_length = 32 * sizeof(WCHAR);
data->ime_composition = (WCHAR *)SDL_calloc(data->ime_composition_length, sizeof(WCHAR));
#endif
WIN_UpdateKeymap(false);
SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu");
SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows");
SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows");
SDL_ToggleModState(SDL_KMOD_CAPS, (GetKeyState(VK_CAPITAL) & 0x0001) ? true : false);
SDL_ToggleModState(SDL_KMOD_NUM, (GetKeyState(VK_NUMLOCK) & 0x0001) ? true : false);
SDL_ToggleModState(SDL_KMOD_SCROLL, (GetKeyState(VK_SCROLL) & 0x0001) ? true : false);
}
void WIN_QuitKeyboard(SDL_VideoDevice *_this)
{
#ifndef SDL_DISABLE_WINDOWS_IME
SDL_VideoData *data = _this->internal;
IME_Quit(data);
if (data->ime_composition) {
SDL_free(data->ime_composition);
data->ime_composition = NULL;
}
#endif
for (int i = 0; i < keymap_cache_size; ++i) {
SDL_DestroyKeymap(keymap_cache[i].keymap);
}
SDL_zeroa(keymap_cache);
keymap_cache_size = 0;
}
void WIN_ResetDeadKeys(void)
{
BYTE keyboardState[256];
WCHAR buffer[16];
int vk, sc, result, i;
if (!GetKeyboardState(keyboardState)) {
return;
}
vk = VK_SPACE;
sc = MapVirtualKey(vk, MAPVK_VK_TO_VSC);
if (sc == 0) {
return;
}
for (i = 0; i < 5; i++) {
result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0);
if (result > 0) {
return;
}
}
}
bool WIN_StartTextInput(SDL_VideoDevice *_this, SDL_Window *window, SDL_PropertiesID props)
{
WIN_ResetDeadKeys();
#ifndef SDL_DISABLE_WINDOWS_IME
HWND hwnd = window->internal->hwnd;
SDL_VideoData *videodata = _this->internal;
IME_Init(videodata, window);
IME_Enable(videodata, hwnd);
WIN_UpdateTextInputArea(_this, window);
#endif
return true;
}
bool WIN_StopTextInput(SDL_VideoDevice *_this, SDL_Window *window)
{
WIN_ResetDeadKeys();
#ifndef SDL_DISABLE_WINDOWS_IME
HWND hwnd = window->internal->hwnd;
SDL_VideoData *videodata = _this->internal;
IME_Init(videodata, window);
IME_Disable(videodata, hwnd);
#endif
return true;
}
bool WIN_UpdateTextInputArea(SDL_VideoDevice *_this, SDL_Window *window)
{
SDL_VideoData *videodata = _this->internal;
SDL_WindowData *data = window->internal;
IME_SetTextInputArea(videodata, data->hwnd, &window->text_input_rect, window->text_input_cursor);
return true;
}
bool WIN_ClearComposition(SDL_VideoDevice *_this, SDL_Window *window)
{
#ifndef SDL_DISABLE_WINDOWS_IME
SDL_VideoData *videodata = _this->internal;
IME_ClearComposition(videodata);
#endif
return true;
}
#ifdef SDL_DISABLE_WINDOWS_IME
bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
{
return false;
}
void WIN_UpdateIMECandidates(SDL_VideoDevice *_this)
{
return;
}
#else
#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)(UINT_PTR)0xE0060404)
#define CHT_HKL_NEW_PHONETIC ((HKL)(UINT_PTR)0xE0080404)
#define CHT_HKL_NEW_CHANG_JIE ((HKL)(UINT_PTR)0xE0090404)
#define CHT_HKL_NEW_QUICK ((HKL)(UINT_PTR)0xE00A0404)
#define CHT_HKL_HK_CANTONESE ((HKL)(UINT_PTR)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)(UINT_PTR)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_SetWindow(SDL_VideoData *videodata, SDL_Window *window);
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_SendClearComposition(SDL_VideoData *videodata);
static bool IME_Init(SDL_VideoData *videodata, SDL_Window *window)
{
HWND hwnd = window->internal->hwnd;
if (videodata->ime_initialized) {
return true;
}
const char *hint = SDL_GetHint(SDL_HINT_IME_IMPLEMENTED_UI);
if (hint && SDL_strstr(hint, "composition")) {
videodata->ime_internal_composition = true;
}
if (hint && SDL_strstr(hint, "candidates")) {
videodata->ime_internal_candidates = true;
}
videodata->ime_hwnd_main = hwnd;
videodata->ime_initialized = true;
videodata->ime_himm32 = SDL_LoadObject("imm32.dll");
if (!videodata->ime_himm32) {
videodata->ime_available = false;
SDL_ClearError();
return true;
}
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, window);
videodata->ime_himc = ImmGetContext(hwnd);
ImmReleaseContext(hwnd, videodata->ime_himc);
if (!videodata->ime_himc) {
videodata->ime_available = false;
IME_Disable(videodata, hwnd);
return true;
}
videodata->ime_available = true;
IME_UpdateInputLocale(videodata);
IME_SetupAPI(videodata);
IME_UpdateInputLocale(videodata);
IME_Disable(videodata, hwnd);
return true;
}
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 = true;
IME_UpdateInputLocale(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 = false;
}
static void IME_Quit(SDL_VideoData *videodata)
{
if (!videodata->ime_initialized) {
return;
}
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;
}
for (int i = 0; i < videodata->ime_candcount; ++i) {
SDL_free(videodata->ime_candidates[i]);
videodata->ime_candidates[i] = NULL;
}
videodata->ime_initialized = 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;
videodata->ime_readingstring[0] = 0;
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 *)(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:
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) {
size_t size = SDL_min((size_t)(len + 1), SDL_arraysize(videodata->ime_readingstring));
SDL_wcslcpy(videodata->ime_readingstring, s, size);
}
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);
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;
LPVOID lpVerBuffer = 0;
LPVOID lpVerData = 0;
UINT cbVerData = 0;
char szTemp[256];
HKL hkl = 0;
DWORD dwLang = 0;
SDL_assert(uIndex < SDL_arraysize(dwRet));
hkl = videodata->ime_hkl;
if (hklprev == hkl) {
return dwRet[uIndex];
}
hklprev = hkl;
SDL_assert(uIndex == 0);
dwLang = ((DWORD_PTR)hkl & 0xffff);
if (videodata->ime_internal_candidates && dwLang == 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[0];
}
if (!ImmGetIMEFileNameA(hkl, szTemp, sizeof(szTemp) - 1)) {
dwRet[0] = dwRet[1] = 0;
return dwRet[0];
}
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[0];
}
#undef LCID_INVARIANT
dwVerSize = GetFileVersionInfoSizeA(szTemp, NULL);
if (dwVerSize) {
lpVerBuffer = SDL_malloc(dwVerSize);
if (lpVerBuffer) {
if (GetFileVersionInfoA(szTemp, 0, 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[0];
}
static void IME_SetupAPI(SDL_VideoData *videodata)
{
char ime_file[MAX_PATH + 1];
SDL_SharedObject *hime = 0;
HKL hkl = 0;
videodata->GetReadingString = NULL;
videodata->ShowReadingWindow = NULL;
hkl = videodata->ime_hkl;
if (!ImmGetIMEFileNameA(hkl, ime_file, sizeof(ime_file) - 1)) {
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, SDL_Window *window)
{
HWND hwnd = window->internal->hwnd;
if (hwnd != videodata->ime_hwnd_current) {
videodata->ime_hwnd_current = hwnd;
SDL_zero(videodata->ime_composition_area);
SDL_zero(videodata->ime_candidate_area);
}
IME_SetTextInputArea(videodata, hwnd, &window->text_input_rect, window->text_input_cursor);
}
#endif
static void IME_SetTextInputArea(SDL_VideoData *videodata, HWND hwnd, const SDL_Rect *rect, int cursor)
{
HIMC himc;
himc = ImmGetContext(hwnd);
if (himc) {
COMPOSITIONFORM cof;
CANDIDATEFORM caf;
int font_height = rect->h;
LOGFONTW font;
if (ImmGetCompositionFontW(himc, &font)) {
font_height = font.lfHeight;
}
SDL_zero(cof);
cof.dwStyle = CFS_RECT;
cof.ptCurrentPos.x = rect->x + cursor;
cof.ptCurrentPos.y = rect->y + (rect->h - font_height) / 2;
cof.rcArea.left = rect->x;
cof.rcArea.right = (LONG)rect->x + rect->w;
cof.rcArea.top = rect->y;
cof.rcArea.bottom = (LONG)rect->y + rect->h;
if (SDL_memcmp(&cof, &videodata->ime_composition_area, sizeof(cof)) != 0) {
SDL_copyp(&videodata->ime_composition_area, &cof);
ImmSetCompositionWindow(himc, &cof);
}
SDL_zero(caf);
caf.dwIndex = 0;
caf.dwStyle = CFS_EXCLUDE;
caf.ptCurrentPos.x = rect->x + cursor;
caf.ptCurrentPos.y = rect->y;
caf.rcArea.left = rect->x;
caf.rcArea.right = (LONG)rect->x + rect->w;
caf.rcArea.top = rect->y;
caf.rcArea.bottom = (LONG)rect->y + rect->h;
if (SDL_memcmp(&caf, &videodata->ime_candidate_area, sizeof(caf)) != 0) {
SDL_copyp(&videodata->ime_candidate_area, &caf);
ImmSetCandidateWindow(himc, &caf);
}
ImmReleaseContext(hwnd, himc);
}
}
#ifndef SDL_DISABLE_WINDOWS_IME
static void IME_UpdateInputLocale(SDL_VideoData *videodata)
{
HKL hklnext = GetKeyboardLayout(0);
if (hklnext == videodata->ime_hkl) {
return;
}
videodata->ime_hkl = hklnext;
videodata->ime_horizontal_candidates = (PRIMLANG() == LANG_KOREAN || LANG() == LANG_CHS);
videodata->ime_candlistindexbase = (videodata->ime_hkl == CHT_HKL_DAYI) ? 0 : 1;
}
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);
ImmSetCompositionString(himc, SCS_SETSTR, TEXT(""), sizeof(TCHAR), TEXT(""), sizeof(TCHAR));
ImmNotifyIME(himc, NI_CLOSECANDIDATE, 0, 0);
ImmReleaseContext(videodata->ime_hwnd_current, himc);
IME_SendClearComposition(videodata);
}
static void IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
{
LONG length;
DWORD dwLang = ((DWORD_PTR)videodata->ime_hkl & 0xffff);
videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
videodata->ime_selected_start = 0;
videodata->ime_selected_length = 0;
SDL_DebugIMELog("Cursor = %d", videodata->ime_cursor);
length = ImmGetCompositionStringW(himc, string, NULL, 0);
if (length > 0 && videodata->ime_composition_length < length) {
SDL_free(videodata->ime_composition);
videodata->ime_composition = (WCHAR *)SDL_malloc(length + sizeof(WCHAR));
videodata->ime_composition_length = length;
}
length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, videodata->ime_composition_length);
if (length < 0) {
length = 0;
}
length /= sizeof(WCHAR);
if ((dwLang == LANG_CHT || dwLang == LANG_CHS) &&
videodata->ime_cursor > 0 &&
videodata->ime_cursor < (int)(videodata->ime_composition_length / sizeof(WCHAR)) &&
(videodata->ime_composition[0] == 0x3000 || videodata->ime_composition[0] == 0x0020)) {
for (int i = videodata->ime_cursor + 1; i < length; ++i) {
videodata->ime_composition[i - 1] = videodata->ime_composition[i];
}
--length;
}
videodata->ime_composition[length] = 0;
length = ImmGetCompositionStringW(himc, GCS_COMPATTR, NULL, 0);
if (length > 0) {
Uint8 *attributes = (Uint8 *)SDL_malloc(length);
if (attributes) {
int start = 0;
int end = 0;
length = ImmGetCompositionString(himc, GCS_COMPATTR, attributes, length);
if (length < 0) {
length = 0;
}
for (LONG i = 0; i < length; ++i) {
SDL_DebugIMELog("attrib[%d] = %d", i, attributes[i]);
}
for (start = 0; start < length; ++start) {
if (attributes[start] == ATTR_TARGET_CONVERTED || attributes[start] == ATTR_TARGET_NOTCONVERTED) {
break;
}
}
for (end = start; end < length; ++end) {
if (attributes[end] != ATTR_TARGET_CONVERTED && attributes[end] != ATTR_TARGET_NOTCONVERTED) {
break;
}
}
if (end > start) {
videodata->ime_selected_start = start;
videodata->ime_selected_length = end - start;
}
SDL_free(attributes);
}
}
}
static void IME_SendInputEvent(SDL_VideoData *videodata)
{
char *s = 0;
s = WIN_StringToUTF8W(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 = NULL;
WCHAR *buffer = NULL;
size_t size = videodata->ime_composition_length;
if (videodata->ime_readingstring[0]) {
size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);
size += sizeof(videodata->ime_readingstring);
buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR));
if (!buffer) {
return;
}
buffer[0] = 0;
SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
SDL_wcslcat(buffer, videodata->ime_readingstring, size);
SDL_wcslcat(buffer, &videodata->ime_composition[len], size);
} else {
buffer = (WCHAR *)SDL_malloc(size + sizeof(WCHAR));
if (!buffer) {
return;
}
buffer[0] = 0;
SDL_wcslcpy(buffer, videodata->ime_composition, size);
}
s = WIN_StringToUTF8W(buffer);
if (s) {
if (videodata->ime_readingstring[0]) {
SDL_SendEditingText(s, videodata->ime_cursor, (int)SDL_wcslen(videodata->ime_readingstring));
} else if (videodata->ime_cursor == videodata->ime_selected_start) {
SDL_SendEditingText(s, videodata->ime_selected_start, videodata->ime_selected_length);
} else {
SDL_SendEditingText(s, videodata->ime_cursor, 0);
}
if (*s) {
videodata->ime_needs_clear_composition = true;
}
SDL_free(s);
}
SDL_free(buffer);
}
static void IME_SendClearComposition(SDL_VideoData *videodata)
{
if (videodata->ime_needs_clear_composition) {
SDL_SendEditingText("", 0, 0);
videodata->ime_needs_clear_composition = false;
}
}
static bool IME_OpenCandidateList(SDL_VideoData *videodata)
{
videodata->ime_candidates_open = true;
videodata->ime_candcount = 0;
return true;
}
static void IME_AddCandidate(SDL_VideoData *videodata, UINT i, LPCWSTR candidate)
{
if (videodata->ime_candidates[i]) {
SDL_free(videodata->ime_candidates[i]);
videodata->ime_candidates[i] = NULL;
}
SDL_COMPILE_TIME_ASSERT(IME_CANDIDATE_INDEXING_REQUIRES, MAX_CANDLIST == 10);
char *candidate_utf8 = WIN_StringToUTF8W(candidate);
SDL_asprintf(&videodata->ime_candidates[i], "%d %s", ((i + videodata->ime_candlistindexbase) % 10), candidate_utf8);
SDL_free(candidate_utf8);
videodata->ime_candcount = (i + 1);
}
static void IME_SendCandidateList(SDL_VideoData *videodata)
{
SDL_SendEditingTextCandidates(videodata->ime_candidates, videodata->ime_candcount, videodata->ime_candsel, videodata->ime_horizontal_candidates);
}
static void IME_CloseCandidateList(SDL_VideoData *videodata)
{
videodata->ime_candidates_open = false;
if (videodata->ime_candcount > 0) {
for (int i = 0; i < videodata->ime_candcount; ++i) {
SDL_free(videodata->ime_candidates[i]);
videodata->ime_candidates[i] = NULL;
}
videodata->ime_candcount = 0;
SDL_SendEditingTextCandidates(NULL, 0, -1, false);
}
}
static void IME_GetCandidateList(SDL_VideoData *videodata, HWND hwnd)
{
HIMC himc;
DWORD size;
LPCANDIDATELIST cand_list;
bool has_candidates = false;
himc = ImmGetContext(hwnd);
if (himc) {
size = ImmGetCandidateListW(himc, 0, NULL, 0);
if (size != 0) {
cand_list = (LPCANDIDATELIST)SDL_malloc(size);
if (cand_list != NULL) {
size = ImmGetCandidateListW(himc, 0, cand_list, size);
if (size != 0) {
if (IME_OpenCandidateList(videodata)) {
UINT i, j;
UINT page_start = 0;
UINT page_size = 0;
videodata->ime_candsel = cand_list->dwSelection;
if (LANG() == LANG_CHS && IME_GetId(videodata, 0)) {
const UINT maxcandchar = 18;
size_t cchars = 0;
for (i = 0; i < cand_list->dwCount; ++i) {
size_t 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;
}
}
page_size = i - page_start;
} else {
page_size = SDL_min(cand_list->dwPageSize == 0 ? MAX_CANDLIST : cand_list->dwPageSize, MAX_CANDLIST);
page_start = (cand_list->dwSelection / page_size) * page_size;
}
for (i = page_start, j = 0; (DWORD)i < cand_list->dwCount && j < page_size; i++, j++) {
LPCWSTR candidate = (LPCWSTR)((DWORD_PTR)cand_list + cand_list->dwOffset[i]);
IME_AddCandidate(videodata, j, candidate);
}
has_candidates = true;
IME_SendCandidateList(videodata);
}
}
SDL_free(cand_list);
}
}
ImmReleaseContext(hwnd, himc);
}
if (!has_candidates) {
IME_CloseCandidateList(videodata);
}
}
bool WIN_HandleIMEMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoData *videodata)
{
bool trap = false;
HIMC himc = 0;
if (msg == WM_IME_SETCONTEXT) {
SDL_DebugIMELog("WM_IME_SETCONTEXT");
LPARAM element_mask;
if (videodata->ime_internal_composition && videodata->ime_internal_candidates) {
element_mask = 0;
} else {
element_mask = ISC_SHOWUIALL;
if (videodata->ime_internal_composition) {
element_mask &= ~ISC_SHOWUICOMPOSITIONWINDOW;
}
if (videodata->ime_internal_candidates) {
element_mask &= ~ISC_SHOWUIALLCANDIDATEWINDOW;
}
}
*lParam &= element_mask;
return false;
} else if (msg == WM_IME_STARTCOMPOSITION) {
SDL_DebugIMELog("WM_IME_STARTCOMPOSITION");
if (videodata->ime_internal_composition) {
return true;
}
}
if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled) {
return false;
}
switch (msg) {
case WM_KEYDOWN:
if (wParam == VK_PROCESSKEY) {
SDL_DebugIMELog("WM_KEYDOWN VK_PROCESSKEY");
trap = true;
} else {
SDL_DebugIMELog("WM_KEYDOWN normal");
}
break;
case WM_SYSKEYDOWN:
if (wParam == VK_PROCESSKEY) {
SDL_DebugIMELog("WM_SYSKEYDOWN VK_PROCESSKEY");
trap = true;
} else {
SDL_DebugIMELog("WM_SYSKEYDOWN normal");
}
break;
case WM_INPUTLANGCHANGE:
SDL_DebugIMELog("WM_INPUTLANGCHANGE");
IME_InputLangChanged(videodata);
break;
case WM_IME_COMPOSITION:
SDL_DebugIMELog("WM_IME_COMPOSITION %x", lParam);
if (videodata->ime_internal_composition) {
trap = true;
himc = ImmGetContext(hwnd);
if (*lParam & GCS_RESULTSTR) {
SDL_DebugIMELog("GCS_RESULTSTR");
IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
IME_SendClearComposition(videodata);
IME_SendInputEvent(videodata);
}
if (*lParam & GCS_COMPSTR) {
SDL_DebugIMELog("GCS_COMPSTR");
videodata->ime_readingstring[0] = 0;
IME_GetCompositionString(videodata, himc, GCS_COMPSTR);
IME_SendEditingEvent(videodata);
}
ImmReleaseContext(hwnd, himc);
}
break;
case WM_IME_ENDCOMPOSITION:
SDL_DebugIMELog("WM_IME_ENDCOMPOSITION");
if (videodata->ime_internal_composition) {
trap = true;
videodata->ime_composition[0] = 0;
videodata->ime_readingstring[0] = 0;
videodata->ime_cursor = 0;
videodata->ime_selected_start = 0;
videodata->ime_selected_length = 0;
IME_SendClearComposition(videodata);
}
break;
case WM_IME_NOTIFY:
SDL_DebugIMELog("WM_IME_NOTIFY %x", wParam);
switch (wParam) {
case IMN_SETCOMPOSITIONWINDOW:
SDL_DebugIMELog("IMN_SETCOMPOSITIONWINDOW");
break;
case IMN_SETCOMPOSITIONFONT:
SDL_DebugIMELog("IMN_SETCOMPOSITIONFONT");
break;
case IMN_SETCANDIDATEPOS:
SDL_DebugIMELog("IMN_SETCANDIDATEPOS");
break;
case IMN_SETCONVERSIONMODE:
case IMN_SETOPENSTATUS:
SDL_DebugIMELog("%s", wParam == IMN_SETCONVERSIONMODE ? "IMN_SETCONVERSIONMODE" : "IMN_SETOPENSTATUS");
IME_UpdateInputLocale(videodata);
break;
case IMN_OPENCANDIDATE:
case IMN_CHANGECANDIDATE:
SDL_DebugIMELog("%s", wParam == IMN_OPENCANDIDATE ? "IMN_OPENCANDIDATE" : "IMN_CHANGECANDIDATE");
if (videodata->ime_internal_candidates) {
trap = true;
videodata->ime_update_candidates = true;
}
break;
case IMN_CLOSECANDIDATE:
SDL_DebugIMELog("IMN_CLOSECANDIDATE");
if (videodata->ime_internal_candidates) {
trap = true;
videodata->ime_update_candidates = false;
IME_CloseCandidateList(videodata);
}
break;
case IMN_PRIVATE:
{
DWORD dwId = IME_GetId(videodata, 0);
SDL_DebugIMELog("IMN_PRIVATE %u", dwId);
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 = 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 = true;
}
break;
}
} break;
default:
trap = true;
break;
}
break;
}
return trap;
}
void WIN_UpdateIMECandidates(SDL_VideoDevice *_this)
{
SDL_VideoData *videodata = _this->internal;
if (videodata->ime_update_candidates) {
IME_GetCandidateList(videodata, videodata->ime_hwnd_current);
videodata->ime_update_candidates = false;
}
}
#endif
#endif