fixup ef70fee8 Add preedit text callback API and implementation, IME status API for X11, macOS, Windows

This commit is contained in:
Yoshiki Shibukawa 2019-03-14 02:03:46 +09:00
parent 8055dad7e4
commit d36a164423
14 changed files with 1367 additions and 18 deletions

2
.gitignore vendored
View File

@ -82,4 +82,4 @@ tests/timeout
tests/title tests/title
tests/vulkan tests/vulkan
tests/windows tests/windows
tests/ime

View File

@ -205,6 +205,7 @@ endif()
if (_GLFW_WIN32) if (_GLFW_WIN32)
list(APPEND glfw_PKG_LIBS "-lgdi32") list(APPEND glfw_PKG_LIBS "-lgdi32")
list(APPEND glfw_LIBRARIES "imm32")
if (GLFW_USE_HYBRID_HPG) if (GLFW_USE_HYBRID_HPG)
set(_GLFW_USE_HYBRID_HPG 1) set(_GLFW_USE_HYBRID_HPG 1)
@ -298,12 +299,13 @@ if (_GLFW_COCOA)
list(APPEND glfw_LIBRARIES list(APPEND glfw_LIBRARIES
"-framework Cocoa" "-framework Cocoa"
"-framework Carbon"
"-framework IOKit" "-framework IOKit"
"-framework CoreFoundation" "-framework CoreFoundation"
"-framework CoreVideo") "-framework CoreVideo")
set(glfw_PKG_DEPS "") set(glfw_PKG_DEPS "")
set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo") set(glfw_PKG_LIBS "-framework Cocoa -framework Carbon -framework IOKit -framework CoreFoundation -framework CoreVideo")
endif() endif()
#-------------------------------------------------------------------- #--------------------------------------------------------------------

View File

@ -214,6 +214,96 @@ void character_callback(GLFWwindow* window, unsigned int codepoint)
} }
@endcode @endcode
@subsection preedit IME Support
All desktop operating systems support IME (Input Method Editor) to input characters
that are not mapped with physical keys. IME have been popular among Eeastern Asian people.
And some operating systems start supporting voice input via IME mechanism.
GLFW provides IME support functions to help
you implement better text input features. You should add suitable visualization code for
preedit text.
IME works in front of actual character input events (@ref input_char).
If your application uses text input and you want to support IME,
you should register preedit callback to receive preedit text before committed.
@code
glfwSetPreeditCallback(window, preedit_callback);
@endcode
The callback function receives chunk of text and focused block information.
@code
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
}
@endcode
strLength and string parameter reprsent whole preedit text. Each character of the preedit string is a codepoint like @ref input_char.
If you want to type the text "寿司(sushi)", Usually the callback is called several times like the following sequence:
-# key event: s
-# preedit: [string: "", block: [1], focusedBlock: 0]
-# key event: u
-# preedit: [string: "す", block: [1], focusedBlock: 0]
-# key event: s
-# preedit: [string: "すs", block: [2], focusedBlock: 0]
-# key event: h
-# preedit: [string: "すsh", block: [2], focusedBlock: 0]
-# key event: i
-# preedit: [string: "すし", block: [2], focusedBlock: 0]
-# key event: ' '
-# preedit: [string: "寿司", block: [2], focusedBlock: 0]
-# char: '寿'
-# char: '司'
-# preedit: [string: "", block: [], focusedBlock: 0]
If preedit text includes several semantic blocks, preedit callbacks returns several blocks after a space key pressed:
-# preedit: [string: "わたしはすしをたべます", block: [11], focusedBlock: 0]
-# preedit: [string: "私は寿司を食べます", block: [2, 7], focusedBlock: 1]
"blocks" is a list of block length. The above case, it contains the following blocks and second block is focused.
- 私は
- [寿司を食べます]
commited text(passed via regular @ref input_char event), unfocused block, focused block should have different text style.
GLFW provides helper function to teach suitable position of the candidate window to window system.
Window system decides the best position from text cursor geometry (x, y coords and height). You should call this function
in the above preedit text callback function.
@code
glfwSetPreeditCursorPos(window, x, y, h);
glfwGetPreeditCursorPos(window, &x, &y, &h);
@endcode
Sometimes IME task is interrupted by user or application. There are several functions to support these situation.
You can receive notification about IME status change(on/off) by using the following function:
@code
glfwSetIMEStatusCallback(window, imestatus_callback);
@endcode
imestatus_callback has simple sigunature like this:
@code
static void imestatus_callback(GLFWwindow* window) {
}
@endcode
You can implement the code that resets or commits preedit text when IME status is changed and preedit text is not empty.
When the focus is gone from text box, you can use the following functions to reset IME status:
@code
void glfwResetPreeditText(GLFWwindow* window);
void glfwSetIMEStatus(GLFWwindow* window, int active)
int glfwGetIMEStatus(GLFWwindow* window)
@endcode
@subsection input_key_name Key names @subsection input_key_name Key names

View File

