Add text input focus API plumbing

Introduce glfwSetTextInputFocus(window, focused), internal state and a
platform hook for explicit application text input focus.

Track whether the application has explicitly called the API with
textInputFocusInitialized, so existing applications keep legacy platform
behavior until they opt into explicit text input focus.

This follows the text input focus direction from the IME support
discussions in clear-code/glfw#5 and clear-code/glfw#7, without changing
platform behavior yet.
This commit is contained in:
Takuro Ashie 2026-06-21 16:24:32 +09:00
parent de0574f1ea
commit 571d06738d
19 changed files with 141 additions and 0 deletions

View file

@ -276,6 +276,47 @@ In this case, the preedit callback also works on X11. However, on-the-spot styl
X11 is unstable, so it is not recommended.
@subsection input_text_focus Text input focus
Text input focus describes whether the application is currently in a text input
context. Examples include a chat box, text field, search box, rename dialog or
editor. This is distinct from native window focus.
This is useful for applications that render their own user interface inside a
single native window, such as games and browsers. In these applications, the
native window may remain focused while the application switches between
gameplay, menus, chat input, search fields, editors and other UI elements. The
application is the only component that reliably knows when text input is
expected.
Use @ref glfwSetTextInputFocus to tell GLFW when the application enters or
leaves a text input context:
@code
glfwSetTextInputFocus(window, GLFW_TRUE); // Text field became active
glfwSetTextInputFocus(window, GLFW_FALSE); // Text field lost focus
@endcode
This function does not turn the IME on or off. It expresses whether GLFW should
route text input through the platform text input or IME path for this window.
It does not request an input language change, force a specific IME state or
force a specific input source.
For compatibility, GLFW preserves the previous platform text input behavior for
applications that never call @ref glfwSetTextInputFocus. Once an application
calls it for a window, that window enters explicit text input focus management.
The application is then responsible for calling it with `GLFW_TRUE` when text
input begins and with `GLFW_FALSE` when text input ends.
Applications that opt into explicit text input focus management should set the
initial state explicitly, usually to `GLFW_FALSE`, after window creation. They
should then set it to `GLFW_TRUE` only while a text input widget is active.
Individual platforms map this abstraction to their native text input
mechanisms. Some platforms may also cancel or clear active preedit text when
text input focus is set to `GLFW_FALSE`.
@subsection input_preedit Preedit input
When inputting text with IME, the text is temporarily inputted, then conversion
@ -407,6 +448,16 @@ glfwSetInputMode(window, GLFW_IME, GLFW_TRUE);
glfwSetInputMode(window, GLFW_IME, GLFW_FALSE);
@endcode
This is related to but distinct from @ref glfwSetTextInputFocus. Text input
focus describes application intent: the application has entered or left a text
input context. `GLFW_IME` controls platform-specific IME state. Applications
should normally prefer @ref glfwSetTextInputFocus unless they specifically need
platform-dependent IME state control.
As a rule of thumb, if you think you need to enable or disable IME because a
chat box, text field, search field, rename dialog or editor gained or lost
focus, you probably want text input focus instead.
You can use the following function to clear the current preedit.
@code

View file

