From 5a079a2d114f96d4847d1ee305d5b7c16eeec50e Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 27 Dec 2025 12:03:39 -0800 Subject: Initial commit --- .../src/video/windows/SDL_windowskeyboard.c | 1145 ++++++++++++++++++++ 1 file changed, 1145 insertions(+) create mode 100644 contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c (limited to 'contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c') diff --git a/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c new file mode 100644 index 0000000..75e8ad4 --- /dev/null +++ b/contrib/SDL-3.2.8/src/video/windows/SDL_windowskeyboard.c @@ -0,0 +1,1145 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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_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 +#include + +#ifndef SDL_DISABLE_WINDOWS_IME +#if 0 +#define SDL_DebugIMELog SDL_Log +#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 // !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 + +// Alphabetic scancodes for PC keyboards +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 // !SDL_DISABLE_WINDOWS_IME + + WIN_UpdateKeymap(false); + + SDL_SetScancodeName(SDL_SCANCODE_APPLICATION, "Menu"); + SDL_SetScancodeName(SDL_SCANCODE_LGUI, "Left Windows"); + SDL_SetScancodeName(SDL_SCANCODE_RGUI, "Right Windows"); + + // Are system caps/num/scroll lock active? Set our state to match. + 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_UpdateKeymap(bool send_event) +{ + SDL_Scancode scancode; + SDL_Keymap *keymap; + BYTE keyboardState[256] = { 0 }; + WCHAR buffer[16]; + 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(); + + keymap = SDL_CreateKeymap(); + + 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; + + // Make sure this scancode is a valid character scancode + scancode = windows_scancode_table[i]; + if (scancode == SDL_SCANCODE_UNKNOWN || + scancode == SDL_SCANCODE_DELETE || + (SDL_GetKeymapKeycode(NULL, scancode, SDL_KMOD_NONE) & SDLK_SCANCODE_MASK)) { + + // The Colemak mapping swaps Backspace and CapsLock + 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; + } + + // Unpack the single byte index to make the scan code. + sc = MAKEWORD(i & 0x7f, (i & 0x80) ? 0xe0 : 0x00); + vk = LOBYTE(MapVirtualKey(sc, MAPVK_VSC_TO_VK)); + if (!vk) { + continue; + } + + // Update the keyboard state for the modifiers + 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; + + // Convert UTF-16 to UTF-32 code points + ch = (Uint32 *)SDL_iconv_string("UTF-32LE", "UTF-16LE", (const char *)buffer, (SDL_abs(result) + 1) * sizeof(WCHAR)); + if (ch) { + /* Windows keyboard layouts can emit several UTF-32 code points on a single key press. + * Use since we cannot fit into single SDL_Keycode value in SDL keymap. + * See https://kbdlayout.info/features/ligatures for a list of such keys. */ + SDL_SetKeymapEntry(keymap, scancode, mods[m], ch[1] == 0 ? ch[0] : 0xfffd); + SDL_free(ch); + } else { + // The default keymap doesn't have any SDL_KMOD_MODE entries, so we don't need to override them + if (!(mods[m] & SDL_KMOD_MODE)) { + SDL_SetKeymapEntry(keymap, scancode, mods[m], SDLK_UNKNOWN); + } + } + + if (result < 0) { + WIN_ResetDeadKeys(); + } + } + } + + SDL_SetKeymap(keymap, send_event); +} + +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 // !SDL_DISABLE_WINDOWS_IME +} + +void WIN_ResetDeadKeys(void) +{ + /* + if a deadkey has been typed, but not the next character (which the deadkey might modify), + this tries to undo the effect pressing the deadkey. + see: http://archives.miloush.net/michkap/archive/2006/09/10/748775.html + */ + 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) { + // the keyboard doesn't have this key + return; + } + + for (i = 0; i < 5; i++) { + result = ToUnicode(vk, sc, keyboardState, buffer, 16, 0); + if (result > 0) { + // success + 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 // !SDL_DISABLE_WINDOWS_IME + + 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 // !SDL_DISABLE_WINDOWS_IME + + 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; + } + /* *INDENT-OFF* */ // clang-format off + 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"); + /* *INDENT-ON* */ // clang-format on + + 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; + DWORD dwVerHandle = 0; + LPVOID lpVerBuffer = 0; + LPVOID lpVerData = 0; + UINT cbVerData = 0; + char szTemp[256]; + HKL hkl = 0; + DWORD dwLang = 0; + SDL_assert(uIndex < sizeof(dwRet) / sizeof(dwRet[0])); + + hkl = videodata->ime_hkl; + if (hklprev == hkl) { + return dwRet[uIndex]; + } + hklprev = hkl; + + SDL_assert(uIndex == 0); + dwLang = ((DWORD_PTR)hkl & 0xffff); + // FIXME: What does this do? + 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, &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[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; + } + + /* *INDENT-OFF* */ // clang-format off + videodata->GetReadingString = (UINT (WINAPI *)(HIMC, UINT, LPWSTR, PINT, BOOL*, PUINT)) + SDL_LoadFunction(hime, "GetReadingString"); + videodata->ShowReadingWindow = (BOOL (WINAPI *)(HIMC, BOOL)) + SDL_LoadFunction(hime, "ShowReadingWindow"); + /* *INDENT-ON* */ // clang-format on + + 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) { + if (videodata->ime_composition) { + 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)) { + // Traditional Chinese IMEs add a placeholder U+3000 + // Simplified Chinese IMEs seem to add a placeholder U+0020 sometimes + 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; + } + + 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_INPUTLANGCHANGE: + SDL_DebugIMELog("WM_INPUTLANGCHANGE"); + IME_InputLangChanged(videodata); + break; + case WM_IME_STARTCOMPOSITION: + SDL_DebugIMELog("WM_IME_STARTCOMPOSITION"); + if (videodata->ime_internal_composition) { + trap = true; + } + 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 // SDL_DISABLE_WINDOWS_IME + +#endif // SDL_VIDEO_DRIVER_WINDOWS -- cgit v1.2.3