@ -1003,6 +1003,7 @@ extern "C" {
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004 #define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_RAW_MOUSE_MOTION 0x00033005 #define GLFW_RAW_MOUSE_MOTION 0x00033005
#define GLFW_IME 0x00033006
#define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_HIDDEN 0x00034002
@ -1458,6 +1459,37 @@ typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
*/ */
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int); typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);
/*! @brief The function signature for preedit callbacks.
*
* This is the function signature for preedit callback functions.
*
* @param[in] window The window that received the event.
* @param[in] length Preedit string length.
* @param[in] string Preedit string.
* @param[in] count Attributed block count.
* @param[in] blocksizes List of attributed block size.
* @param[in] focusedblock Focused block index.
*
* @sa @ref preedit
* @sa glfwSetPreeditCallback
*
* @ingroup input
*/
typedef void (* GLFWpreeditfun)(GLFWwindow*,int,unsigned int*,int,int*,int);
/*! @brief The function signature for IME status change callbacks.
*
* This is the function signature for IME status change callback functions.
*
* @param[in] window The window that received the event.
*
* @sa @ref preedit
* @sa glfwSetIMEStatusCallback
*
* @ingroup monitor
*/
typedef void (* GLFWimestatusfun)(GLFWwindow*);
/*! @brief The function signature for file drop callbacks. /*! @brief The function signature for file drop callbacks.
* *
* This is the function signature for file drop callbacks. * This is the function signature for file drop callbacks.
@ -3844,13 +3876,13 @@ GLFWAPI void glfwPostEmptyEvent(void);
* *
* This function returns the value of an input option for the specified window. * This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
* @ref GLFW_RAW_MOUSE_MOTION. * @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME.
* *
* @param[in] window The window to query. * @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`
* `GLFW_RAW_MOUSE_MOTION`. * `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM. * GLFW_INVALID_ENUM.
@ -3869,8 +3901,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* *
* This function sets an input mode option for the specified window. The mode * This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS, * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or * @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
* @ref GLFW_RAW_MOUSE_MOTION. * @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME.
* *
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes: * modes:
@ -3908,10 +3940,13 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* attempting to set this will emit @ref GLFW_PLATFORM_ERROR. Call @ref * attempting to set this will emit @ref GLFW_PLATFORM_ERROR. Call @ref
* glfwRawMouseMotionSupported to check for support. * glfwRawMouseMotionSupported to check for support.
* *
* If the mode is `GLFW_IME`, the value must be either `GLFW_TRUE` to turn on IME,
* or `GLFW_FALSE` to turn off it.
*
* @param[in] window The window whose input mode to set. * @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`, * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or * `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`,
* `GLFW_RAW_MOUSE_MOTION`. * `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`.
* @param[in] value The new value of the specified input mode. * @param[in] value The new value of the specified input mode.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
@ -4308,6 +4343,67 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
*/ */
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor); GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
/*! @brief Retrieves the position of the text cursor relative to the client area of window.
*
* This function returns position hint to decide the candidate window.
*
* @param[in] window The window to set the text cursor for.
* @param[out] x The text cursor x position (relative position from window coordinates).
* @param[out] y The text cursor y position (relative position from window coordinates).
* @param[out] h The text cursor height.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwGetPreeditCursorPos(GLFWwindow* window, int *x, int *y, int *h);
/*! @brief Notify the text cursor position to window system to decide the candidate window position.
*
* This function teach position hint to decide the candidate window. The candidate window
* is a part of IME(Input Method Editor) and show several candidate strings.
*
* Windows sytems decide proper pisition from text cursor geometry.
* You should call this function in preedit callback.
*
* @param[in] window The window to set the text cursor for.
* @param[in] x The text cursor x position (relative position from window coordinates).
* @param[in] y The text cursor y position (relative position from window coordinates).
* @param[in] h The text cursor height.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwSetPreeditCursorPos(GLFWwindow* window, int x, int y, int h);
/*! @brief Reset IME input status.
*
* This function resets IME's preedit text.
*
* @param[in] window The window.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref preedit
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwResetPreeditText(GLFWwindow* window);
/*! @brief Sets the key callback. /*! @brief Sets the key callback.
* *
* This function sets the key callback of the specified window, which is called * This function sets the key callback of the specified window, which is called
@ -4422,6 +4518,58 @@ GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun);
*/ */
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun); GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun);
/*! @brief Sets the preedit callback.
*
* This function sets the preedit callback of the specified
* window, which is called when an IME is processing text before commited.
*
* Callback receives relative position of input cursor inside preedit text and
* attributed text blocks. This callback is used for on-the-spot text editing
* with IME.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or an
* error occurred.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X
*
* @ingroup input
*/
GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun cbfun);
/*! @brief Sets the IME status change callback.
*
* This function sets the preedit callback of the specified
* window, which is called when an IME is processing text before commited.
*
* Callback receives relative position of input cursor inside preedit text and
* attributed text blocks. This callback is used for on-the-spot text editing
* with IME.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or an
* error occurred.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X
*
* @ingroup input
*/
GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* window, GLFWimestatusfun cbfun);
/*! @brief Sets the mouse button callback. /*! @brief Sets the mouse button callback.
* *
* This function sets the mouse button callback of the specified window, which * This function sets the mouse button callback of the specified window, which

View File

@ -344,8 +344,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
_glfwUpdateDisplayLinkDisplayNSGL(window); _glfwUpdateDisplayLinkDisplayNSGL(window);
} }
@end - (void)imeStatusChangeNotified:(NSNotification *)notification {
_glfwInputIMEStatus(window);
}
@end
//------------------------------------------------------------------------ //------------------------------------------------------------------------
// Content view class for the GLFW window // Content view class for the GLFW window
@ -712,6 +715,56 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string]; markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
else else
markedText = [[NSMutableAttributedString alloc] initWithString:string]; markedText = [[NSMutableAttributedString alloc] initWithString:string];
NSString* markedTextString = markedText.string;
NSUInteger i, length = [markedTextString length];
int ctext = window->ctext;
while (ctext < length+1) {
ctext = (ctext == 0) ? 1 : ctext*2;
}
if (ctext != window->ctext) {
unsigned int* preeditText = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return;
}
window->preeditText = preeditText;
window->ctext = ctext;
}
window->ntext = length;
window->preeditText[length] = 0;
for (i = 0; i < length; i++)
{
const unichar codepoint = [markedTextString characterAtIndex:i];
window->preeditText[i] = codepoint;
}
int focusedBlock = 0;
NSInteger offset = 0;
window->nblocks = 0;
while (offset < length) {
NSRange effectiveRange;
NSDictionary *attributes = [markedText attributesAtIndex:offset effectiveRange:&effectiveRange];
if (window->nblocks == window->cblocks) {
int cblocks = window->cblocks * 2;
int* blocks = realloc(window->preeditAttributeBlocks, sizeof(int)*cblocks);
if (blocks == NULL) {
return;
}
window->preeditAttributeBlocks = blocks;
window->cblocks = cblocks;
}
window->preeditAttributeBlocks[window->nblocks] = effectiveRange.length;
offset += effectiveRange.length;
if (effectiveRange.length == 0) {
break;
}
NSNumber* underline = (NSNumber*) [attributes objectForKey:@"NSUnderline"];
if ([underline intValue] != 1) {
focusedBlock = window->nblocks;
}
window->nblocks++;
}
_glfwInputPreedit(window, focusedBlock);
} }
- (void)unmarkText - (void)unmarkText
@ -959,6 +1012,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
acquireMonitor(window); acquireMonitor(window);
} }
[[NSNotificationCenter defaultCenter]
addObserver: window->ns.delegate
selector:@selector(imeStatusChangeNotified:)
name:NSTextInputContextKeyboardSelectionDidChangeNotification
object: nil];
return GLFW_TRUE; return GLFW_TRUE;
} // autoreleasepool } // autoreleasepool
@ -971,6 +1029,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
if (_glfw.ns.disabledCursorWindow == window) if (_glfw.ns.disabledCursorWindow == window)
_glfw.ns.disabledCursorWindow = NULL; _glfw.ns.disabledCursorWindow = NULL;
[[NSNotificationCenter defaultCenter] removeObserver: window->ns.delegate];
[window->ns.object orderOut:nil]; [window->ns.object orderOut:nil];
if (window->monitor) if (window->monitor)
@ -1776,6 +1836,49 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
} // autoreleasepool } // autoreleasepool
} }
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
NSTextInputContext *context = [NSTextInputContext currentInputContext];
[context discardMarkedText];
[window->ns.view unmarkText];
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
// Mac OS has several input sources.
// this code assumes input methods not in ascii capable inputs using IME.
NSArray* asciiInputSources = CFBridgingRelease(TISCreateASCIICapableInputSourceList());
TISInputSourceRef asciiSource = (__bridge TISInputSourceRef)([asciiInputSources firstObject]);
if (active) {
NSArray* allInputSources = CFBridgingRelease(TISCreateInputSourceList(NULL, false));
NSString* asciiSourceID = (__bridge NSString *)(TISGetInputSourceProperty(asciiSource, kTISPropertyInputSourceID));
int i;
int count = [allInputSources count];
for (i = 0; i < count; i++) {
TISInputSourceRef source = (__bridge TISInputSourceRef)([allInputSources objectAtIndex: i]);
NSString* sourceID = (__bridge NSString *)(TISGetInputSourceProperty(source, kTISPropertyInputSourceID));
if ([asciiSourceID compare: sourceID] != NSOrderedSame) {
TISSelectInputSource(source);
break;
}
}
} else if (asciiSource) {
TISSelectInputSource(asciiSource);
}
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
TISInputSourceRef currentSource = TISCopyCurrentKeyboardInputSource();
NSString* currentSourceID = (__bridge NSString *)(TISGetInputSourceProperty(currentSource, kTISPropertyInputSourceID));
NSArray* asciiInputSources = CFBridgingRelease(TISCreateASCIICapableInputSourceList());
TISInputSourceRef asciiSource = (__bridge TISInputSourceRef)([asciiInputSources firstObject]);
if (asciiSource) {
NSString* asciiSourceID = (__bridge NSString *)(TISGetInputSourceProperty(asciiSource, kTISPropertyInputSourceID));
return ([asciiSourceID compare: currentSourceID] == NSOrderedSame) ? GLFW_FALSE : GLFW_TRUE;
}
return GLFW_FALSE;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW native API ////// ////// GLFW native API //////

View File

@ -305,6 +305,20 @@ void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWb
} }
} }
void _glfwInputPreedit(_GLFWwindow* window, int focusedBlock)
{
if (window->callbacks.preedit) {
window->callbacks.preedit((GLFWwindow*) window, window->ntext, window->preeditText, window->nblocks, window->preeditAttributeBlocks, focusedBlock);
}
}
void _glfwInputIMEStatus(_GLFWwindow* window)
{
if (window->callbacks.imestatus) {
window->callbacks.imestatus((GLFWwindow*) window);
}
}
// Notifies shared code of a scroll event // Notifies shared code of a scroll event
// //
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset) void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
@ -487,7 +501,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->lockKeyMods; return window->lockKeyMods;
case GLFW_RAW_MOUSE_MOTION: case GLFW_RAW_MOUSE_MOTION:
return window->rawMouseMotion; return window->rawMouseMotion;
} case GLFW_IME:
return _glfwPlatformGetIMEStatus(window); }
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
return 0; return 0;
@ -582,7 +597,10 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
window->rawMouseMotion = value; window->rawMouseMotion = value;
_glfwPlatformSetRawMouseMotion(window, value); _glfwPlatformSetRawMouseMotion(window, value);
} }
else else if (mode == GLFW_IME)
{
_glfwPlatformSetIMEStatus(window, value ? GLFW_TRUE : GLFW_FALSE);
} else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
} }
@ -824,6 +842,30 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
_glfwPlatformSetCursor(window, cursor); _glfwPlatformSetCursor(window, cursor);
} }
GLFWAPI void glfwGetPreeditCursorPos(GLFWwindow* handle, int *x, int *y, int *h)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
if (x)
*x = window->preeditCursorPosX;
if (y)
*y = window->preeditCursorPosY;
if (h)
*h = window->preeditCursorHeight;
}
GLFWAPI void glfwSetPreeditCursorPos(GLFWwindow* handle, int x, int y, int h)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
window->preeditCursorPosX = x;
window->preeditCursorPosY = y;
window->preeditCursorHeight = h;
}
GLFWAPI void glfwResetPreeditText(GLFWwindow* handle) {
_GLFWwindow* window = (_GLFWwindow*) handle;
_glfwPlatformResetPreeditText(window);
}
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun) GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
{ {
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;
@ -854,6 +896,22 @@ GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmods
return cbfun; return cbfun;
} }
GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* handle, GLFWpreeditfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.preedit, cbfun);
return cbfun;
}
GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* handle, GLFWimestatusfun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.imestatus, cbfun);
return cbfun;
}
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle, GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
GLFWmousebuttonfun cbfun) GLFWmousebuttonfun cbfun)
{ {

View File

@ -392,6 +392,15 @@ struct _GLFWwindow
double virtualCursorPosX, virtualCursorPosY; double virtualCursorPosX, virtualCursorPosY;
GLFWbool rawMouseMotion; GLFWbool rawMouseMotion;
// Preedit texts
unsigned int* preeditText;
int ntext;
int ctext;
int* preeditAttributeBlocks;
int nblocks;
int cblocks;
int preeditCursorPosX, preeditCursorPosY, preeditCursorHeight;
_GLFWcontext context; _GLFWcontext context;
struct { struct {
@ -411,6 +420,8 @@ struct _GLFWwindow
GLFWkeyfun key; GLFWkeyfun key;
GLFWcharfun character; GLFWcharfun character;
GLFWcharmodsfun charmods; GLFWcharmodsfun charmods;
GLFWpreeditfun preedit;
GLFWimestatusfun imestatus;
GLFWdropfun drop; GLFWdropfun drop;
} callbacks; } callbacks;
@ -716,6 +727,8 @@ void _glfwInputKey(_GLFWwindow* window,
int key, int scancode, int action, int mods); int key, int scancode, int action, int mods);
void _glfwInputChar(_GLFWwindow* window, void _glfwInputChar(_GLFWwindow* window,
unsigned int codepoint, int mods, GLFWbool plain); unsigned int codepoint, int mods, GLFWbool plain);
void _glfwInputPreedit(_GLFWwindow* window, int focusedBlock);
void _glfwInputIMEStatus(_GLFWwindow* window);
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset); void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods); void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos); void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
@ -736,6 +749,9 @@ void _glfwInputError(int code, const char* format, ...)
void _glfwInputError(int code, const char* format, ...); void _glfwInputError(int code, const char* format, ...);
#endif #endif
void _glfwPlatformResetPreeditText(_GLFWwindow* window);
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active);
int _glfwPlatformGetIMEStatus(_GLFWwindow* window);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW internal API ////// ////// GLFW internal API //////

View File

@ -33,6 +33,7 @@
#include <string.h> #include <string.h>
#include <windowsx.h> #include <windowsx.h>
#include <shellapi.h> #include <shellapi.h>
#include <imm.h>
#define _GLFW_KEY_INVALID -2 #define _GLFW_KEY_INVALID -2
@ -574,6 +575,15 @@ static void releaseMonitor(_GLFWwindow* window)
_glfwRestoreVideoModeWin32(window->monitor); _glfwRestoreVideoModeWin32(window->monitor);
} }
// Set cursor position to decide candidate window
static void _win32ChangeCursorPosition(HIMC hIMC, _GLFWwindow* window) {
int x = window->preeditCursorPosX;
int y = window->preeditCursorPosY;
int h = window->preeditCursorHeight;
CANDIDATEFORM excludeRect = {0, CFS_EXCLUDE, {x, y}, {x, y, x, y+h}};
ImmSetCandidateWindow(hIMC, &excludeRect);
}
// Window callback function (handles window messages) // Window callback function (handles window messages)
// //
static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg, static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
@ -733,7 +743,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
} }
_glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain); _glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain);
return 0; return TRUE;
} }
case WM_KEYDOWN: case WM_KEYDOWN:
@ -768,7 +778,93 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
break; break;
} }
case WM_IME_COMPOSITION:
{
if (lParam & GCS_RESULTSTR) {
window->nblocks = 0;
window->ntext = 0;
_glfwInputPreedit(window, 0);
return TRUE;
}
if (lParam & GCS_COMPSTR) {
HIMC hIMC = ImmGetContext(hWnd);
// get preedit data sizes
LONG preeditTextLength = ImmGetCompositionStringW(hIMC, GCS_COMPSTR, NULL, 0);
LONG attrLength = ImmGetCompositionString(hIMC, GCS_COMPATTR, NULL, 0);
LONG clauseLength = ImmGetCompositionString(hIMC, GCS_COMPCLAUSE, NULL, 0);
if (preeditTextLength > 0) {
// get preedit data
int length = preeditTextLength/sizeof(WCHAR);
LPWSTR buffer = (LPWSTR)malloc(sizeof(WCHAR)+preeditTextLength);
LPSTR attributes = (LPSTR)malloc(attrLength);
DWORD *clauses = (DWORD*)malloc(clauseLength);
ImmGetCompositionStringW(hIMC, GCS_COMPSTR, buffer, preeditTextLength);
ImmGetCompositionString(hIMC, GCS_COMPATTR, attributes, attrLength);
ImmGetCompositionString(hIMC, GCS_COMPCLAUSE, clauses, clauseLength);
// store preedit text
int ctext = window->ctext;
while (ctext < length+1) {
ctext = (ctext == 0) ? 1 : ctext*2;
}
if (ctext != window->ctext) {
unsigned int* preeditText = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return 0;
free(buffer);
free(attributes);
free(clauses);
}
window->preeditText = preeditText;
window->ctext = ctext;
}
window->ntext = length;
window->preeditText[length] = 0;
int i;
for (i=0; i < length; i++) {
window->preeditText[i] = buffer[i];
}
// store blocks
window->nblocks = clauseLength/sizeof(DWORD)-1;
// last element of clauses is a block count, but
// text length is convenient.
clauses[window->nblocks] = length;
int cblocks = window->cblocks;
while (cblocks < window->nblocks) {
cblocks = (cblocks == 0) ? 1 : cblocks*2;
}
if (cblocks != window->cblocks) {
int* blocks = realloc(window->preeditAttributeBlocks, sizeof(int)*cblocks);
if (blocks == NULL) {
return 0;
free(buffer);
free(attributes);
free(clauses);
}
window->preeditAttributeBlocks = blocks;
window->cblocks = cblocks;
}
int focusedBlock = 0;
for (i=0; i < window->nblocks; i++) {
window->preeditAttributeBlocks[i] = clauses[i+1]-clauses[i];
if (attributes[clauses[i]] != ATTR_CONVERTED) {
focusedBlock = i;
}
}
free(buffer);
free(attributes);
free(clauses);
_glfwInputPreedit(window, focusedBlock);
_win32ChangeCursorPosition(hIMC, window);
}
ImmReleaseContext(hWnd, hIMC);
return TRUE;
}
break;
}
case WM_IME_NOTIFY:
if (wParam == IMN_SETOPENSTATUS)
_glfwInputIMEStatus(window);
break;
case WM_LBUTTONDOWN: case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN: case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN: case WM_MBUTTONDOWN:
@ -2229,6 +2325,30 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
return err; return err;
} }
void _glfwPlatformResetPreeditText(_GLFWwindow* window)
{
HWND hWnd = window->win32.handle;
HIMC hIMC = ImmGetContext(hWnd);
ImmNotifyIME(hIMC, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
ImmReleaseContext(hWnd, hIMC);
}
void _glfwPlatformSetIMEStatus(_GLFWwindow* window, int active)
{
HWND hWnd = window->win32.handle;
HIMC hIMC = ImmGetContext(hWnd);
ImmSetOpenStatus(hIMC, active ? TRUE : FALSE);
ImmReleaseContext(hWnd, hIMC);
}
int _glfwPlatformGetIMEStatus(_GLFWwindow* window)
{
HWND hWnd = window->win32.handle;
HIMC hIMC = ImmGetContext(hWnd);
BOOL result = ImmGetOpenStatus(hIMC);
ImmReleaseContext(hWnd, hIMC);
return result ? GLFW_TRUE : GLFW_FALSE;
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
////// GLFW native API ////// ////// GLFW native API //////

View File

@ -230,6 +230,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
{ {
if (wndconfig.centerCursor) if (wndconfig.centerCursor)
_glfwCenterCursorInContentArea(window); _glfwCenterCursorInContentArea(window);
window->preeditCursorPosX = 0;
window->preeditCursorPosY = height;
window->preeditCursorHeight = 0;
} }
else else
{ {
@ -239,6 +242,9 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
if (wndconfig.focused) if (wndconfig.focused)
_glfwPlatformFocusWindow(window); _glfwPlatformFocusWindow(window);
} }
window->preeditCursorPosX = 0;
window->preeditCursorPosY = height;
window->preeditCursorHeight = 0;
} }
return (GLFWwindow*) window; return (GLFWwindow*) window;
@ -465,7 +471,11 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
*prev = window->next; *prev = window->next;
} }
// Clear memory for preedit text
if (window->preeditText)
free(window->preeditText);
if (window->preeditAttributeBlocks)
free(window->preeditAttributeBlocks);
free(window); free(window);
} }

View File

@ -201,6 +201,16 @@ typedef struct _GLFWwindowX11
// The time of the last KeyPress event // The time of the last KeyPress event
Time lastKeyTime; Time lastKeyTime;
// Preedit callbacks
XIMCallback preeditStartCallback;
XIMCallback preeditDoneCallback;
XIMCallback preeditDrawCallback;
XIMCallback preeditCaretCallback;
XIMCallback statusStartCallback;
XIMCallback statusDoneCallback;
XIMCallback statusDrawCallback;
int imeFocus;
} _GLFWwindowX11; } _GLFWwindowX11;
// X11-specific global data // X11-specific global data

View File

@ -50,6 +50,9 @@
#define _GLFW_XDND_VERSION 5 #define _GLFW_XDND_VERSION 5
#if defined(X_HAVE_UTF8_STRING)
static unsigned int decodeUTF8(const char** s);
#endif
// Wait for data to arrive using select // Wait for data to arrive using select
// This avoids blocking other threads via the per-display Xlib lock that also // This avoids blocking other threads via the per-display Xlib lock that also
@ -587,6 +590,196 @@ static void enableCursor(_GLFWwindow* window)
updateCursorImage(window); updateCursorImage(window);
} }
// Update cursor position to decide candidate window
static void _ximChangeCursorPosition(XIC xic, _GLFWwindow* window)
{
XVaNestedList preedit_attr;
XPoint spot;
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)
static void _ximPreeditStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME Done callback (do nothing)
static void _ximPreeditDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
// IME Draw callback
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;
// keep cursor position to reduce API call
int cursorX = window->preeditCursorPosX;
int cursorY = window->preeditCursorPosY;
int cursorHeight = window->preeditCursorHeight;
if (!callData->text) {
// preedit text is empty
window->ntext = 0;
window->nblocks = 0;
_glfwInputPreedit(window, 0);
return;
} else {
text = callData->text;
length = callData->chg_length;
if (text->encoding_is_wchar) {
// wchar is not supported
return;
}
ctext = window->ctext;
while (ctext < length+1) {
ctext = (ctext == 0) ? 1 : ctext * 2;
}
if (ctext != window->ctext) {
preeditText = realloc(window->preeditText, sizeof(unsigned int)*ctext);
if (preeditText == NULL) {
return;
}
window->preeditText = preeditText;
window->ctext = ctext;
}
window->ntext = length;
window->preeditText[length] = 0;
if (window->cblocks == 0) {
window->preeditAttributeBlocks = malloc(sizeof(int)*4);
window->cblocks = 4;
}
src = text->string.multi_byte;
rend = 0;
rstart = length;
for (i = 0, j = 0; i < text->length; i++) {
#if defined(X_HAVE_UTF8_STRING)
codePoint = decodeUTF8(&src);
#else
codePoint = *src;
src++;
#endif
if (i < callData->chg_first || callData->chg_first+length < i) {
continue;
}
window->preeditText[j++] = codePoint;
f = text->feedback[i];
if ((f & XIMReverse) || (f & XIMHighlight)) {
rend = i;
if (i < rstart) {
rstart = i;
}
}
}
if (rstart == length) {
window->nblocks = 1;
window->preeditAttributeBlocks[0] = length;
window->preeditAttributeBlocks[1] = 0;
_glfwInputPreedit(window, 0);
} else if (rstart == 0) {
if (rend == length -1) {
window->nblocks = 1;
window->preeditAttributeBlocks[0] = length;
window->preeditAttributeBlocks[1] = 0;
_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) {
window->nblocks = 2;
window->preeditAttributeBlocks[0] = rstart;
window->preeditAttributeBlocks[1] = length - rstart;
window->preeditAttributeBlocks[2] = 0;
_glfwInputPreedit(window, 1);
} else {
window->nblocks = 3;
window->preeditAttributeBlocks[0] = rstart;
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)
|| (cursorHeight != window->preeditCursorHeight)) {
_ximChangeCursorPosition(xic, window);
}
}
}
// IME Caret callback (do nothing)
static void _ximPreeditCaretCallback(XIC xic, XPointer clientData, XPointer callData)
{
}
static void _ximStatusStartCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
window->x11.imeFocus = GLFW_TRUE;
}
static void _ximStatusDoneCallback(XIC xic, XPointer clientData, XPointer callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
window->x11.imeFocus = GLFW_FALSE;
}
static void _ximStatusDrawCallback(XIC xic, XPointer clientData, XIMStatusDrawCallbackStruct* callData)
{
_GLFWwindow* window = (_GLFWwindow*)clientData;
_glfwInputIMEStatus(window);
}
// Create XIM Preedit callback
static XVaNestedList _createXIMPreeditCallbacks(_GLFWwindow* window)
{
window->x11.preeditStartCallback.client_data = (XPointer)window;
window->x11.preeditStartCallback.callback = (XIMProc)_ximPreeditStartCallback;
window->x11.preeditDoneCallback.client_data = (XPointer)window;
window->x11.preeditDoneCallback.callback = (XIMProc)_ximPreeditDoneCallback;
window->x11.preeditDrawCallback.client_data = (XPointer)window;
window->x11.preeditDrawCallback.callback = (XIMProc)_ximPreeditDrawCallback;
window->x11.preeditCaretCallback.client_data = (XPointer)window;
window->x11.preeditCaretCallback.callback = (XIMProc)_ximPreeditCaretCallback;
return XVaCreateNestedList (0,
XNPreeditStartCallback, &window->x11.preeditStartCallback.client_data,
XNPreeditDoneCallback, &window->x11.preeditDoneCallback.client_data,
XNPreeditDrawCallback, &window->x11.preeditDrawCallback.client_data,
XNPreeditCaretCallback, &window->x11.preeditCaretCallback.client_data,
NULL);
}
// Create XIM status callback
static XVaNestedList _createXIMStatusCallbacks(_GLFWwindow* window)
{
window->x11.statusStartCallback.client_data = (XPointer)window;
window->x11.statusStartCallback.callback = (XIMProc)_ximStatusStartCallback;
window->x11.statusDoneCallback.client_data = (XPointer)window;
window->x11.statusDoneCallback.callback = (XIMProc)_ximStatusDoneCallback;
window->x11.statusDrawCallback.client_data = (XPointer)window;
window->x11.statusDrawCallback.callback = (XIMProc)_ximStatusDrawCallback;
return XVaCreateNestedList (0,
XNStatusStartCallback, &window->x11.statusStartCallback.client_data,
XNStatusDoneCallback, &window->x11.statusDoneCallback.client_data,
XNStatusDrawCallback, &window->x11.statusDrawCallback.client_data,
NULL);
}
// Create the X11 window (and its colormap) // Create the X11 window (and its colormap)
// //
static GLFWbool createNativeWindow(_GLFWwindow* window, static GLFWbool createNativeWindow(_GLFWwindow* window,
@ -774,14 +967,23 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
if (_glfw.x11.im) if (_glfw.x11.im)
{ {
XVaNestedList preeditList = _createXIMPreeditCallbacks(window);
XVaNestedList statusList = _createXIMStatusCallbacks(window);
window->x11.ic = XCreateIC(_glfw.x11.im, window->x11.ic = XCreateIC(_glfw.x11.im,
XNInputStyle, XNInputStyle,
XIMPreeditNothing | XIMStatusNothing, XIMPreeditCallbacks | XIMStatusCallbacks,
XNClientWindow, XNClientWindow,
window->x11.handle, window->x11.handle,
XNFocusWindow, XNFocusWindow,
window->x11.handle, window->x11.handle,
XNPreeditAttributes,
preeditList,
XNStatusAttributes,
statusList,
NULL); NULL);
XFree(preeditList);
XFree(statusList);
window->x11.imeFocus = GLFW_FALSE;
} }
_glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos); _glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos);
@ -3046,6 +3248,46 @@ VkResult _glfwPlatformCreateWindowSurface(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 //////

View File

@ -26,6 +26,7 @@ add_executable(iconify iconify.c ${GETOPT} ${GLAD})
add_executable(monitors monitors.c ${GETOPT} ${GLAD}) add_executable(monitors monitors.c ${GETOPT} ${GLAD})
add_executable(reopen reopen.c ${GLAD}) add_executable(reopen reopen.c ${GLAD})
add_executable(cursor cursor.c ${GLAD}) add_executable(cursor cursor.c ${GLAD})
add_executable(ime ime.c ${GETOPT} ${GLAD})
add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD} ${GLAD}) add_executable(empty WIN32 MACOSX_BUNDLE empty.c ${TINYCTHREAD} ${GLAD})
add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD}) add_executable(gamma WIN32 MACOSX_BUNDLE gamma.c ${GLAD})
@ -49,7 +50,7 @@ endif()
set(WINDOWS_BINARIES empty gamma icon inputlag joysticks opacity tearing set(WINDOWS_BINARIES empty gamma icon inputlag joysticks opacity tearing
threads timeout title windows) threads timeout title windows)
set(CONSOLE_BINARIES clipboard events msaa glfwinfo iconify monitors reopen set(CONSOLE_BINARIES clipboard events msaa glfwinfo iconify monitors reopen
cursor) cursor ime)
if (VULKAN_FOUND) if (VULKAN_FOUND)
add_executable(vulkan WIN32 vulkan.c ${ICON}) add_executable(vulkan WIN32 vulkan.c ${ICON})

View File

@ -429,6 +429,45 @@ static void char_callback(GLFWwindow* window, unsigned int codepoint)
get_character_string(codepoint)); get_character_string(codepoint));
} }
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
Slot* slot = glfwGetWindowUserPointer(window);
int i, blockIndex = -1, blockCount = 0;
int width, height;
printf("%08x to %i at %0.3f: Preedit text ",
counter++, slot->number, glfwGetTime());
if (strLength == 0 || blockLength == 0) {
printf("(empty)\n");
} else {
for (i = 0; i < strLength; i++) {
if (blockCount == 0) {
if (blockIndex == focusedBlock) {
printf("]");
}
blockIndex++;
blockCount = blocks[blockIndex];
printf("\n block %d: ", blockIndex);
if (blockIndex == focusedBlock) {
printf("[");
}
}
printf("%s", get_character_string(string[i]));
blockCount--;
}
if (blockIndex == focusedBlock) {
printf("]");
}
printf("\n");
glfwGetWindowSize(window, &width, &height);
glfwSetPreeditCursorPos(window, width/2, height/2, 20);
}
}
static void ime_callback(GLFWwindow* window) {
Slot* slot = glfwGetWindowUserPointer(window);
printf("%08x to %i at %0.3f: IME switched\n",
counter++, slot->number, glfwGetTime());
}
static void drop_callback(GLFWwindow* window, int count, const char** paths) static void drop_callback(GLFWwindow* window, int count, const char** paths)
{ {
int i; int i;
@ -601,6 +640,8 @@ int main(int argc, char** argv)
glfwSetScrollCallback(slots[i].window, scroll_callback); glfwSetScrollCallback(slots[i].window, scroll_callback);
glfwSetKeyCallback(slots[i].window, key_callback); glfwSetKeyCallback(slots[i].window, key_callback);
glfwSetCharCallback(slots[i].window, char_callback); glfwSetCharCallback(slots[i].window, char_callback);
glfwSetPreeditCallback(slots[i].window, preedit_callback);
glfwSetIMEStatusCallback(slots[i].window, ime_callback);
glfwSetDropCallback(slots[i].window, drop_callback); glfwSetDropCallback(slots[i].window, drop_callback);
glfwMakeContextCurrent(slots[i].window); glfwMakeContextCurrent(slots[i].window);

508
tests/ime.c Normal file
View File

@ -0,0 +1,508 @@
//========================================================================
// Event linter (event spewer)
// Copyright (c) Camilla Berglund <elmindreda@elmindreda.org>
//
// 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.
//
//========================================================================
//
// This test hooks every available callback and outputs their arguments
//
// Log messages go to stdout, error messages to stderr
//
// Every event also gets a (sequential) number to aid discussion of logs
//
//========================================================================
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <locale.h>
#include "getopt.h"
// Event index
static unsigned int counter = 0;
// Mouse position
static int posX = 0, posY = 0;
typedef struct
{
GLFWwindow* window;
int number;
int closeable;
} Slot;
static void usage(void)
{
printf("Usage: ime [-f] [-h] [-n WINDOWS]\n");
printf("Options:\n");
printf(" -f use full screen\n");
printf(" -h show this help\n");
printf(" -n the number of windows to create\n");
}
static const char* get_key_name(int key)
{
switch (key)
{
// Printable keys
case GLFW_KEY_A: return "A";
case GLFW_KEY_B: return "B";
case GLFW_KEY_C: return "C";
case GLFW_KEY_D: return "D";
case GLFW_KEY_E: return "E";
case GLFW_KEY_F: return "F";
case GLFW_KEY_G: return "G";
case GLFW_KEY_H: return "H";
case GLFW_KEY_I: return "I";
case GLFW_KEY_J: return "J";
case GLFW_KEY_K: return "K";
case GLFW_KEY_L: return "L";
case GLFW_KEY_M: return "M";
case GLFW_KEY_N: return "N";
case GLFW_KEY_O: return "O";
case GLFW_KEY_P: return "P";
case GLFW_KEY_Q: return "Q";
case GLFW_KEY_R: return "R";
case GLFW_KEY_S: return "S";
case GLFW_KEY_T: return "T";
case GLFW_KEY_U: return "U";
case GLFW_KEY_V: return "V";
case GLFW_KEY_W: return "W";
case GLFW_KEY_X: return "X";
case GLFW_KEY_Y: return "Y";
case GLFW_KEY_Z: return "Z";
case GLFW_KEY_1: return "1";
case GLFW_KEY_2: return "2";
case GLFW_KEY_3: return "3";
case GLFW_KEY_4: return "4";
case GLFW_KEY_5: return "5";
case GLFW_KEY_6: return "6";
case GLFW_KEY_7: return "7";
case GLFW_KEY_8: return "8";
case GLFW_KEY_9: return "9";
case GLFW_KEY_0: return "0";
case GLFW_KEY_SPACE: return "SPACE";
case GLFW_KEY_MINUS: return "MINUS";
case GLFW_KEY_EQUAL: return "EQUAL";
case GLFW_KEY_LEFT_BRACKET: return "LEFT BRACKET";
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT BRACKET";
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
case GLFW_KEY_SEMICOLON: return "SEMICOLON";
case GLFW_KEY_APOSTROPHE: return "APOSTROPHE";
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE ACCENT";
case GLFW_KEY_COMMA: return "COMMA";
case GLFW_KEY_PERIOD: return "PERIOD";
case GLFW_KEY_SLASH: return "SLASH";
case GLFW_KEY_WORLD_1: return "WORLD 1";
case GLFW_KEY_WORLD_2: return "WORLD 2";
// Function keys
case GLFW_KEY_ESCAPE: return "ESCAPE";
case GLFW_KEY_F1: return "F1";
case GLFW_KEY_F2: return "F2";
case GLFW_KEY_F3: return "F3";
case GLFW_KEY_F4: return "F4";
case GLFW_KEY_F5: return "F5";
case GLFW_KEY_F6: return "F6";
case GLFW_KEY_F7: return "F7";
case GLFW_KEY_F8: return "F8";
case GLFW_KEY_F9: return "F9";
case GLFW_KEY_F10: return "F10";
case GLFW_KEY_F11: return "F11";
case GLFW_KEY_F12: return "F12";
case GLFW_KEY_F13: return "F13";
case GLFW_KEY_F14: return "F14";
case GLFW_KEY_F15: return "F15";
case GLFW_KEY_F16: return "F16";
case GLFW_KEY_F17: return "F17";
case GLFW_KEY_F18: return "F18";
case GLFW_KEY_F19: return "F19";
case GLFW_KEY_F20: return "F20";
case GLFW_KEY_F21: return "F21";
case GLFW_KEY_F22: return "F22";
case GLFW_KEY_F23: return "F23";
case GLFW_KEY_F24: return "F24";
case GLFW_KEY_F25: return "F25";
case GLFW_KEY_UP: return "UP";
case GLFW_KEY_DOWN: return "DOWN";
case GLFW_KEY_LEFT: return "LEFT";
case GLFW_KEY_RIGHT: return "RIGHT";
case GLFW_KEY_LEFT_SHIFT: return "LEFT SHIFT";
case GLFW_KEY_RIGHT_SHIFT: return "RIGHT SHIFT";
case GLFW_KEY_LEFT_CONTROL: return "LEFT CONTROL";
case GLFW_KEY_RIGHT_CONTROL: return "RIGHT CONTROL";
case GLFW_KEY_LEFT_ALT: return "LEFT ALT";
case GLFW_KEY_RIGHT_ALT: return "RIGHT ALT";
case GLFW_KEY_TAB: return "TAB";
case GLFW_KEY_ENTER: return "ENTER";
case GLFW_KEY_BACKSPACE: return "BACKSPACE";
case GLFW_KEY_INSERT: return "INSERT";
case GLFW_KEY_DELETE: return "DELETE";
case GLFW_KEY_PAGE_UP: return "PAGE UP";
case GLFW_KEY_PAGE_DOWN: return "PAGE DOWN";
case GLFW_KEY_HOME: return "HOME";
case GLFW_KEY_END: return "END";
case GLFW_KEY_KP_0: return "KEYPAD 0";
case GLFW_KEY_KP_1: return "KEYPAD 1";
case GLFW_KEY_KP_2: return "KEYPAD 2";
case GLFW_KEY_KP_3: return "KEYPAD 3";
case GLFW_KEY_KP_4: return "KEYPAD 4";
case GLFW_KEY_KP_5: return "KEYPAD 5";
case GLFW_KEY_KP_6: return "KEYPAD 6";
case GLFW_KEY_KP_7: return "KEYPAD 7";
case GLFW_KEY_KP_8: return "KEYPAD 8";
case GLFW_KEY_KP_9: return "KEYPAD 9";
case GLFW_KEY_KP_DIVIDE: return "KEYPAD DIVIDE";
case GLFW_KEY_KP_MULTIPLY: return "KEYPAD MULTPLY";
case GLFW_KEY_KP_SUBTRACT: return "KEYPAD SUBTRACT";
case GLFW_KEY_KP_ADD: return "KEYPAD ADD";
case GLFW_KEY_KP_DECIMAL: return "KEYPAD DECIMAL";
case GLFW_KEY_KP_EQUAL: return "KEYPAD EQUAL";
case GLFW_KEY_KP_ENTER: return "KEYPAD ENTER";
case GLFW_KEY_PRINT_SCREEN: return "PRINT SCREEN";
case GLFW_KEY_NUM_LOCK: return "NUM LOCK";
case GLFW_KEY_CAPS_LOCK: return "CAPS LOCK";
case GLFW_KEY_SCROLL_LOCK: return "SCROLL LOCK";
case GLFW_KEY_PAUSE: return "PAUSE";
case GLFW_KEY_LEFT_SUPER: return "LEFT SUPER";
case GLFW_KEY_RIGHT_SUPER: return "RIGHT SUPER";
case GLFW_KEY_MENU: return "MENU";
default: return "UNKNOWN";
}
}
static const char* get_action_name(int action)
{
switch (action)
{
case GLFW_PRESS:
return "pressed";
case GLFW_RELEASE:
return "released";
case GLFW_REPEAT:
return "repeated";
}
return "caused unknown action";
}
static const char* get_mods_name(int mods)
{
static char name[512];
if (mods == 0)
return " no mods";
name[0] = '\0';
if (mods & GLFW_MOD_SHIFT)
strcat(name, " shift");
if (mods & GLFW_MOD_CONTROL)
strcat(name, " control");
if (mods & GLFW_MOD_ALT)
strcat(name, " alt");
if (mods & GLFW_MOD_SUPER)
strcat(name, " super");
return name;
}
static const char* get_character_string(int codepoint)
{
// This assumes UTF-8, which is stupid
static char result[6 + 1];
int length = wctomb(result, codepoint);
if (length == -1)
length = 0;
result[length] = '\0';
return result;
}
static void error_callback(int error, const char* description)
{
fprintf(stderr, "Error: %s\n", description);
}
static void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
{
Slot* slot = glfwGetWindowUserPointer(window);
int currentIMEstatus;
int x, y;
if (action == GLFW_PRESS) {
if (button == GLFW_MOUSE_BUTTON_LEFT) {
currentIMEstatus = glfwGetInputMode(window, GLFW_IME);
glfwSetInputMode(window, GLFW_IME, 1-currentIMEstatus);
glfwResetPreeditText(window);
printf("%08x to %i at %0.3f: Reset preedit text and IME status -> %s\n",
counter++, slot->number, glfwGetTime(), currentIMEstatus ? "OFF" : "ON");
} else if (button == GLFW_MOUSE_BUTTON_RIGHT) {
glfwGetPreeditCursorPos(window, &x, &y, NULL);
glfwSetPreeditCursorPos(window, posX, posY, 20);
printf("%08x to %i at %0.3f: Move preedit text cursor position (%d, %d) -> (%d, %d)\n",
counter++, slot->number, glfwGetTime(), x, y, posX, posY);
}
}
}
static void cursor_position_callback(GLFWwindow* window, double x, double y)
{
posX = x;
posY = y;
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
Slot* slot = glfwGetWindowUserPointer(window);
const char* name = glfwGetKeyName(key, scancode);
if (name)
{
printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (%s) (with%s) was %s\n",
counter++, slot->number, glfwGetTime(), key, scancode,
get_key_name(key),
name,
get_mods_name(mods),
get_action_name(action));
}
else
{
printf("%08x to %i at %0.3f: Key 0x%04x Scancode 0x%04x (%s) (with%s) was %s\n",
counter++, slot->number, glfwGetTime(), key, scancode,
get_key_name(key),
get_mods_name(mods),
get_action_name(action));
}
if (action != GLFW_PRESS)
return;
switch (key)
{
case GLFW_KEY_C:
{
slot->closeable = !slot->closeable;
printf("(( closing %s ))\n", slot->closeable ? "enabled" : "disabled");
break;
}
}
}
static void char_callback(GLFWwindow* window, unsigned int codepoint)
{
Slot* slot = glfwGetWindowUserPointer(window);
printf("%08x to %i at %0.3f: Character 0x%08x (%s) input\n",
counter++, slot->number, glfwGetTime(), codepoint,
get_character_string(codepoint));
}
static void char_mods_callback(GLFWwindow* window, unsigned int codepoint, int mods)
{
Slot* slot = glfwGetWindowUserPointer(window);
printf("%08x to %i at %0.3f: Character 0x%08x (%s) with modifiers (with%s) input\n",
counter++, slot->number, glfwGetTime(), codepoint,
get_character_string(codepoint),
get_mods_name(mods));
}
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
Slot* slot = glfwGetWindowUserPointer(window);
int i, blockIndex = -1, blockCount = 0;
int width, height;
printf("%08x to %i at %0.3f: Preedit text ",
counter++, slot->number, glfwGetTime());
if (strLength == 0 || blockLength == 0) {
printf("(empty)\n");
} else {
for (i = 0; i < strLength; i++) {
if (blockCount == 0) {
if (blockIndex == focusedBlock) {
printf("]");
}
blockIndex++;
blockCount = blocks[blockIndex];
printf("\n block %d: ", blockIndex);
if (blockIndex == focusedBlock) {
printf("[");
}
}
printf("%s", get_character_string(string[i]));
blockCount--;
}
if (blockIndex == focusedBlock) {
printf("]");
}
printf("\n");
glfwGetWindowSize(window, &width, &height);
}
}
static void ime_callback(GLFWwindow* window) {
Slot* slot = glfwGetWindowUserPointer(window);
printf("%08x to %i at %0.3f: IME switched\n",
counter++, slot->number, glfwGetTime());
}
int main(int argc, char** argv)
{
Slot* slots;
GLFWmonitor* monitor = NULL;
int ch, i, width, height, count = 1;
setlocale(LC_ALL, "");
glfwSetErrorCallback(error_callback);
if (!glfwInit())
exit(EXIT_FAILURE);
printf("Library initialized\n");
while ((ch = getopt(argc, argv, "hfn:")) != -1)
{
switch (ch)
{
case 'h':
usage();
exit(EXIT_SUCCESS);
case 'f':
monitor = glfwGetPrimaryMonitor();
break;
case 'n':
count = (int) strtol(optarg, NULL, 10);
break;
default:
usage();
exit(EXIT_FAILURE);
}
}
if (monitor)
{
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwWindowHint(GLFW_REFRESH_RATE, mode->refreshRate);
glfwWindowHint(GLFW_RED_BITS, mode->redBits);
glfwWindowHint(GLFW_GREEN_BITS, mode->greenBits);
glfwWindowHint(GLFW_BLUE_BITS, mode->blueBits);
width = mode->width;
height = mode->height;
}
else
{
width = 640;
height = 480;
}
if (!count)
{
fprintf(stderr, "Invalid user\n");
exit(EXIT_FAILURE);
}
slots = calloc(count, sizeof(Slot));
for (i = 0; i < count; i++)
{
char title[128];
slots[i].closeable = GLFW_TRUE;
slots[i].number = i + 1;
sprintf(title, "Event Linter (Window %i)", slots[i].number);
if (monitor)
{
printf("Creating full screen window %i (%ix%i on %s)\n",
slots[i].number,
width, height,
glfwGetMonitorName(monitor));
}
else
{
printf("Creating windowed mode window %i (%ix%i)\n",
slots[i].number,
width, height);
}
slots[i].window = glfwCreateWindow(width, height, title, monitor, NULL);
if (!slots[i].window)
{
free(slots);
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwSetWindowUserPointer(slots[i].window, slots + i);
glfwSetMouseButtonCallback(slots[i].window, mouse_button_callback);
glfwSetCursorPosCallback(slots[i].window, cursor_position_callback);
glfwSetKeyCallback(slots[i].window, key_callback);
glfwSetCharCallback(slots[i].window, char_callback);
glfwSetCharModsCallback(slots[i].window, char_mods_callback);
glfwSetPreeditCallback(slots[i].window, preedit_callback);
glfwSetIMEStatusCallback(slots[i].window, ime_callback);
glfwMakeContextCurrent(slots[i].window);
gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
glfwSwapInterval(1);
}
printf("Main loop starting\n");
printf("Left Mouse Button: toggle IME\n");
printf("Right Mouse Button: set preedit cursor position\n\n");
for (;;)
{
for (i = 0; i < count; i++)
{
if (glfwWindowShouldClose(slots[i].window))
break;
}
if (i < count)
break;
glfwWaitEvents();
// Workaround for an issue with msvcrt and mintty
fflush(stdout);
}
free(slots);
glfwTerminate();
exit(EXIT_SUCCESS);
}