diff --git a/docs/input.md b/docs/input.md index a502ffa9f6..1880f3f8f4 100644 --- a/docs/input.md +++ b/docs/input.md @@ -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 @@ -1239,4 +1290,3 @@ void drop_callback(GLFWwindow* window, int count, const char** paths) The path array and its strings are only valid until the file drop callback returns, as they may have been generated specifically for that event. You need to make a deep copy of the array if you want to keep the paths. - diff --git a/include/GLFW/glfw3.h b/include/GLFW/glfw3.h index cb75ad99cd..ed36feb93b 100644 --- a/include/GLFW/glfw3.h +++ b/include/GLFW/glfw3.h @@ -5310,6 +5310,56 @@ GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* window, int x, int y, int */ GLFWAPI void glfwResetPreeditText(GLFWwindow* window); +/*! @brief Sets whether the application has text input focus. + * + * This function informs GLFW that the specified window has entered or left + * a text input context, such as a chat box, text field, search box, rename + * dialog or editor. Text input focus is separate from native window focus. + * A 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. + * + * Pass `GLFW_TRUE` when the application enters a text input context and + * `GLFW_FALSE` when it leaves that context. + * + * This function does not turn the IME on or off, switch input languages or + * force a specific platform input source. It expresses whether GLFW should + * route key and text input through the platform text input or IME path for + * this window. Individual platforms may map this abstraction differently. + * + * 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, that window enters explicit text input + * focus management and the application is responsible for notifying GLFW when + * text input begins and ends. + * Applications that opt into explicit text input focus management should set + * the initial state explicitly after window creation. + * + * This function is related to but distinct from + * `glfwSetInputMode(window, GLFW_IME, value)`. Text input focus describes + * application intent, while `GLFW_IME` controls platform-specific IME state. + * 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. @@ -6862,4 +6912,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window #endif #endif /* _glfw3_h_ */ - diff --git a/src/cocoa_init.m b/src/cocoa_init.m index c982027047..0674ebb401 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -536,6 +536,7 @@ GLFWbool _glfwConnectCocoa(int platformID, _GLFWplatform* platform) .getClipboardString = _glfwGetClipboardStringCocoa, .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleCocoa, .resetPreeditText = _glfwResetPreeditTextCocoa, + .setTextInputFocus = _glfwSetTextInputFocusCocoa, .setIMEStatus = _glfwSetIMEStatusCocoa, .getIMEStatus = _glfwGetIMEStatusCocoa, .initJoysticks = _glfwInitJoysticksCocoa, @@ -723,4 +724,3 @@ void _glfwTerminateCocoa(void) } #endif // _GLFW_COCOA - diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index fff4178b15..44a620bd72 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -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); @@ -332,4 +333,3 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); void _glfwDestroyContextNSGL(_GLFWwindow* window); - diff --git a/src/cocoa_window.m b/src/cocoa_window.m index d894b4a4e9..1caa41bfb4 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -573,7 +573,14 @@ - (void)keyDown:(NSEvent *)event if (![self hasMarkedText]) _glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods); - [self interpretKeyEvents:@[event]]; + if (!window->textInputFocusExplicit || window->textInputFocus) + [self interpretKeyEvents:@[event]]; + else + { + NSString* characters = [event characters]; + if (characters) + [self insertText:characters replacementRange:[self selectedRange]]; + } } - (void)flagsChanged:(NSEvent *)event @@ -2037,6 +2044,12 @@ void _glfwResetPreeditTextCocoa(_GLFWwindow* window) } // autoreleasepool } +void _glfwSetTextInputFocusCocoa(_GLFWwindow* window, GLFWbool focused) +{ + if (!focused) + _glfwResetPreeditTextCocoa(window); +} + void _glfwSetIMEStatusCocoa(_GLFWwindow* window, int active) { @autoreleasepool { @@ -2311,4 +2324,3 @@ GLFWAPI id glfwGetCocoaView(GLFWwindow* handle) } #endif // _GLFW_COCOA - diff --git a/src/input.c b/src/input.c index 4507f22e97..23391ad5f5 100644 --- a/src/input.c +++ b/src/input.c @@ -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->textInputFocusExplicit = GLFW_TRUE; + window->textInputFocus = focused; + _glfw.platform.setTextInputFocus(window, focused); +} + GLFWAPI unsigned int* glfwGetPreeditCandidate(GLFWwindow* handle, int index, int* textCount) { _GLFWwindow* window = (_GLFWwindow*) handle; @@ -1647,4 +1660,3 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void) _GLFW_REQUIRE_INIT_OR_RETURN(0); return _glfwPlatformGetTimerFrequency(); } - diff --git a/src/internal.h b/src/internal.h index ca5c280b99..378fccb266 100644 --- a/src/internal.h +++ b/src/internal.h @@ -592,6 +592,18 @@ struct _GLFWwindow GLFWbool stickyMouseButtons; GLFWbool lockKeyMods; GLFWbool disableMouseButtonLimit; + + // Requested application text input focus state. This is meaningful only + // after textInputFocusExplicit has been set by glfwSetTextInputFocus. + // It describes whether an application-drawn text input context, such as a + // chat box or text field, currently has focus inside this native window. + GLFWbool textInputFocus; + // Whether the application has opted into explicit text input focus + // management for this window. This exists so that the zero-initialized + // textInputFocus value does not disable or bypass existing platform text + // input behavior for applications that never call glfwSetTextInputFocus. + GLFWbool textInputFocusExplicit; + int cursorMode; char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1]; char keys[GLFW_KEY_LAST + 1]; @@ -744,6 +756,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); @@ -1070,4 +1083,3 @@ int _glfw_max(int a, int b); void* _glfw_calloc(size_t count, size_t size); void* _glfw_realloc(void* pointer, size_t size); void _glfw_free(void* pointer); - diff --git a/src/null_init.c b/src/null_init.c index 1cf0eccdf5..05377d7170 100644 --- a/src/null_init.c +++ b/src/null_init.c @@ -57,6 +57,7 @@ GLFWbool _glfwConnectNull(int platformID, _GLFWplatform* platform) .getClipboardString = _glfwGetClipboardStringNull, .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleNull, .resetPreeditText = _glfwResetPreeditTextNull, + .setTextInputFocus = _glfwSetTextInputFocusNull, .setIMEStatus = _glfwSetIMEStatusNull, .getIMEStatus = _glfwGetIMEStatusNull, .initJoysticks = _glfwInitJoysticksNull, @@ -266,4 +267,3 @@ void _glfwTerminateNull(void) _glfwTerminateEGL(); memset(&_glfw.null, 0, sizeof(_glfw.null)); } - diff --git a/src/null_platform.h b/src/null_platform.h index bbd3ee42e4..4b22634152 100644 --- a/src/null_platform.h +++ b/src/null_platform.h @@ -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); @@ -284,4 +285,3 @@ GLFWbool _glfwGetPhysicalDevicePresentationSupportNull(VkInstance instance, VkPh VkResult _glfwCreateWindowSurfaceNull(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface); void _glfwPollMonitorsNull(void); - diff --git a/src/null_window.c b/src/null_window.c index efadbe18fa..108b564ed4 100644 --- a/src/null_window.c +++ b/src/null_window.c @@ -559,6 +559,10 @@ void _glfwResetPreeditTextNull(_GLFWwindow* window) { } +void _glfwSetTextInputFocusNull(_GLFWwindow* window, GLFWbool focused) +{ +} + void _glfwSetIMEStatusNull(_GLFWwindow* window, int active) { } @@ -763,4 +767,3 @@ VkResult _glfwCreateWindowSurfaceNull(VkInstance instance, return err; } - diff --git a/src/win32_init.c b/src/win32_init.c index b809cfe214..dafd3d751a 100644 --- a/src/win32_init.c +++ b/src/win32_init.c @@ -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) @@ -621,6 +623,7 @@ GLFWbool _glfwConnectWin32(int platformID, _GLFWplatform* platform) .getClipboardString = _glfwGetClipboardStringWin32, .updatePreeditCursorRectangle = _glfwUpdatePreeditCursorRectangleWin32, .resetPreeditText = _glfwResetPreeditTextWin32, + .setTextInputFocus = _glfwSetTextInputFocusWin32, .setIMEStatus = _glfwSetIMEStatusWin32, .getIMEStatus = _glfwGetIMEStatusWin32, .initJoysticks = _glfwInitJoysticksWin32, @@ -741,4 +744,3 @@ void _glfwTerminateWin32(void) } #endif // _GLFW_WIN32 - diff --git a/src/win32_platform.h b/src/win32_platform.h index 4df8442f0f..a2cea9ede0 100644 --- a/src/win32_platform.h +++ b/src/win32_platform.h @@ -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,10 @@ typedef struct _GLFWlibraryWGL typedef struct _GLFWwindowWin32 { HWND handle; + // Saved HIMC while explicit text input focus is out. A NULL HIMC is + // associated with the HWND during that period to keep IMM from routing key + // input through composition and candidate UI. + HIMC textInputContext; HICON bigIcon; HICON smallIcon; @@ -473,6 +479,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_; @@ -578,6 +585,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); @@ -609,4 +617,3 @@ void _glfwTerminateWGL(void); GLFWbool _glfwCreateContextWGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig); - diff --git a/src/win32_window.c b/src/win32_window.c index 55b9185f4b..28b2bcf95e 100644 --- a/src/win32_window.c +++ b/src/win32_window.c @@ -834,6 +834,11 @@ static void clearImmPreedit(_GLFWwindow* window) _glfwInputPreedit(window); } +static GLFWbool textInputFocusDisabled(_GLFWwindow* window) +{ + return window->textInputFocusExplicit && !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; @@ -1145,6 +1153,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) @@ -1158,6 +1169,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. @@ -1167,6 +1181,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: @@ -1954,6 +1971,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; @@ -2856,6 +2876,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 } }; @@ -2868,24 +2891,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) +{ + 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; } @@ -3019,4 +3109,3 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle) } #endif // _GLFW_WIN32 - diff --git a/src/wl_init.c b/src/wl_init.c index 3c00b9df38..102c7462fc 100644 --- a/src/wl_init.c +++ b/src/wl_init.c @@ -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) @@ -1045,4 +1046,3 @@ void _glfwTerminateWayland(void) } #endif // _GLFW_WAYLAND - diff --git a/src/wl_platform.h b/src/wl_platform.h index dc4bc5deae..1b087721cc 100644 --- a/src/wl_platform.h +++ b/src/wl_platform.h @@ -718,6 +718,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); @@ -745,4 +746,3 @@ void _glfwAddSeatListenerWayland(struct wl_seat* seat); void _glfwAddDataDeviceListenerWayland(struct wl_data_device* device); GLFWbool _glfwWaitForEGLFrameWayland(_GLFWwindow* window); - diff --git a/src/wl_window.c b/src/wl_window.c index d3591d224e..e3f13d40a3 100644 --- a/src/wl_window.c +++ b/src/wl_window.c @@ -734,6 +734,11 @@ static void deactivateTextInputV1(_GLFWwindow* window) zwp_text_input_v1_deactivate(window->wl.textInputV1, _glfw.wl.seat); } +static GLFWbool textInputFocusDisabled(_GLFWwindow* window) +{ + return window->textInputFocusExplicit && !window->textInputFocus; +} + static void xdgToplevelHandleConfigure(void* userData, struct xdg_toplevel* toplevel, int32_t width, @@ -761,7 +766,8 @@ static void xdgToplevelHandleConfigure(void* userData, break; case XDG_TOPLEVEL_STATE_ACTIVATED: window->wl.pending.activated = GLFW_TRUE; - activateTextInputV1(window); + if (!textInputFocusDisabled(window)) + activateTextInputV1(window); break; } } @@ -1706,7 +1712,8 @@ static void pointerHandleButton(void* userData, // On weston, pressing the title bar will cause leave event and never emit // enter event even though back to content area by pressing mouse button // just after it. So activate it here explicitly. - activateTextInputV1(window); + if (!textInputFocusDisabled(window)) + activateTextInputV1(window); _glfw.wl.serial = serial; @@ -2399,8 +2406,13 @@ static void textInputV3Enter(void* data, struct zwp_text_input_v3* textInputV3, struct wl_surface* surface) { - zwp_text_input_v3_enable(textInputV3); - zwp_text_input_v3_commit(textInputV3); + _GLFWwindow* window = (_GLFWwindow*) data; + + if (!textInputFocusDisabled(window)) + { + zwp_text_input_v3_enable(textInputV3); + zwp_text_input_v3_commit(textInputV3); + } } static void textInputV3Reset(_GLFWwindow* window) @@ -2440,6 +2452,9 @@ static void textInputV3PreeditString(void* data, const char* cur = text; unsigned int cursorLength = 0; + if (textInputFocusDisabled(window)) + return; + preedit->textCount = 0; preedit->blockSizesCount = 0; preedit->focusedBlockIndex = 0; @@ -2513,6 +2528,9 @@ static void textInputV3CommitString(void* data, _GLFWwindow* window = (_GLFWwindow*) data; const char* cur = text; + if (textInputFocusDisabled(window)) + return; + if (!window->callbacks.character) return; @@ -2535,6 +2553,9 @@ static void textInputV3Done(void* data, uint32_t serial) { _GLFWwindow* window = (_GLFWwindow*) data; + if (textInputFocusDisabled(window)) + return; + _glfwUpdatePreeditCursorRectangleWayland(window); _glfwInputPreedit(window); } @@ -2560,7 +2581,9 @@ static void textInputV1Enter(void* data, struct wl_surface* surface) { _GLFWwindow* window = (_GLFWwindow*) data; - activateTextInputV1(window); + + if (!textInputFocusDisabled(window)) + activateTextInputV1(window); } static void textInputV1Reset(_GLFWwindow* window) @@ -2586,6 +2609,13 @@ static void textInputV1Leave(void* data, _GLFWwindow* window = (_GLFWwindow*) data; char* commitText = window->wl.textInputV1Context.commitTextOnReset; + if (textInputFocusDisabled(window)) + { + textInputV1Reset(window); + deactivateTextInputV1(window); + return; + } + textInputV3CommitString(data, NULL, commitText); textInputV1Reset(window); deactivateTextInputV1(window); @@ -2611,6 +2641,9 @@ static void textInputV1PreeditString(void* data, { _GLFWwindow* window = (_GLFWwindow*) data; + if (textInputFocusDisabled(window)) + return; + _glfw_free(window->wl.textInputV1Context.preeditText); _glfw_free(window->wl.textInputV1Context.commitTextOnReset); window->wl.textInputV1Context.preeditText = strdup(text); @@ -2637,6 +2670,9 @@ static void textInputV1PreeditCursor(void* data, const char* text = window->wl.textInputV1Context.preeditText; const char* cur = text; + if (textInputFocusDisabled(window)) + return; + preedit->caretIndex = 0; if (index <= 0 || preedit->textCount == 0) return; @@ -2659,6 +2695,9 @@ static void textInputV1CommitString(void* data, { _GLFWwindow* window = (_GLFWwindow*) data; + if (textInputFocusDisabled(window)) + return; + textInputV1Reset(window); textInputV3CommitString(data, NULL, text); } @@ -2686,6 +2725,10 @@ static void textInputV1Keysym(void* data, uint32_t state, uint32_t modifiers) { + _GLFWwindow* window = (_GLFWwindow*) data; + if (textInputFocusDisabled(window)) + return; + uint32_t scancode; // This code supports only weston-keyboard because we aren't aware @@ -3932,6 +3975,33 @@ void _glfwResetPreeditTextWayland(_GLFWwindow* window) { } +void _glfwSetTextInputFocusWayland(_GLFWwindow* window, GLFWbool focused) +{ + if (window->wl.textInputV3) + { + if (focused) + zwp_text_input_v3_enable(window->wl.textInputV3); + else + { + zwp_text_input_v3_disable(window->wl.textInputV3); + textInputV3Reset(window); + } + + zwp_text_input_v3_commit(window->wl.textInputV3); + } + else if (window->wl.textInputV1) + { + if (focused) + activateTextInputV1(window); + else + { + zwp_text_input_v1_reset(window->wl.textInputV1); + textInputV1Reset(window); + deactivateTextInputV1(window); + } + } +} + void _glfwSetIMEStatusWayland(_GLFWwindow* window, int active) { } @@ -4059,4 +4129,3 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle) } #endif // _GLFW_WAYLAND - diff --git a/src/x11_init.c b/src/x11_init.c index d2f0da0f93..720eed2216 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -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) @@ -1633,4 +1634,3 @@ void _glfwTerminateX11(void) } #endif // _GLFW_X11 - diff --git a/src/x11_platform.h b/src/x11_platform.h index 4d6693133d..2800105257 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -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); @@ -1032,4 +1033,3 @@ GLFWbool _glfwChooseVisualGLX(const _GLFWwndconfig* wndconfig, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig, Visual** visual, int* depth); - diff --git a/src/x11_window.c b/src/x11_window.c index dc6d3e5681..0915be9744 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -1961,8 +1961,11 @@ static void processEvent(XEvent *event) else if (window->cursorMode == GLFW_CURSOR_CAPTURED) captureCursor(window); - if (window->x11.ic) + if (window->x11.ic && + (!window->textInputFocusExplicit || window->textInputFocus)) + { XSetICFocus(window->x11.ic); + } _glfwInputWindowFocus(window, GLFW_TRUE); return; @@ -3429,6 +3432,24 @@ void _glfwSetIMEStatusX11(_GLFWwindow* window, int active) XUnsetICFocus(ic); } +void _glfwSetTextInputFocusX11(_GLFWwindow* window, GLFWbool focused) +{ + XIC ic = window->x11.ic; + + if (!ic) + return; + + // This API reflects the text input focus abstraction discussed in the + // clear-code/glfw#5 and clear-code/glfw#7 IME support work. + if (focused) + XSetICFocus(ic); + else + { + _glfwResetPreeditTextX11(window); + XUnsetICFocus(ic); + } +} + int _glfwGetIMEStatusX11(_GLFWwindow* window) { if (!window->x11.ic) @@ -3715,4 +3736,3 @@ GLFWAPI const char* glfwGetX11SelectionString(void) } #endif // _GLFW_X11 - diff --git a/tests/input_text.c b/tests/input_text.c index cc006f2532..a315db28cf 100644 --- a/tests/input_text.c +++ b/tests/input_text.c @@ -112,6 +112,14 @@ static int fontNum = 0; static int currentFontIndex = 0; static int currentIMEStatus = GLFW_FALSE; +enum text_input_focus_status +{ + TEXT_INPUT_FOCUS_COMPATIBLE, + TEXT_INPUT_FOCUS_IN, + TEXT_INPUT_FOCUS_OUT +}; +static enum text_input_focus_status currentTextInputFocusStatus = + TEXT_INPUT_FOCUS_COMPATIBLE; #define MAX_PREEDIT_LEN 128 static char preeditBuf[MAX_PREEDIT_LEN] = ""; @@ -510,13 +518,27 @@ static int set_font_selecter(GLFWwindow* window, struct nk_context* nk, int heig static void set_ime_buttons(GLFWwindow* window, struct nk_context* nk, int height) { - nk_layout_row_dynamic(nk, height, 2); + nk_layout_row_dynamic(nk, height, 3); if (nk_button_label(nk, "Toggle IME status")) { glfwSetInputMode(window, GLFW_IME, !currentIMEStatus); } + if (nk_button_label(nk, "Toggle text input focus")) + { + if (currentTextInputFocusStatus == TEXT_INPUT_FOCUS_IN) + { + glfwSetTextInputFocus(window, GLFW_FALSE); + currentTextInputFocusStatus = TEXT_INPUT_FOCUS_OUT; + } + else + { + glfwSetTextInputFocus(window, GLFW_TRUE); + currentTextInputFocusStatus = TEXT_INPUT_FOCUS_IN; + } + } + if (nk_button_label(nk, "Reset preedit text")) { glfwResetPreeditText(window); @@ -615,8 +637,23 @@ static void set_preedit_cursor_edit(GLFWwindow* window, struct nk_context* nk, i static void set_ime_stauts_labels(GLFWwindow* window, struct nk_context* nk, int height) { - nk_layout_row_dynamic(nk, height, 1); + const char* textInputFocusStatus = "Text input focus: compatible"; + + switch (currentTextInputFocusStatus) + { + case TEXT_INPUT_FOCUS_IN: + textInputFocusStatus = "Text input focus: Focus In"; + break; + case TEXT_INPUT_FOCUS_OUT: + textInputFocusStatus = "Text input focus: Focus Out"; + break; + case TEXT_INPUT_FOCUS_COMPATIBLE: + break; + } + + nk_layout_row_dynamic(nk, height, 2); nk_value_bool(nk, "IME status", currentIMEStatus); + nk_label(nk, textInputFocusStatus, NK_TEXT_LEFT); } static void set_preedit_labels(GLFWwindow* window, struct nk_context* nk, int height)