mirror of
https://github.com/glfw/glfw
synced 2026-09-26 16:18:21 +03:00
Win32: Implement text input focus with IMM
When explicit text input focus is inactive, keep GLFW from processing its own WM_IME_* composition, preedit, candidate and IME status paths while preserving normal key input. On Win32, gating GLFW message handling is not enough to prevent native IMM candidate UI from appearing. Temporarily associate a NULL HIMC with the window while explicit text input focus is out, then restore the saved HIMC when focus returns. Cancel and clear active preedit/candidate state as focus-out cleanup. After restoring the HIMC, reapply the preedit cursor rectangle so candidate UI uses the latest location.
This commit is contained in:
parent
44ec6b2467
commit
a8819c7924
3 changed files with 99 additions and 8 deletions
|
|
@ -172,6 +172,8 @@ static GLFWbool loadLibraries(void)
|
|||
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetCompositionStringW");
|
||||
_glfw.win32.imm32.ImmGetContext_ = (PFN_ImmGetContext)
|
||||
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetContext");
|
||||
_glfw.win32.imm32.ImmAssociateContext_ = (PFN_ImmAssociateContext)
|
||||
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmAssociateContext");
|
||||
_glfw.win32.imm32.ImmGetConversionStatus_ = (PFN_ImmGetConversionStatus)
|
||||
_glfwPlatformGetModuleSymbol(_glfw.win32.imm32.instance, "ImmGetConversionStatus");
|
||||
_glfw.win32.imm32.ImmGetDescriptionW_ = (PFN_ImmGetDescriptionW)
|
||||
|
|
|
|||
|
|
@ -263,6 +263,7 @@ typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLO
|
|||
typedef DWORD (WINAPI * PFN_ImmGetCandidateListW)(HIMC,DWORD,LPCANDIDATELIST,DWORD);
|
||||
typedef LONG (WINAPI * PFN_ImmGetCompositionStringW)(HIMC,DWORD,LPVOID,DWORD);
|
||||
typedef HIMC (WINAPI * PFN_ImmGetContext)(HWND);
|
||||
typedef HIMC (WINAPI * PFN_ImmAssociateContext)(HWND,HIMC);
|
||||
typedef BOOL (WINAPI * PFN_ImmGetConversionStatus)(HIMC,LPDWORD,LPDWORD);
|
||||
typedef UINT (WINAPI * PFN_ImmGetDescriptionW)(HKL,LPWSTR,UINT);
|
||||
typedef BOOL (WINAPI * PFN_ImmGetOpenStatus)(HIMC);
|
||||
|
|
@ -274,6 +275,7 @@ typedef BOOL (WINAPI * PFN_ImmSetOpenStatus)(HIMC,BOOL);
|
|||
#define ImmGetCandidateListW _glfw.win32.imm32.ImmGetCandidateListW_
|
||||
#define ImmGetCompositionStringW _glfw.win32.imm32.ImmGetCompositionStringW_
|
||||
#define ImmGetContext _glfw.win32.imm32.ImmGetContext_
|
||||
#define ImmAssociateContext _glfw.win32.imm32.ImmAssociateContext_
|
||||
#define ImmGetConversionStatus _glfw.win32.imm32.ImmGetConversionStatus_
|
||||
#define ImmGetDescriptionW _glfw.win32.imm32.ImmGetDescriptionW_
|
||||
#define ImmGetOpenStatus _glfw.win32.imm32.ImmGetOpenStatus_
|
||||
|
|
@ -380,6 +382,7 @@ typedef struct _GLFWlibraryWGL
|
|||
typedef struct _GLFWwindowWin32
|
||||
{
|
||||
HWND handle;
|
||||
HIMC textInputContext;
|
||||
HICON bigIcon;
|
||||
HICON smallIcon;
|
||||
|
||||
|
|
@ -473,6 +476,7 @@ typedef struct _GLFWlibraryWin32
|
|||
PFN_ImmGetCandidateListW ImmGetCandidateListW_;
|
||||
PFN_ImmGetCompositionStringW ImmGetCompositionStringW_;
|
||||
PFN_ImmGetContext ImmGetContext_;
|
||||
PFN_ImmAssociateContext ImmAssociateContext_;
|
||||
PFN_ImmGetConversionStatus ImmGetConversionStatus_;
|
||||
PFN_ImmGetDescriptionW ImmGetDescriptionW_;
|
||||
PFN_ImmGetOpenStatus ImmGetOpenStatus_;
|
||||
|
|
|
|||
|
|
@ -834,6 +834,11 @@ static void clearImmPreedit(_GLFWwindow* window)
|
|||
_glfwInputPreedit(window);
|
||||
}
|
||||
|
||||
static GLFWbool textInputFocusDisabled(_GLFWwindow* window)
|
||||
{
|
||||
return window->textInputFocusInitialized && !window->textInputFocus;
|
||||
}
|
||||
|
||||
// Commit the result texts of Imm32 to character-callback
|
||||
//
|
||||
static GLFWbool commitImmResultStr(_GLFWwindow* window)
|
||||
|
|
@ -904,6 +909,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||
{
|
||||
case WM_IME_SETCONTEXT:
|
||||
{
|
||||
if (textInputFocusDisabled(window))
|
||||
break;
|
||||
|
||||
// To draw preedit text by an application side
|
||||
if (lParam & ISC_SHOWUICOMPOSITIONWINDOW)
|
||||
lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;
|
||||
|
|
@ -1146,6 +1154,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||
|
||||
case WM_IME_COMPOSITION:
|
||||
{
|
||||
if (textInputFocusDisabled(window))
|
||||
return 0;
|
||||
|
||||
if (lParam & (GCS_RESULTSTR | GCS_COMPSTR))
|
||||
{
|
||||
if (lParam & GCS_RESULTSTR)
|
||||
|
|
@ -1159,6 +1170,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||
|
||||
case WM_IME_ENDCOMPOSITION:
|
||||
{
|
||||
if (textInputFocusDisabled(window))
|
||||
return 0;
|
||||
|
||||
clearImmPreedit(window);
|
||||
// Usually clearing candidates in IMN_CLOSECANDIDATE is sufficient.
|
||||
// However, some IME need it here, e.g. Google Japanese Input.
|
||||
|
|
@ -1168,6 +1182,9 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
|
|||
|
||||
case WM_IME_NOTIFY:
|
||||
{
|
||||
if (textInputFocusDisabled(window))
|
||||
return 0;
|
||||
|
||||
switch (wParam)
|
||||
{
|
||||
case IMN_SETOPENSTATUS:
|
||||
|
|
@ -1955,6 +1972,9 @@ void _glfwDestroyWindowWin32(_GLFWwindow* window)
|
|||
|
||||
if (window->win32.handle)
|
||||
{
|
||||
if (window->win32.textInputContext)
|
||||
ImmAssociateContext(window->win32.handle, window->win32.textInputContext);
|
||||
|
||||
RemovePropW(window->win32.handle, L"GLFW");
|
||||
DestroyWindow(window->win32.handle);
|
||||
window->win32.handle = NULL;
|
||||
|
|
@ -2857,6 +2877,9 @@ void _glfwUpdatePreeditCursorRectangleWin32(_GLFWwindow* window)
|
|||
int h = preedit->cursorHeight;
|
||||
|
||||
COMPOSITIONFORM areaRect = { CFS_RECT, { x, y }, { x, y, x + w, y + h } };
|
||||
if (!hIMC)
|
||||
return;
|
||||
|
||||
ImmSetCompositionWindow(hIMC, &areaRect);
|
||||
|
||||
CANDIDATEFORM excludeRect = { 0, CFS_EXCLUDE, { x, y }, { x, y, x + w, y + h } };
|
||||
|
|
@ -2869,29 +2892,91 @@ void _glfwResetPreeditTextWin32(_GLFWwindow* window)
|
|||
{
|
||||
HWND hWnd = window->win32.handle;
|
||||
HIMC hIMC = ImmGetContext(hWnd);
|
||||
ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
GLFWbool releaseContext = GLFW_TRUE;
|
||||
|
||||
if (!hIMC && window->win32.textInputContext)
|
||||
{
|
||||
hIMC = window->win32.textInputContext;
|
||||
releaseContext = GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (hIMC)
|
||||
{
|
||||
ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
|
||||
if (releaseContext)
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
}
|
||||
|
||||
clearImmPreedit(window);
|
||||
clearImmCandidate(window);
|
||||
}
|
||||
|
||||
void _glfwSetTextInputFocusWin32(_GLFWwindow* window, GLFWbool focused)
|
||||
{
|
||||
// TODO: Add safe IMM/TSF text input focus plumbing without changing IME status.
|
||||
if (focused)
|
||||
{
|
||||
if (window->win32.textInputContext)
|
||||
{
|
||||
ImmAssociateContext(window->win32.handle,
|
||||
window->win32.textInputContext);
|
||||
window->win32.textInputContext = NULL;
|
||||
_glfwUpdatePreeditCursorRectangleWin32(window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwResetPreeditTextWin32(window);
|
||||
|
||||
if (!window->win32.textInputContext)
|
||||
{
|
||||
window->win32.textInputContext =
|
||||
ImmAssociateContext(window->win32.handle, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwSetIMEStatusWin32(_GLFWwindow* window, int active)
|
||||
{
|
||||
HWND hWnd = window->win32.handle;
|
||||
HIMC hIMC = ImmGetContext(hWnd);
|
||||
HIMC hIMC = window->win32.textInputContext;
|
||||
GLFWbool releaseContext = GLFW_FALSE;
|
||||
|
||||
if (!hIMC)
|
||||
{
|
||||
hIMC = ImmGetContext(hWnd);
|
||||
releaseContext = GLFW_TRUE;
|
||||
}
|
||||
|
||||
if (!hIMC)
|
||||
return;
|
||||
|
||||
ImmSetOpenStatus(hIMC, active ? TRUE : FALSE);
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
|
||||
if (releaseContext)
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
}
|
||||
|
||||
int _glfwGetIMEStatusWin32(_GLFWwindow* window)
|
||||
{
|
||||
HWND hWnd = window->win32.handle;
|
||||
HIMC hIMC = ImmGetContext(hWnd);
|
||||
BOOL result = ImmGetOpenStatus(hIMC);
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
HIMC hIMC = window->win32.textInputContext;
|
||||
GLFWbool releaseContext = GLFW_FALSE;
|
||||
BOOL result;
|
||||
|
||||
if (!hIMC)
|
||||
{
|
||||
hIMC = ImmGetContext(hWnd);
|
||||
releaseContext = GLFW_TRUE;
|
||||
}
|
||||
|
||||
if (!hIMC)
|
||||
return GLFW_FALSE;
|
||||
|
||||
result = ImmGetOpenStatus(hIMC);
|
||||
|
||||
if (releaseContext)
|
||||
ImmReleaseContext(hWnd, hIMC);
|
||||
|
||||
return result ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue