Skip to content

Commit

Permalink
IME Composition Truncation + SDL_IsTextInputShown + SDL_ClearComposit…
Browse files Browse the repository at this point in the history
…ion (#5398)

* Fixes for IME Composition Truncation + Addition of SDL_ClearComposition, SDL_IsTextInputShown

* Fixed: Documentation and code style issues raised during code review.
  • Loading branch information
zreedy committed Mar 11, 2022
1 parent 9de97e1 commit d14a126
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 12 deletions.
15 changes: 15 additions & 0 deletions include/SDL_events.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ typedef enum
SDL_KEYMAPCHANGED, /**< Keymap changed due to a system event such as an
input language or keyboard layout change.
*/
SDL_TEXTEDITING_EXT, /**< Extended keyboard text editing (composition) */

/* Mouse events */
SDL_MOUSEMOTION = 0x400, /**< Mouse moved */
Expand Down Expand Up @@ -243,6 +244,19 @@ typedef struct SDL_TextEditingEvent
Sint32 length; /**< The length of selected editing text */
} SDL_TextEditingEvent;

/**
* \brief Extended keyboard text editing event structure (event.editExt.*) when text would be
* truncated if stored in the text buffer SDL_TextEditingEvent
*/
typedef struct SDL_TextEditingExtEvent
{
Uint32 type; /**< ::SDL_TEXTEDITING_EXT */
Uint32 timestamp; /**< In milliseconds, populated using SDL_GetTicks() */
Uint32 windowID; /**< The window with keyboard focus, if any */
char* text; /**< The editing text, which should be freed with SDL_free(), and will not be NULL */
Sint32 start; /**< The start cursor of selected editing text */
Sint32 length; /**< The length of selected editing text */
} SDL_TextEditingExtEvent;

