X11: Support IME

This commit re-organizes 6e7f93916b96c643ca7abe45d09f72d841ff15ed.

* Load missing XIM related function symbols.
* Generalize platform-specific features to _GLFWplatform.
* Change the defalut input style to over-the-spot style.
* Rename `decodeUTF8()` to `_glfwDecodeUTF8()` to make it as internal API.
    * It will be also needed to implment input method for Wayland.
* Refactor code shapes and variable names.

About over-the-spot style and on-the-spot style on X11:

* In over-the-spot mode, almost all APIs are disabled since applications only
  need to specify the preedit candidate window position by `glfwSetPreeditCursorPos()`.
* We can change the style by enabling `GLFW_X11_ONTHESPOT` init hint, but it
  has the following problems.
    * Status APIs don't work because status callbacks don't work.
      (at least in my ibus environment).
    * Can't specify the candidate window position.

Known problems:

* Some keys (arrow, Enter, BackSpace, ...) are passed to applications during preediting.
    * This will be fixed in PR #1972 : https://github.com/glfw/glfw/pull/1972

Co-authored-by: Takuro Ashie <ashie@clear-code.com>
This commit is contained in:
Daijiro Fukuda 2022-12-06 10:33:58 +09:00 committed by Takuro Ashie
parent 6981f7ae83
commit b0506b7912
6 changed files with 362 additions and 211 deletions

View File