@ -5310,6 +5310,43 @@ GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* window, int x, int y, int
*/
GLFWAPI void glfwResetPreeditText(GLFWwindow* window);
/*! @brief Sets whether the application is in a text input context.
*
* This function tells GLFW whether the specified window is currently handling
* application text input, such as a chat box, text field, search box, rename
* dialog or editor. Text input focus is separate from native window focus.
*
* Pass `GLFW_TRUE` when the application enters a text input context and
* `GLFW_FALSE` when it leaves that context. This does not turn the IME on or
* off, switch input languages or force a specific platform input source.
*
* For compatibility, applications that never call this function keep the same
* platform text input behavior as before this API was introduced. Once this
* function is called for a window, the application is responsible for
* notifying GLFW when text input begins and ends.
*
* This function is related to but distinct from `GLFW_IME`. Applications
* should normally prefer this function unless they specifically need
* platform-dependent IME state control.
*
* @param[in] window The window whose text input focus state to set.
* @param[in] focused `GLFW_TRUE` to enter text input focus, or `GLFW_FALSE`
* to leave it.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_PLATFORM_ERROR.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref ime_support
* @sa @ref glfwSetInputMode
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwSetTextInputFocus(GLFWwindow* window, int focused);
/*! @brief Returns the preedit candidate.
*
* This function returns the text and the text-count of the preedit candidate.

View file

@ -536,6 +536,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform)
.getClipboardString = _glfwGetClipboardStringCocoa,
.updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleCocoa,
.resetPreeditText = _glfwResetPreeditTextCocoa,
.setTextInputFocus = _glfwSetTextInputFocusCocoa,
.setIMEStatus = _glfwSetIMEStatusCocoa,
.getIMEStatus = _glfwGetIMEStatusCocoa,
.initJoysticks = _glfwInitJoysticksCocoa,

View file

@ -300,6 +300,7 @@ const char* _glfwGetClipboardStringCocoa(void);
void _glfwUpdatePreeditCursorRectangleCocoa(_GLFWwindow* window);
void _glfwResetPreeditTextCocoa(_GLFWwindow* window);
void _glfwSetTextInputFocusCocoa(_GLFWwindow* window, GLFWbool focused);
void _glfwSetIMEStatusCocoa(_GLFWwindow* window, int active);
int _glfwGetIMEStatusCocoa(_GLFWwindow* window);

View file

@ -2037,6 +2037,11 @@ void _glfwResetPreeditTextCocoa(_GLFWwindow* window)
} // autoreleasepool
}
void _glfwSetTextInputFocusCocoa(_GLFWwindow* window, GLFWbool focused)
{
// TODO: Add a safe NSTextInputContext mapping without changing TIS behavior.
}
void _glfwSetIMEStatusCocoa(_GLFWwindow* window, int active)
{
@autoreleasepool {

View file

@ -1040,6 +1040,19 @@ GLFWAPI void glfwResetPreeditText(GLFWwindow* handle)
_glfw.platform.resetPreeditText(window);
}
GLFWAPI void glfwSetTextInputFocus(GLFWwindow* handle, int focused)
{
_GLFW_REQUIRE_INIT();
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
focused = focused ? GLFW_TRUE : GLFW_FALSE;
window->textInputFocusInitialized = GLFW_TRUE;
window->textInputFocus = focused;
_glfw.platform.setTextInputFocus(window, focused);
}
GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* handle, int index, int* textCount)
{
_GLFWwindow* window = (_GLFWwindow*) handle;

View file

@ -592,6 +592,12 @@ struct _GLFWwindow
GLFWbool stickyMouseButtons;
GLFWbool lockKeyMods;
GLFWbool disableMouseButtonLimit;
// Preserve legacy text input behavior for backward compatibility until
// glfwSetTextInputFocus is used for this window.
GLFWbool textInputFocusInitialized;
GLFWbool textInputFocus;
int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1];
@ -744,6 +750,7 @@ struct _GLFWplatform
const char* (*getClipboardString)(void);
void (*updatePreeditCursorRectangle)(_GLFWwindow*);
void (*resetPreeditText)(_GLFWwindow*);
void (*setTextInputFocus)(_GLFWwindow*,GLFWbool);
void (*setIMEStatus)(_GLFWwindow*,int);
int (*getIMEStatus)(_GLFWwindow*);
GLFWbool (*initJoysticks)(void);

View file

@ -57,6 +57,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform)
.getClipboardString = _glfwGetClipboardStringNull,
.updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleNull,
.resetPreeditText = _glfwResetPreeditTextNull,
.setTextInputFocus = _glfwSetTextInputFocusNull,
.setIMEStatus = _glfwSetIMEStatusNull,
.getIMEStatus = _glfwGetIMEStatusNull,
.initJoysticks = _glfwInitJoysticksNull,

View file

@ -272,6 +272,7 @@ int _glfwGetKeyScancodeNull(int key);
void _glfwUpdatePreeditCursorRectangleNull(_GLFWwindow* window);
void _glfwResetPreeditTextNull(_GLFWwindow* window);
void _glfwSetTextInputFocusNull(_GLFWwindow* window, GLFWbool focused);
void _glfwSetIMEStatusNull(_GLFWwindow* window, int active);
int _glfwGetIMEStatusNull(_GLFWwindow* window);

View file

@ -559,6 +559,10 @@ void _glfwResetPreeditTextNull(_GLFWwindow* window)
{
}
void _glfwSetTextInputFocusNull(_GLFWwindow* window, GLFWbool focused)
{
}
void _glfwSetIMEStatusNull(_GLFWwindow* window, int active)
{
}

View file

@ -621,6 +621,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform)
.getClipboardString = _glfwGetClipboardStringWin32,
.updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleWin32,
.resetPreeditText = _glfwResetPreeditTextWin32,
.setTextInputFocus = _glfwSetTextInputFocusWin32,
.setIMEStatus = _glfwSetIMEStatusWin32,
.getIMEStatus = _glfwGetIMEStatusWin32,
.initJoysticks = _glfwInitJoysticksWin32,

View file

@ -578,6 +578,7 @@ const char* _glfwGetClipboardStringWin32(void);
void _glfwUpdatePreeditCursorRectangleWin32(_GLFWwindow* window);
void _glfwResetPreeditTextWin32(_GLFWwindow* window);
void _glfwSetTextInputFocusWin32(_GLFWwindow* window, GLFWbool focused);
void _glfwSetIMEStatusWin32(_GLFWwindow* window, int active);
int _glfwGetIMEStatusWin32(_GLFWwindow* window);

View file

@ -2873,6 +2873,11 @@ void _glfwResetPreeditTextWin32(_GLFWwindow* window)
ImmReleaseContext(hWnd, hIMC);
}
void _glfwSetTextInputFocusWin32(_GLFWwindow* window, GLFWbool focused)
{
// TODO: Add safe IMM/TSF text input focus plumbing without changing IME status.
}
void _glfwSetIMEStatusWin32(_GLFWwindow* window, int active)
{
HWND hWnd = window->win32.handle;

View file

@ -468,6 +468,7 @@ GLFWbool _glfwConnectWayland(int platformID, _GLFWplatform* platform)
.getClipboardString = _glfwGetClipboardStringWayland,
.updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleWayland,
.resetPreeditText = _glfwResetPreeditTextWayland,
.setTextInputFocus = _glfwSetTextInputFocusWayland,
.setIMEStatus = _glfwSetIMEStatusWayland,
.getIMEStatus = _glfwGetIMEStatusWayland,
#if defined(GLFW_BUILD_LINUX_JOYSTICK)

View file

@ -720,6 +720,7 @@ const char* _glfwGetClipboardStringWayland(void);
void _glfwUpdatePreeditCursorRectangleWayland(_GLFWwindow* window);
void _glfwResetPreeditTextWayland(_GLFWwindow* window);
void _glfwSetTextInputFocusWayland(_GLFWwindow* window, GLFWbool focused);
void _glfwSetIMEStatusWayland(_GLFWwindow* window, int active);
int _glfwGetIMEStatusWayland(_GLFWwindow* window);

View file

@ -3943,6 +3943,11 @@ void _glfwResetPreeditTextWayland(_GLFWwindow* window)
{
}
void _glfwSetTextInputFocusWayland(_GLFWwindow* window, GLFWbool focused)
{
// TODO: Wire this to text-input-v3 enable/disable or focus integration.
}
void _glfwSetIMEStatusWayland(_GLFWwindow* window, int active)
{
}

View file

@ -1190,6 +1190,7 @@ GLFWbool _glfwConnectX11(int platformID, _GLFWplatform* platform)
.getClipboardString = _glfwGetClipboardStringX11,
.updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleX11,
.resetPreeditText = _glfwResetPreeditTextX11,
.setTextInputFocus = _glfwSetTextInputFocusX11,
.setIMEStatus = _glfwSetIMEStatusX11,
.getIMEStatus = _glfwGetIMEStatusX11,
#if defined(GLFW_BUILD_LINUX_JOYSTICK)

View file

@ -981,6 +981,7 @@ const char* _glfwGetClipboardStringX11(void);
void _glfwUpdatePreeditCursorRectangleX11(_GLFWwindow* window);
void _glfwResetPreeditTextX11(_GLFWwindow* window);
void _glfwSetTextInputFocusX11(_GLFWwindow* window, GLFWbool focused);
void _glfwSetIMEStatusX11(_GLFWwindow* window, int active);
int _glfwGetIMEStatusX11(_GLFWwindow* window);

View file

@ -3429,6 +3429,10 @@ void _glfwSetIMEStatusX11(_GLFWwindow* window, int active)
XUnsetICFocus(ic);
}
void _glfwSetTextInputFocusX11(_GLFWwindow* window, GLFWbool focused)
{
}
int _glfwGetIMEStatusX11(_GLFWwindow* window)
{
if (!window->x11.ic)