#define SDL_TEXTINPUTEVENT_TEXT_SIZE (32)
/**
Expand Down Expand Up @@ -601,6 +615,7 @@ typedef union SDL_Event
SDL_WindowEvent window; /**< Window event data */
SDL_KeyboardEvent key; /**< Keyboard event data */
SDL_TextEditingEvent edit; /**< Text editing event data */
SDL_TextEditingExtEvent editExt; /**< Extended text editing event data */
SDL_TextInputEvent text; /**< Text input event data */
SDL_MouseMotionEvent motion; /**< Mouse motion event data */
SDL_MouseButtonEvent button; /**< Mouse button event data */
Expand Down
11 changes: 11 additions & 0 deletions include/SDL_hints.h
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,17 @@ extern "C" {
*/
#define SDL_HINT_IME_SHOW_UI "SDL_IME_SHOW_UI"

/**
* \brief A variable to control if extended IME text support is enabled.
* If enabled then SDL_TextEditingExtEvent will be issued if the text would be truncated otherwise.
* Additionally SDL_TextInputEvent will be dispatched multiple times so that it is not truncated.
*
* The variable can be set to the following values:
* "0" - Legacy behavior. Text can be truncated, no heap allocations. (default)
* "1" - Modern behavior.
*/
#define SDL_HINT_IME_SUPPORT_EXTENDED_TEXT "SDL_IME_SUPPORT_EXTENDED_TEXT"

/**
* \brief A variable controlling whether the home indicator bar on iPhone X
* should be hidden.
Expand Down
18 changes: 18 additions & 0 deletions include/SDL_keyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,24 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputActive(void);
*/
extern DECLSPEC void SDLCALL SDL_StopTextInput(void);

/**
* Dismiss the composition window/IME without disabling the subsystem.
*
* \since This function is available since SDL 2.0.22
*
* \sa SDL_StartTextInput
* \sa SDL_StopTextInput
*/
extern DECLSPEC void SDLCALL SDL_ClearComposition(void);

/**
* Returns if an IME Composite or Candidate window is currently shown.
*
* \since This function is available since SDL 2.0.22
*
*/
extern DECLSPEC SDL_bool SDLCALL SDL_IsTextInputShown(void);

/**
* Set the rectangle used to type Unicode text inputs.
*
Expand Down
2 changes: 2 additions & 0 deletions src/dynapi/SDL_dynapi_overrides.h
Original file line number Diff line number Diff line change
Expand Up @@ -857,3 +857,5 @@
#define SDL_PremultiplyAlpha SDL_PremultiplyAlpha_REAL
#define SDL_AndroidSendMessage SDL_AndroidSendMessage_REAL
#define SDL_GetTouchName SDL_GetTouchName_REAL
#define SDL_ClearComposition SDL_ClearComposition_REAL
#define SDL_IsTextInputShown SDL_IsTextInputShown_REAL
2 changes: 2 additions & 0 deletions src/dynapi/SDL_dynapi_procs.h
Original file line number Diff line number Diff line change
Expand Up @@ -928,3 +928,5 @@ SDL_DYNAPI_PROC(int,SDL_PremultiplyAlpha,(int a, int b, Uint32 c, const void *d,
SDL_DYNAPI_PROC(int,SDL_AndroidSendMessage,(Uint32 a, int b),(a,b),return)
#endif
SDL_DYNAPI_PROC(const char*,SDL_GetTouchName,(int a),(a),return)
SDL_DYNAPI_PROC(void,SDL_ClearComposition,(void),(),)
SDL_DYNAPI_PROC(SDL_bool,SDL_IsTextInputShown,(void),(),return)
16 changes: 16 additions & 0 deletions src/events/SDL_keyboard.c
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,22 @@ SDL_SendEditingText(const char *text, int start, int length)
event.edit.start = start;
event.edit.length = length;
SDL_utf8strlcpy(event.edit.text, text, SDL_arraysize(event.edit.text));

if (SDL_GetHintBoolean(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, SDL_FALSE) &&
SDL_strlen(text) > SDL_arraysize(event.text.text)) {
event.editExt.type = SDL_TEXTEDITING_EXT;
event.editExt.windowID = keyboard->focus ? keyboard->focus->id : 0;
event.editExt.text = text ? SDL_strdup(text) : NULL;
event.editExt.start = start;
event.editExt.length = length;
} else {
event.edit.type = SDL_TEXTEDITING;
event.edit.windowID = keyboard->focus ? keyboard->focus->id : 0;
event.edit.start = start;
event.edit.length = length;
SDL_utf8strlcpy(event.edit.text, text, SDL_arraysize(event.edit.text));
}

posted = (SDL_PushEvent(&event) > 0);
}
return (posted);
Expand Down
2 changes: 2 additions & 0 deletions src/video/SDL_sysvideo.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ struct SDL_VideoDevice
void (*StartTextInput) (_THIS);
void (*StopTextInput) (_THIS);
void (*SetTextInputRect) (_THIS, SDL_Rect *rect);
void (*ClearComposition) (_THIS);
SDL_bool (*IsTextInputShown) (_THIS);

/* Screen keyboard */
SDL_bool (*HasScreenKeyboardSupport) (_THIS);
Expand Down
18 changes: 18 additions & 0 deletions src/video/SDL_video.c
Original file line number Diff line number Diff line change
Expand Up @@ -4135,6 +4135,24 @@ SDL_StartTextInput(void)
}
}

void
SDL_ClearComposition(void)
{
if (_this && _this->ClearComposition) {
_this->ClearComposition(_this);
}
}

SDL_bool
SDL_IsTextInputShown(void)
{
if (_this && _this->IsTextInputShown) {
return _this->IsTextInputShown(_this);
}

return SDL_FALSE;
}

SDL_bool
SDL_IsTextInputActive(void)
{
Expand Down
129 changes: 118 additions & 11 deletions src/video/windows/SDL_windowskeyboard.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ static void IME_Init(SDL_VideoData *videodata, HWND hwnd);
static void IME_Enable(SDL_VideoData *videodata, HWND hwnd);
static void IME_Disable(SDL_VideoData *videodata, HWND hwnd);
static void IME_Quit(SDL_VideoData *videodata);
static void IME_ClearComposition(SDL_VideoData *videodata);
static SDL_bool IME_IsTextInputShown(SDL_VideoData* videodata);
#endif /* !SDL_DISABLE_WINDOWS_IME */

#ifndef MAPVK_VK_TO_VSC
Expand All @@ -62,6 +64,8 @@ WIN_InitKeyboard(_THIS)
data->ime_hwnd_main = 0;
data->ime_hwnd_current = 0;
data->ime_himc = 0;
data->ime_composition_length = 32 * sizeof(WCHAR);
data->ime_composition = (WCHAR*)SDL_malloc(data->ime_composition_length);
data->ime_composition[0] = 0;
data->ime_readingstring[0] = 0;
data->ime_cursor = 0;
Expand Down Expand Up @@ -272,6 +276,18 @@ WIN_SetTextInputRect(_THIS, SDL_Rect *rect)
}
}