@ -1324,6 +1324,11 @@ extern "C" {
* X11 specific [init hint](@ref GLFW_X11_XCB_VULKAN_SURFACE_hint). * X11 specific [init hint](@ref GLFW_X11_XCB_VULKAN_SURFACE_hint).
*/ */
#define GLFW_X11_XCB_VULKAN_SURFACE 0x00052001 #define GLFW_X11_XCB_VULKAN_SURFACE 0x00052001
/*! @brief X11 specific init hint.
*
* X11 specific [init hint](@ref GLFW_X11_ONTHESPOT_hint).
*/
#define GLFW_X11_ONTHESPOT 0x00052002
/*! @brief Wayland specific init hint. /*! @brief Wayland specific init hint.
* *
* Wayland specific [init hint](@ref GLFW_WAYLAND_LIBDECOR_hint). * Wayland specific [init hint](@ref GLFW_WAYLAND_LIBDECOR_hint).
@ -5249,6 +5254,9 @@ GLFWAPI void glfwSetPreeditCursorRectangle(GLFWwindow* window, int x, int y, int
* *
* @param[in] window The window. * @param[in] window The window.
* *
* @remark @x11 Since over-the-spot style is used by default, you don't need
* to use this function.
*
* @par Thread Safety * @par Thread Safety
* This function may only be called from the main thread. * This function may only be called from the main thread.
* *
@ -5423,6 +5431,9 @@ GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmods
* For more information about the callback parameters, see the * For more information about the callback parameters, see the
* [function pointer type](@ref GLFWpreeditfun). * [function pointer type](@ref GLFWpreeditfun).
* *
* @remark @x11 Since over-the-spot style is used by default, you don't need
* to use this function.
*
* @par Thread Safety * @par Thread Safety
* This function may only be called from the main thread. * This function may only be called from the main thread.
* *
@ -5452,6 +5463,8 @@ GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun
* For more information about the callback parameters, see the * For more information about the callback parameters, see the
* [function pointer type](@ref GLFWimestatusfun). * [function pointer type](@ref GLFWimestatusfun).
* *
* @remark @x11 Don't support this function. The callback is not called.
*
* @par Thread Safety * @par Thread Safety
* This function may only be called from the main thread. * This function may only be called from the main thread.
* *

View File

@ -61,6 +61,7 @@ static _GLFWinitconfig _glfwInitHints =
.x11 = .x11 =
{ {
.xcbVulkanSurface = GLFW_TRUE, .xcbVulkanSurface = GLFW_TRUE,
.onTheSpotIMStyle = GLFW_FALSE
}, },
.wl = .wl =
{ {
@ -175,6 +176,29 @@ size_t _glfwEncodeUTF8(char* s, uint32_t codepoint)
return count; return count;
} }
// Decode a Unicode code point from a UTF-8 stream
// Based on cutef8 by Jeff Bezanson (Public Domain)
//
uint32_t _glfwDecodeUTF8(const char** s)
{
uint32_t codepoint = 0, count = 0;
static const uint32_t offsets[] =
{
0x00000000u, 0x00003080u, 0x000e2080u,
0x03c82080u, 0xfa082080u, 0x82082080u
};
do
{
codepoint = (codepoint << 6) + (unsigned char) **s;
(*s)++;
count++;
} while ((**s & 0xc0) == 0x80);
assert(count <= 6);
return codepoint - offsets[count - 1];
}
// Splits and translates a text/uri-list into separate file paths // Splits and translates a text/uri-list into separate file paths
// NOTE: This function destroys the provided string // NOTE: This function destroys the provided string
// //
@ -459,6 +483,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
case GLFW_X11_XCB_VULKAN_SURFACE: case GLFW_X11_XCB_VULKAN_SURFACE:
_glfwInitHints.x11.xcbVulkanSurface = value; _glfwInitHints.x11.xcbVulkanSurface = value;
return; return;
case GLFW_X11_ONTHESPOT:
_glfwInitHints.x11.onTheSpotIMStyle = value;
return;
case GLFW_WAYLAND_LIBDECOR: case GLFW_WAYLAND_LIBDECOR:
_glfwInitHints.wl.libdecorMode = value; _glfwInitHints.wl.libdecorMode = value;
return; return;

View File

@ -385,6 +385,7 @@ struct _GLFWinitconfig
} ns; } ns;
struct { struct {
GLFWbool xcbVulkanSurface; GLFWbool xcbVulkanSurface;
GLFWbool onTheSpotIMStyle;
} x11; } x11;
struct { struct {
int libdecorMode; int libdecorMode;
@ -1036,6 +1037,7 @@ void _glfwTerminateVulkan(void);
const char* _glfwGetVulkanResultString(VkResult result); const char* _glfwGetVulkanResultString(VkResult result);
size_t _glfwEncodeUTF8(char* s, uint32_t codepoint); size_t _glfwEncodeUTF8(char* s, uint32_t codepoint);
uint32_t _glfwDecodeUTF8(const char** s);
char** _glfwParseUriList(char* text, int* count); char** _glfwParseUriList(char* text, int* count);
char* _glfw_strdup(const char* source); char* _glfw_strdup(const char* source);

View File

@ -445,9 +445,14 @@ static GLFWbool hasUsableInputMethodStyle(void)
if (XGetIMValues(_glfw.x11.im, XNQueryInputStyle, &styles, NULL) != NULL) if (XGetIMValues(_glfw.x11.im, XNQueryInputStyle, &styles, NULL) != NULL)
return GLFW_FALSE; return GLFW_FALSE;
if (_glfw.hints.init.x11.onTheSpotIMStyle)
_glfw.x11.imStyle = STYLE_ONTHESPOT;
else
_glfw.x11.imStyle = STYLE_OVERTHESPOT;
for (unsigned int i = 0; i < styles->count_styles; i++) for (unsigned int i = 0; i < styles->count_styles; i++)
{ {
if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing)) if (styles->supported_styles[i] == _glfw.x11.imStyle)
{ {
found = GLFW_TRUE; found = GLFW_TRUE;
break; break;
@ -1455,6 +1460,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetErrorHandler"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetErrorHandler");
_glfw.x11.xlib.SetICFocus = (PFN_XSetICFocus) _glfw.x11.xlib.SetICFocus = (PFN_XSetICFocus)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetICFocus"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetICFocus");
_glfw.x11.xlib.SetICValues = (PFN_XSetICValues)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetICValues");
_glfw.x11.xlib.SetIMValues = (PFN_XSetIMValues) _glfw.x11.xlib.SetIMValues = (PFN_XSetIMValues)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetIMValues"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XSetIMValues");
_glfw.x11.xlib.SetInputFocus = (PFN_XSetInputFocus) _glfw.x11.xlib.SetInputFocus = (PFN_XSetInputFocus)
@ -1485,6 +1492,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnmapWindow"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnmapWindow");
_glfw.x11.xlib.UnsetICFocus = (PFN_XUnsetICFocus) _glfw.x11.xlib.UnsetICFocus = (PFN_XUnsetICFocus)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnsetICFocus"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnsetICFocus");
_glfw.x11.xlib.VaCreateNestedList = (PFN_XVaCreateNestedList)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XVaCreateNestedList");
_glfw.x11.xlib.VisualIDFromVisual = (PFN_XVisualIDFromVisual) _glfw.x11.xlib.VisualIDFromVisual = (PFN_XVisualIDFromVisual)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XVisualIDFromVisual"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XVisualIDFromVisual");
_glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer) _glfw.x11.xlib.WarpPointer = (PFN_XWarpPointer)
@ -1517,6 +1526,8 @@ int _glfwInitX11(void)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XrmUniqueQuark"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XrmUniqueQuark");
_glfw.x11.xlib.UnregisterIMInstantiateCallback = (PFN_XUnregisterIMInstantiateCallback) _glfw.x11.xlib.UnregisterIMInstantiateCallback = (PFN_XUnregisterIMInstantiateCallback)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnregisterIMInstantiateCallback"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XUnregisterIMInstantiateCallback");
_glfw.x11.xlib.mbResetIC = (PFN_XmbResetIC)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "XmbResetIC");
_glfw.x11.xlib.utf8LookupString = (PFN_Xutf8LookupString) _glfw.x11.xlib.utf8LookupString = (PFN_Xutf8LookupString)
_glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "Xutf8LookupString"); _glfwPlatformGetModuleSymbol(_glfw.x11.xlib.handle, "Xutf8LookupString");
_glfw.x11.xlib.utf8SetWMProperties = (PFN_Xutf8SetWMProperties) _glfw.x11.xlib.utf8SetWMProperties = (PFN_Xutf8SetWMProperties)

View File

@ -91,6 +91,9 @@
#define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098 #define GLX_CONTEXT_RELEASE_BEHAVIOR_FLUSH_ARB 0x2098
#define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3 #define GLX_CONTEXT_OPENGL_NO_ERROR_ARB 0x31b3
#define STYLE_OVERTHESPOT (XIMPreeditNothing | XIMStatusNothing)
#define STYLE_ONTHESPOT (XIMPreeditCallbacks | XIMStatusCallbacks)
typedef XID GLXWindow; typedef XID GLXWindow;
typedef XID GLXDrawable; typedef XID GLXDrawable;
typedef struct __GLXFBConfig* GLXFBConfig; typedef struct __GLXFBConfig* GLXFBConfig;
@ -165,6 +168,7 @@ typedef Status (* PFN_XSendEvent)(Display*,Window,Bool,long,XEvent*);
typedef int (* PFN_XSetClassHint)(Display*,Window,XClassHint*); typedef int (* PFN_XSetClassHint)(Display*,Window,XClassHint*);
typedef XErrorHandler (* PFN_XSetErrorHandler)(XErrorHandler); typedef XErrorHandler (* PFN_XSetErrorHandler)(XErrorHandler);
typedef void (* PFN_XSetICFocus)(XIC); typedef void (* PFN_XSetICFocus)(XIC);
typedef char* (* PFN_XSetICValues)(XIC,...);
typedef char* (* PFN_XSetIMValues)(XIM,...); typedef char* (* PFN_XSetIMValues)(XIM,...);
typedef int (* PFN_XSetInputFocus)(Display*,Window,int,Time); typedef int (* PFN_XSetInputFocus)(Display*,Window,int,Time);
typedef char* (* PFN_XSetLocaleModifiers)(const char*); typedef char* (* PFN_XSetLocaleModifiers)(const char*);
@ -180,6 +184,7 @@ typedef int (* PFN_XUndefineCursor)(Display*,Window);
typedef int (* PFN_XUngrabPointer)(Display*,Time); typedef int (* PFN_XUngrabPointer)(Display*,Time);
typedef int (* PFN_XUnmapWindow)(Display*,Window); typedef int (* PFN_XUnmapWindow)(Display*,Window);
typedef void (* PFN_XUnsetICFocus)(XIC); typedef void (* PFN_XUnsetICFocus)(XIC);
typedef XVaNestedList (* PFN_XVaCreateNestedList)(int,...);
typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*); typedef VisualID (* PFN_XVisualIDFromVisual)(Visual*);
typedef int (* PFN_XWarpPointer)(Display*,Window,Window,int,int,unsigned int,unsigned int,int,int); typedef int (* PFN_XWarpPointer)(Display*,Window,Window,int,int,unsigned int,unsigned int,int,int);
typedef void (* PFN_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool); typedef void (* PFN_XkbFreeKeyboard)(XkbDescPtr,unsigned int,Bool);
@ -191,6 +196,7 @@ typedef KeySym (* PFN_XkbKeycodeToKeysym)(Display*,KeyCode,int,int);
typedef Bool (* PFN_XkbQueryExtension)(Display*,int*,int*,int*,int*,int*); typedef Bool (* PFN_XkbQueryExtension)(Display*,int*,int*,int*,int*,int*);
typedef Bool (* PFN_XkbSelectEventDetails)(Display*,unsigned int,unsigned int,unsigned long,unsigned long); typedef Bool (* PFN_XkbSelectEventDetails)(Display*,unsigned int,unsigned int,unsigned long,unsigned long);
typedef Bool (* PFN_XkbSetDetectableAutoRepeat)(Display*,Bool,Bool*); typedef Bool (* PFN_XkbSetDetectableAutoRepeat)(Display*,Bool,Bool*);
typedef char* (* PFN_XmbResetIC)(XIC);
typedef void (* PFN_XrmDestroyDatabase)(XrmDatabase); typedef void (* PFN_XrmDestroyDatabase)(XrmDatabase);
typedef Bool (* PFN_XrmGetResource)(XrmDatabase,const char*,const char*,char**,XrmValue*); typedef Bool (* PFN_XrmGetResource)(XrmDatabase,const char*,const char*,char**,XrmValue*);
typedef XrmDatabase (* PFN_XrmGetStringDatabase)(const char*); typedef XrmDatabase (* PFN_XrmGetStringDatabase)(const char*);
@ -265,6 +271,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XSetClassHint _glfw.x11.xlib.SetClassHint #define XSetClassHint _glfw.x11.xlib.SetClassHint
#define XSetErrorHandler _glfw.x11.xlib.SetErrorHandler #define XSetErrorHandler _glfw.x11.xlib.SetErrorHandler
#define XSetICFocus _glfw.x11.xlib.SetICFocus #define XSetICFocus _glfw.x11.xlib.SetICFocus
#define XSetICValues _glfw.x11.xlib.SetICValues
#define XSetIMValues _glfw.x11.xlib.SetIMValues #define XSetIMValues _glfw.x11.xlib.SetIMValues
#define XSetInputFocus _glfw.x11.xlib.SetInputFocus #define XSetInputFocus _glfw.x11.xlib.SetInputFocus
#define XSetLocaleModifiers _glfw.x11.xlib.SetLocaleModifiers #define XSetLocaleModifiers _glfw.x11.xlib.SetLocaleModifiers
@ -280,6 +287,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XUngrabPointer _glfw.x11.xlib.UngrabPointer #define XUngrabPointer _glfw.x11.xlib.UngrabPointer
#define XUnmapWindow _glfw.x11.xlib.UnmapWindow #define XUnmapWindow _glfw.x11.xlib.UnmapWindow
#define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus #define XUnsetICFocus _glfw.x11.xlib.UnsetICFocus
#define XVaCreateNestedList _glfw.x11.xlib.VaCreateNestedList
#define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual #define XVisualIDFromVisual _glfw.x11.xlib.VisualIDFromVisual
#define XWarpPointer _glfw.x11.xlib.WarpPointer #define XWarpPointer _glfw.x11.xlib.WarpPointer
#define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard #define XkbFreeKeyboard _glfw.x11.xkb.FreeKeyboard
@ -291,6 +299,7 @@ typedef void (* PFN_Xutf8SetWMProperties)(Display*,Window,const char*,const char
#define XkbQueryExtension _glfw.x11.xkb.QueryExtension #define XkbQueryExtension _glfw.x11.xkb.QueryExtension
#define XkbSelectEventDetails _glfw.x11.xkb.SelectEventDetails #define XkbSelectEventDetails _glfw.x11.xkb.SelectEventDetails
#define XkbSetDetectableAutoRepeat _glfw.x11.xkb.SetDetectableAutoRepeat #define XkbSetDetectableAutoRepeat _glfw.x11.xkb.SetDetectableAutoRepeat
#define XmbResetIC _glfw.x11.xlib.mbResetIC
#define XrmDestroyDatabase _glfw.x11.xrm.DestroyDatabase #define XrmDestroyDatabase _glfw.x11.xrm.DestroyDatabase
#define XrmGetResource _glfw.x11.xrm.GetResource #define XrmGetResource _glfw.x11.xrm.GetResource
#define XrmGetStringDatabase _glfw.x11.xrm.GetStringDatabase #define XrmGetStringDatabase _glfw.x11.xrm.GetStringDatabase
@ -577,6 +586,8 @@ typedef struct _GLFWlibraryX11
XContext context; XContext context;
// XIM input method // XIM input method
XIM im; XIM im;
// XIM input method style
XIMStyle imStyle;
// The previous X error handler, to be restored later // The previous X error handler, to be restored later
XErrorHandler errorHandler; XErrorHandler errorHandler;
// Most recent error code received by X error handler // Most recent error code received by X error handler
@ -722,6 +733,7 @@ typedef struct _GLFWlibraryX11
PFN_XSetClassHint SetClassHint; PFN_XSetClassHint SetClassHint;
PFN_XSetErrorHandler SetErrorHandler; PFN_XSetErrorHandler SetErrorHandler;
PFN_XSetICFocus SetICFocus; PFN_XSetICFocus SetICFocus;
PFN_XSetICValues SetICValues;
PFN_XSetIMValues SetIMValues; PFN_XSetIMValues SetIMValues;
PFN_XSetInputFocus SetInputFocus; PFN_XSetInputFocus SetInputFocus;
PFN_XSetLocaleModifiers SetLocaleModifiers; PFN_XSetLocaleModifiers SetLocaleModifiers;
@ -737,9 +749,11 @@ typedef struct _GLFWlibraryX11
PFN_XUngrabPointer UngrabPointer; PFN_XUngrabPointer UngrabPointer;
PFN_XUnmapWindow UnmapWindow; PFN_XUnmapWindow UnmapWindow;
PFN_XUnsetICFocus UnsetICFocus; PFN_XUnsetICFocus UnsetICFocus;
PFN_XVaCreateNestedList VaCreateNestedList;
PFN_XVisualIDFromVisual VisualIDFromVisual; PFN_XVisualIDFromVisual VisualIDFromVisual;
PFN_XWarpPointer WarpPointer; PFN_XWarpPointer WarpPointer;
PFN_XUnregisterIMInstantiateCallback UnregisterIMInstantiateCallback; PFN_XUnregisterIMInstantiateCallback UnregisterIMInstantiateCallback;
PFN_XmbResetIC mbResetIC;
PFN_Xutf8LookupString utf8LookupString; PFN_Xutf8LookupString utf8LookupString;
PFN_Xutf8SetWMProperties utf8SetWMProperties; PFN_Xutf8SetWMProperties utf8SetWMProperties;
} xlib; } xlib;

View File

@ -410,29 +410,6 @@ static void updateWindowMode(_GLFWwindow* window)
} }
} }
// Decode a Unicode code point from a UTF-8 stream
// Based on cutef8 by Jeff Bezanson (Public Domain)
//
static uint32_t decodeUTF8(const char** s)
{
uint32_t codepoint = 0, count = 0;
static const uint32_t offsets[] =
{
0x00000000u, 0x00003080u, 0x000e2080u,
0x03c82080u, 0xfa082080u, 0x82082080u
};
do
{
codepoint = (codepoint << 6) + (unsigned char) **s;
(*s)++;
count++;
} while ((**s & 0xc0) == 0x80);
assert(count <= 6);
return codepoint - offsets[count - 1];
}
// Convert the specified Latin-1 string to UTF-8 // Convert the specified Latin-1 string to UTF-8
// //
static char* convertLatin1toUTF8(const char* source) static char* convertLatin1toUTF8(const char* source)
@ -553,158 +530,189 @@ static void enableCursor(_GLFWwindow* window)
updateCursorImage(window); updateCursorImage(window);
} }
// TODO This callback is replaced by _createXIMPreeditCallbacks. Is there a possibility that this clearing process is necessary?
// Clear its handle when the input context has been destroyed // Clear its handle when the input context has been destroyed
// static void inputContextDestroyCallback(XIC ic, XPointer clientData, XPointer callData) //
// { static void inputContextDestroyCallback(XIC ic, XPointer clientData, XPointer callData)
// _GLFWwindow* window = (_GLFWwindow*) clientData;
// window->x11.ic = NULL;
// }
// Update cursor position to decide candidate window
static void _ximChangeCursorPosition(XIC xic, _GLFWwindow* window)
{ {
XVaNestedList preedit_attr; _GLFWwindow* window = (_GLFWwindow*) clientData;
XPoint spot; window->x11.ic = NULL;
spot.x = window->preeditCursorPosX;
spot.y = window->preeditCursorPosY + window->preeditCursorHeight;
preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(xic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
} }
// IME Start callback (do nothing) // IME Start callback (do nothing)
//
static void _ximPreeditStartCallback(XIC xic, XPointer clientData, XPointer callData) static void _ximPreeditStartCallback(XIC xic, XPointer clientData, XPointer callData)
{ {
} }
// IME Done callback (do nothing) // IME Done callback (do nothing)
//
static void _ximPreeditDoneCallback(XIC xic, XPointer clientData, XPointer callData) static void _ximPreeditDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{ {
} }
// IME Draw callback // IME Draw callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used since applications
// don't need to display preedit texts.
//
static void _ximPreeditDrawCallback(XIC xic, XPointer clientData, XIMPreeditDrawCallbackStruct* callData) static void _ximPreeditDrawCallback(XIC xic, XPointer clientData, XIMPreeditDrawCallbackStruct* callData)
{ {
int i, j, length, ctext, rstart, rend;
XIMText* text;
const char* src;
unsigned int codePoint;
unsigned int* preeditText;
XIMFeedback f;
_GLFWwindow* window = (_GLFWwindow*) clientData; _GLFWwindow* window = (_GLFWwindow*) clientData;
_GLFWpreedit* preedit = &window->preedit;
// keep cursor position to reduce API call if (!callData->text)
int cursorX = window->preeditCursorPosX; {
int cursorY = window->preeditCursorPosY;
int cursorHeight = window->preeditCursorHeight;
if (!callData->text) {
// preedit text is empty // preedit text is empty
window->ntext = 0; preedit->textCount = 0;
window->nblocks = 0; preedit->blockSizesCount = 0;
_glfwInputPreedit(window, 0); preedit->focusedBlockIndex = 0;
preedit->caretIndex = 0;
_glfwInputPreedit(window);
return; return;
} else { }
text = callData->text; else if (callData->text->encoding_is_wchar)
length = callData->chg_length; {
if (text->encoding_is_wchar) {
// wchar is not supported // wchar is not supported
return; return;
} }
ctext = window->ctext; else
while (ctext < length+1) { {
ctext = (ctext == 0) ? 1 : ctext * 2; XIMText* text = callData->text;
} int textLen = preedit->textCount + text->length - callData->chg_length;
if (ctext != window->ctext) { int textBufferCount = preedit->textBufferCount;
preeditText = _glfw_realloc(window->preeditText, sizeof(unsigned int)*ctext); int i, j, rstart, rend;
if (preeditText == NULL) { const char* src;
// realloc preedit text
while (textBufferCount < textLen + 1)
textBufferCount = (textBufferCount == 0) ? 1 : textBufferCount * 2;
if (textBufferCount != preedit->textBufferCount)
{
unsigned int* preeditText = _glfw_realloc(preedit->text,
sizeof(unsigned int) * textBufferCount);
if (preeditText == NULL)
return; return;
preedit->text = preeditText;
preedit->textBufferCount = textBufferCount;
} }
window->preeditText = preeditText; preedit->textCount = textLen;
window->ctext = ctext; preedit->text[textLen] = 0;
}
window->ntext = length; // realloc block sizes
window->preeditText[length] = 0; if (preedit->blockSizesBufferCount == 0)
if (window->cblocks == 0) { {
window->preeditAttributeBlocks = _glfw_calloc(4, sizeof(int)); preedit->blockSizes = _glfw_calloc(4, sizeof(int));
window->cblocks = 4; preedit->blockSizesBufferCount = 4;
} }
// store preedit text
src = text->string.multi_byte; src = text->string.multi_byte;
rend = 0; rend = 0;
rstart = length; rstart = textLen;
for (i = 0, j = 0; i < text->length; i++) { for (i = 0, j = callData->chg_first; i < text->length; i++)
codePoint = decodeUTF8(&src); {
if (i < callData->chg_first || callData->chg_first+length < i) { XIMFeedback f;
if (i < callData->chg_first || callData->chg_first + textLen < i)
continue; continue;
}
window->preeditText[j++] = codePoint; preedit->text[j++] = _glfwDecodeUTF8(&src);
f = text->feedback[i]; f = text->feedback[i];
if ((f & XIMReverse) || (f & XIMHighlight)) { if ((f & XIMReverse) || (f & XIMHighlight))
{
rend = i; rend = i;
if (i < rstart) { if (i < rstart)
rstart = i; rstart = i;
} }
} }
// store block sizes
// TODO: It doesn't care callData->chg_first != 0 case although it's quite rare.
if (rstart == textLen)
{
preedit->blockSizesCount = 1;
preedit->blockSizes[0] = textLen;
preedit->blockSizes[1] = 0;
preedit->focusedBlockIndex = 0;
preedit->caretIndex = callData->caret;
_glfwInputPreedit(window);
} }
if (rstart == length) { else if (rstart == 0)
window->nblocks = 1; {
window->preeditAttributeBlocks[0] = length; if (rend == textLen -1)
window->preeditAttributeBlocks[1] = 0; {
_glfwInputPreedit(window, 0); preedit->blockSizesCount = 1;
} else if (rstart == 0) { preedit->blockSizes[0] = textLen;
if (rend == length -1) { preedit->blockSizes[1] = 0;
window->nblocks = 1; preedit->focusedBlockIndex = 0;
window->preeditAttributeBlocks[0] = length; preedit->caretIndex = callData->caret;
window->preeditAttributeBlocks[1] = 0; _glfwInputPreedit(window);
_glfwInputPreedit(window, 0);
} else {
window->nblocks = 2;
window->preeditAttributeBlocks[0] = rend + 1;
window->preeditAttributeBlocks[1] = length - rend - 1;
window->preeditAttributeBlocks[2] = 0;
_glfwInputPreedit(window, 0);
} }
} else if (rend == length -1) { else
window->nblocks = 2; {
window->preeditAttributeBlocks[0] = rstart; preedit->blockSizesCount = 2;
window->preeditAttributeBlocks[1] = length - rstart; preedit->blockSizes[0] = rend + 1;
window->preeditAttributeBlocks[2] = 0; preedit->blockSizes[1] = textLen - rend - 1;
_glfwInputPreedit(window, 1); preedit->blockSizes[2] = 0;
} else { preedit->focusedBlockIndex = 0;
window->nblocks = 3; preedit->caretIndex = callData->caret;
window->preeditAttributeBlocks[0] = rstart; _glfwInputPreedit(window);
window->preeditAttributeBlocks[1] = rend - rstart + 1;
window->preeditAttributeBlocks[2] = length - rend - 1;
window->preeditAttributeBlocks[3] = 0;
_glfwInputPreedit(window, 1);
} }
if ((cursorX != window->preeditCursorPosX) }
|| (cursorY != window->preeditCursorPosY) else if (rend == textLen - 1)
|| (cursorHeight != window->preeditCursorHeight)) { {
_ximChangeCursorPosition(xic, window); preedit->blockSizesCount = 2;
preedit->blockSizes[0] = rstart;
preedit->blockSizes[1] = textLen - rstart;
preedit->blockSizes[2] = 0;
preedit->focusedBlockIndex = 1;
preedit->caretIndex = callData->caret;
_glfwInputPreedit(window);
}
else
{
preedit->blockSizesCount = 3;
preedit->blockSizes[0] = rstart;
preedit->blockSizes[1] = rend - rstart + 1;
preedit->blockSizes[2] = textLen - rend - 1;
preedit->blockSizes[3] = 0;
preedit->focusedBlockIndex = 1;
preedit->caretIndex = callData->caret;
_glfwInputPreedit(window);
} }
} }
} }
// IME Caret callback (do nothing) // IME Caret callback (do nothing)
//
static void _ximPreeditCaretCallback(XIC xic, XPointer clientData, XPointer callData) static void _ximPreeditCaretCallback(XIC xic, XPointer clientData, XPointer callData)
{ {
} }
// IME Status Start callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
// can not be taken.
//
static void _ximStatusStartCallback(XIC xic, XPointer clientData, XPointer callData) static void _ximStatusStartCallback(XIC xic, XPointer clientData, XPointer callData)
{ {
_GLFWwindow* window = (_GLFWwindow*) clientData; _GLFWwindow* window = (_GLFWwindow*) clientData;
window->x11.imeFocus = GLFW_TRUE; window->x11.imeFocus = GLFW_TRUE;
} }
// IME Status Done callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
// can not be taken.
//
static void _ximStatusDoneCallback(XIC xic, XPointer clientData, XPointer callData) static void _ximStatusDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{ {
_GLFWwindow* window = (_GLFWwindow*) clientData; _GLFWwindow* window = (_GLFWwindow*) clientData;
window->x11.imeFocus = GLFW_FALSE; window->x11.imeFocus = GLFW_FALSE;
} }
// IME Status Draw callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
// can not be taken.
//
static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCallbackStruct* callData) static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCallbackStruct* callData)
{ {
_GLFWwindow* window = (_GLFWwindow*) clientData; _GLFWwindow* window = (_GLFWwindow*) clientData;
@ -712,6 +720,9 @@ static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCa
} }
// Create XIM Preedit callback // Create XIM Preedit callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used since applications
// don't need to display preedit texts.
//
static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window) static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window)
{ {
window->x11.preeditStartCallback.client_data = (XPointer) window; window->x11.preeditStartCallback.client_data = (XPointer) window;
@ -723,14 +734,21 @@ static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window)
window->x11.preeditCaretCallback.client_data = (XPointer) window; window->x11.preeditCaretCallback.client_data = (XPointer) window;
window->x11.preeditCaretCallback.callback = (XIMProc) _ximPreeditCaretCallback; window->x11.preeditCaretCallback.callback = (XIMProc) _ximPreeditCaretCallback;
return XVaCreateNestedList(0, return XVaCreateNestedList(0,
XNPreeditStartCallback, &window->x11.preeditStartCallback.client_data, XNPreeditStartCallback,
XNPreeditDoneCallback, &window->x11.preeditDoneCallback.client_data, &window->x11.preeditStartCallback.client_data,
XNPreeditDrawCallback, &window->x11.preeditDrawCallback.client_data, XNPreeditDoneCallback,
XNPreeditCaretCallback, &window->x11.preeditCaretCallback.client_data, &window->x11.preeditDoneCallback.client_data,
XNPreeditDrawCallback,
&window->x11.preeditDrawCallback.client_data,
XNPreeditCaretCallback,
&window->x11.preeditCaretCallback.client_data,
NULL); NULL);
} }
// Create XIM status callback // Create XIM status callback
// When using the dafault style: STYLE_OVERTHESPOT, this is not used and the IME status
// can not be taken.
//
static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window) static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window)
{ {
window->x11.statusStartCallback.client_data = (XPointer) window; window->x11.statusStartCallback.client_data = (XPointer) window;
@ -740,9 +758,12 @@ static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window)
window->x11.statusDrawCallback.client_data = (XPointer) window; window->x11.statusDrawCallback.client_data = (XPointer) window;
window->x11.statusDrawCallback.callback = (XIMProc) _ximStatusDrawCallback; window->x11.statusDrawCallback.callback = (XIMProc) _ximStatusDrawCallback;
return XVaCreateNestedList(0, return XVaCreateNestedList(0,
XNStatusStartCallback, &window->x11.statusStartCallback.client_data, XNStatusStartCallback,
XNStatusDoneCallback, &window->x11.statusDoneCallback.client_data, &window->x11.statusStartCallback.client_data,
XNStatusDrawCallback, &window->x11.statusDrawCallback.client_data, XNStatusDoneCallback,
&window->x11.statusDoneCallback.client_data,
XNStatusDrawCallback,
&window->x11.statusDrawCallback.client_data,
NULL); NULL);
} }
@ -1475,7 +1496,7 @@ static void processEvent(XEvent *event)
const char* c = chars; const char* c = chars;
chars[count] = '\0'; chars[count] = '\0';
while (c - chars < count) while (c - chars < count)
_glfwInputChar(window, decodeUTF8(&c), mods, plain); _glfwInputChar(window, _glfwDecodeUTF8(&c), mods, plain);
} }
if (chars != buffer) if (chars != buffer)
@ -2106,12 +2127,23 @@ void _glfwPushSelectionToManagerX11(void)
void _glfwCreateInputContextX11(_GLFWwindow* window) void _glfwCreateInputContextX11(_GLFWwindow* window)
{ {
XIMCallback callback;
callback.callback = (XIMProc) inputContextDestroyCallback;
callback.client_data = (XPointer) window;
window->x11.imeFocus = GLFW_FALSE;
if (_glfw.x11.imStyle == STYLE_ONTHESPOT)
{
// On X11, on-the-spot style is unstable.
// Status callbacks are not called and the preedit cursor position
// can not be changed.
XVaNestedList preeditList = _createXIMPreeditCallbacks(window); XVaNestedList preeditList = _createXIMPreeditCallbacks(window);
XVaNestedList statusList = _createXIMStatusCallbacks(window); XVaNestedList statusList = _createXIMStatusCallbacks(window);
window->x11.ic = XCreateIC(_glfw.x11.im, window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle, XNInputStyle,
XIMPreeditCallbacks | XIMStatusCallbacks, _glfw.x11.imStyle,
XNClientWindow, XNClientWindow,
window->x11.handle, window->x11.handle,
XNFocusWindow, XNFocusWindow,
@ -2120,11 +2152,34 @@ void _glfwCreateInputContextX11(_GLFWwindow* window)
preeditList, preeditList,
XNStatusAttributes, XNStatusAttributes,
statusList, statusList,
XNDestroyCallback,
&callback,
NULL); NULL);
XFree(preeditList); XFree(preeditList);
XFree(statusList); XFree(statusList);
window->x11.imeFocus = GLFW_FALSE; }
else if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
{
window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle,
_glfw.x11.imStyle,
XNClientWindow,
window->x11.handle,
XNFocusWindow,
window->x11.handle,
XNDestroyCallback,
&callback,
NULL);
}
else
{
// (XIMPreeditNothing | XIMStatusNothing) is considered as STYLE_OVERTHESPOT.
// So this branch should not be used now.
_glfwInputError(GLFW_PLATFORM_ERROR,
"X11: Failed to create input context.");
return;
}
if (window->x11.ic) if (window->x11.ic)
{ {
@ -3274,21 +3329,90 @@ const char* _glfwGetClipboardStringX11(void)
return getSelectionString(_glfw.x11.CLIPBOARD); return getSelectionString(_glfw.x11.CLIPBOARD);
} }
// When using STYLE_ONTHESPOT, this doesn't work and the cursor position can't be updated
//
void _glfwUpdatePreeditCursorRectangleX11(_GLFWwindow* window) void _glfwUpdatePreeditCursorRectangleX11(_GLFWwindow* window)
{ {
XVaNestedList preedit_attr;
XPoint spot;
_GLFWpreedit* preedit = &window->preedit;
if (!window->x11.ic)
return;
spot.x = preedit->cursorPosX + preedit->cursorWidth;
spot.y = preedit->cursorPosY + preedit->cursorHeight;
preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, NULL);
XSetICValues(window->x11.ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
} }
void _glfwResetPreeditTextX11(_GLFWwindow* window) void _glfwResetPreeditTextX11(_GLFWwindow* window)
{ {
XIC ic = window->x11.ic;
_GLFWpreedit* preedit = &window->preedit;
/* restore conversion state after resetting ic later */
XIMPreeditState preedit_state = XIMPreeditUnKnown;
XVaNestedList preedit_attr;
char* result;
if (!ic)
return;
// Can not manage IME in the case of over-the-spot.
if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
return;
if (preedit->textCount == 0)
return;
preedit_attr = XVaCreateNestedList(0, XNPreeditState, &preedit_state, NULL);
XGetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
result = XmbResetIC(ic);
preedit_attr = XVaCreateNestedList(0, XNPreeditState, preedit_state, NULL);
XSetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
preedit->textCount = 0;
preedit->blockSizesCount = 0;
preedit->focusedBlockIndex = 0;
preedit->caretIndex = 0;
_glfwInputPreedit(window);
XFree (result);
} }
void _glfwSetIMEStatusX11(_GLFWwindow* window, int active) void _glfwSetIMEStatusX11(_GLFWwindow* window, int active)
{ {
XIC ic = window->x11.ic;
if (!ic)
return;
// Can not manage IME in the case of over-the-spot.
if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
return;
if (active)
XSetICFocus(ic);
else
XUnsetICFocus(ic);
} }
int _glfwGetIMEStatusX11(_GLFWwindow* window) int _glfwGetIMEStatusX11(_GLFWwindow* window)
{ {
if (!window->x11.ic)
return GLFW_FALSE; return GLFW_FALSE;
// Can not manage IME in the case of over-the-spot.
if (_glfw.x11.imStyle == STYLE_OVERTHESPOT)
return GLFW_FALSE;
return window->x11.imeFocus;
} }
EGLenum _glfwGetEGLPlatformX11(EGLint** attribs) EGLenum _glfwGetEGLPlatformX11(EGLint** attribs)
@ -3489,46 +3613,6 @@ VkResult _glfwCreateWindowSurfaceX11(VkInstance instance,
} }
} }
void _glfwPlatformResetPreeditText(_GLFWwindow* window) {
XIC ic = window->x11.ic;
/* restore conversion state after resetting ic later */
XIMPreeditState preedit_state = XIMPreeditUnKnown;
XVaNestedList preedit_attr;
char* result;
if (window->ntext == 0)
return;
preedit_attr = XVaCreateNestedList(0, XNPreeditState, &preedit_state, NULL);
XGetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
result = XmbResetIC(ic);
preedit_attr = XVaCreateNestedList(0, XNPreeditState, preedit_state, NULL);
XSetICValues(ic, XNPreeditAttributes, preedit_attr, NULL);
XFree(preedit_attr);
window->ntext = 0;
window->nblocks = 0;
_glfwInputPreedit(window, 0);
XFree (result);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active) {
XIC ic = window->x11.ic;
if (active) {
XSetICFocus(ic);
} else {
XUnsetICFocus(ic);
}
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window) {
return window->x11.imeFocus;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW native API ////// ////// GLFW native API //////