void WIN_ClearComposition(_THIS)
{
SDL_VideoData *videodata = (SDL_VideoData *)_this->driverdata;
IME_ClearComposition(videodata);
}

SDL_bool WIN_IsTextInputShown(_THIS)
{
SDL_VideoData* videodata = (SDL_VideoData*)_this->driverdata;
return IME_IsTextInputShown(videodata);
}

static SDL_bool
WIN_ShouldShowNativeUI()
{
Expand Down Expand Up @@ -742,18 +758,51 @@ IME_ClearComposition(SDL_VideoData *videodata)
SDL_SendEditingText("", 0, 0);
}

static SDL_bool
IME_IsTextInputShown(SDL_VideoData* videodata)
{
BOOL result;
HIMC himc;

if (!videodata->ime_initialized || !videodata->ime_available || !videodata->ime_enabled)
return SDL_FALSE;

return videodata->ime_uicontext != 0 ? SDL_TRUE : SDL_FALSE;
}

static void
IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
{
LONG length = ImmGetCompositionStringW(himc, string, videodata->ime_composition, sizeof(videodata->ime_composition) - sizeof(videodata->ime_composition[0]));
LONG length;
DWORD dwLang = ((DWORD_PTR)videodata->ime_hkl & 0xffff);

length = ImmGetCompositionStringW(himc, string, NULL, 0);
if (length > 0 && videodata->ime_composition_length < length) {
if (videodata->ime_composition != NULL)
SDL_free(videodata->ime_composition);

videodata->ime_composition = (WCHAR*)SDL_malloc(length + sizeof(WCHAR));
videodata->ime_composition_length = length;
}

length = ImmGetCompositionStringW(
himc,
string,
videodata->ime_composition,
videodata->ime_composition_length
);

if (length < 0)
length = 0;

length /= sizeof(videodata->ime_composition[0]);
length /= sizeof(WCHAR);
videodata->ime_cursor = LOWORD(ImmGetCompositionStringW(himc, GCS_CURSORPOS, 0, 0));
if (videodata->ime_cursor > 0 &&
videodata->ime_cursor < SDL_arraysize(videodata->ime_composition) &&
videodata->ime_composition[videodata->ime_cursor] == 0x3000) {
if ((dwLang == LANG_CHT || dwLang == LANG_CHS) &&
videodata->ime_cursor > 0 &&
videodata->ime_cursor < videodata->ime_composition_length / sizeof(WCHAR) &&
(videodata->ime_composition[0] == 0x3000 || videodata->ime_composition[0] == 0x0020)) {
// Traditional Chinese IMEs add a placeholder U+3000
// Simplified Chinese IMEs seem to add a placholder U+0020 sometimes
int i;
for (i = videodata->ime_cursor + 1; i < length; ++i)
videodata->ime_composition[i - 1] = videodata->ime_composition[i];
Expand All @@ -762,6 +811,39 @@ IME_GetCompositionString(SDL_VideoData *videodata, HIMC himc, DWORD string)
}

videodata->ime_composition[length] = 0;

// Get the correct caret position if we've selected a candidate from the candidate window
if (videodata->ime_cursor == 0 && length > 0) {
Sint32 start = 0;
Sint32 end = 0;

length = ImmGetCompositionStringW(himc, GCS_COMPATTR, NULL, 0);
if (length > 0) {
Uint8* attributes = (Uint8*)SDL_malloc(length);
ImmGetCompositionString(himc, GCS_COMPATTR, attributes, length);

for (start = 0; start < length; ++start) {
if (attributes[start] == ATTR_TARGET_CONVERTED ||
attributes[start] == ATTR_TARGET_NOTCONVERTED)
break;
}

for (end = start; end < length; ++end) {
if (attributes[end] != ATTR_TARGET_CONVERTED &&
attributes[end] != ATTR_TARGET_NOTCONVERTED)
break;
}

if (start == length) {
start = 0;
end = length;
}

SDL_free(attributes);
}

videodata->ime_cursor = end;
}
}

static void
Expand All @@ -780,22 +862,30 @@ IME_SendInputEvent(SDL_VideoData *videodata)
static void
IME_SendEditingEvent(SDL_VideoData *videodata)
{
char *s = 0;
WCHAR buffer[SDL_TEXTEDITINGEVENT_TEXT_SIZE];
const size_t size = SDL_arraysize(buffer);
buffer[0] = 0;
char *s = NULL;
WCHAR *buffer = NULL;
size_t size = videodata->ime_composition_length;
if (videodata->ime_readingstring[0]) {
size_t len = SDL_min(SDL_wcslen(videodata->ime_composition), (size_t)videodata->ime_cursor);

size += sizeof(videodata->ime_readingstring);
buffer = (WCHAR*)SDL_malloc(size);
buffer[0] = 0;

SDL_wcslcpy(buffer, videodata->ime_composition, len + 1);
SDL_wcslcat(buffer, videodata->ime_readingstring, size);
SDL_wcslcat(buffer, &videodata->ime_composition[len], size);
}
else {
buffer = (WCHAR*)SDL_malloc(size);
buffer[0] = 0;
SDL_wcslcpy(buffer, videodata->ime_composition, size);
}

s = WIN_StringToUTF8W(buffer);
SDL_SendEditingText(s, videodata->ime_cursor + (int)SDL_wcslen(videodata->ime_readingstring), 0);
SDL_free(s);
SDL_free(buffer);
}

static void
Expand Down Expand Up @@ -914,6 +1004,15 @@ IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoD
return SDL_FALSE;

switch (msg) {
case WM_KEYDOWN:
if (wParam == VK_PROCESSKEY)
{
videodata->ime_uicontext = 1;
trap = SDL_TRUE;
}
else
videodata->ime_uicontext = 0;
break;
case WM_INPUTLANGCHANGE:
IME_InputLangChanged(videodata);
break;
Expand All @@ -922,14 +1021,17 @@ IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoD
*lParam = 0;
}
break;
case WM_IME_STARTCOMPOSITION:
case WM_IME_STARTCOMPOSITION:
videodata->ime_suppress_endcomposition_event = SDL_FALSE;
trap = SDL_TRUE;
break;
case WM_IME_COMPOSITION:
trap = SDL_TRUE;
himc = ImmGetContext(hwnd);
if (*lParam & GCS_RESULTSTR) {
videodata->ime_suppress_endcomposition_event = SDL_TRUE;
IME_GetCompositionString(videodata, himc, GCS_RESULTSTR);
SDL_SendEditingText("", 0, 0);
IME_SendInputEvent(videodata);
}
if (*lParam & GCS_COMPSTR) {
Expand All @@ -942,10 +1044,13 @@ IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoD
ImmReleaseContext(hwnd, himc);
break;
case WM_IME_ENDCOMPOSITION:
videodata->ime_uicontext = 0;
videodata->ime_composition[0] = 0;
videodata->ime_readingstring[0] = 0;
videodata->ime_cursor = 0;
SDL_SendEditingText("", 0, 0);
if (videodata->ime_suppress_endcomposition_event == SDL_FALSE)
SDL_SendEditingText("", 0, 0);
videodata->ime_suppress_endcomposition_event = SDL_FALSE;
break;
case WM_IME_NOTIFY:
switch (wParam) {
Expand All @@ -959,10 +1064,12 @@ IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, SDL_VideoD
break;

trap = SDL_TRUE;
videodata->ime_uicontext = 1;
IME_GetCandidateList(hwnd, videodata);
break;
case IMN_CLOSECANDIDATE:
trap = SDL_TRUE;
videodata->ime_uicontext = 0;
IME_HideCandidateList(videodata);
break;
case IMN_PRIVATE:
Expand Down
2 changes: 2 additions & 0 deletions src/video/windows/SDL_windowskeyboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ extern void WIN_ResetDeadKeys(void);
extern void WIN_StartTextInput(_THIS);
extern void WIN_StopTextInput(_THIS);
extern void WIN_SetTextInputRect(_THIS, SDL_Rect *rect);
extern void WIN_ClearComposition(_THIS);
extern SDL_bool WIN_IsTextInputShown(_THIS);

extern SDL_bool IME_HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM *lParam, struct SDL_VideoData *videodata);

Expand Down
2 changes: 2 additions & 0 deletions src/video/windows/SDL_windowsvideo.c
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ WIN_CreateDevice(int devindex)
device->StartTextInput = WIN_StartTextInput;
device->StopTextInput = WIN_StopTextInput;
device->SetTextInputRect = WIN_SetTextInputRect;
device->ClearComposition = WIN_ClearComposition;
device->IsTextInputShown = WIN_IsTextInputShown;

device->SetClipboardText = WIN_SetClipboardText;
device->GetClipboardText = WIN_GetClipboardText;
Expand Down

0 comments on commit d14a126

Please sign in to comment.