Support IME composition strings of arbitrary length

Previously, IME composition strings were limited to at most 32 bytes, e.g. 10 Hiragana characters. Now, `SDL_TextEditingExtEvent` (`SDL_TEXTEDITING_EXT`) is supported, which uses heap-allocated composition strings of arbitrary length.

On the other hand, the buffer for input event text was larger than necessary, as SDL text events only contain at most 32 bytes (`SDL_TEXTINPUTEVENT_TEXT_SIZE` is not used directly to avoid the include). With the hint for extended IME handling enabled, long composition text will be sent as multiple text events (without breaking UTF-8).
This commit is contained in:
Robert Müller 2024-07-27 17:14:13 +02:00
parent 78cbb45d49
commit 8a3929d48c
4 changed files with 41 additions and 43 deletions

View file

@ -4562,6 +4562,11 @@ int main(int argc, const char **argv)
SDL_SetHint("SDL_TOUCH_MOUSE_EVENTS", "0"); SDL_SetHint("SDL_TOUCH_MOUSE_EVENTS", "0");
SDL_SetHint("SDL_MOUSE_TOUCH_EVENTS", "0"); SDL_SetHint("SDL_MOUSE_TOUCH_EVENTS", "0");
// Support longer IME composition strings (enables SDL_TEXTEDITING_EXT).
#if SDL_VERSION_ATLEAST(2, 0, 22)
SDL_SetHint(SDL_HINT_IME_SUPPORT_EXTENDED_TEXT, "1");
#endif
#if defined(CONF_PLATFORM_MACOS) #if defined(CONF_PLATFORM_MACOS)
// Hints will not be set if there is an existing override hint or environment variable that takes precedence. // Hints will not be set if there is an existing override hint or environment variable that takes precedence.
// So this respects cli environment overrides. // So this respects cli environment overrides.

View file

@ -70,7 +70,6 @@ CInput::CInput()
m_pClipboardText = nullptr; m_pClipboardText = nullptr;
m_CompositionLength = COMP_LENGTH_INACTIVE;
m_CompositionCursor = 0; m_CompositionCursor = 0;
m_CandidateSelectedIndex = -1; m_CandidateSelectedIndex = -1;
@ -328,9 +327,8 @@ void CInput::StopTextInput()
SDL_StopTextInput(); SDL_StopTextInput();
// disable system messages for performance // disable system messages for performance
SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE); SDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE);
m_CompositionLength = COMP_LENGTH_INACTIVE; m_CompositionString = "";
m_CompositionCursor = 0; m_CompositionCursor = 0;
m_aComposition[0] = '\0';
m_vCandidates.clear(); m_vCandidates.clear();
} }
@ -574,6 +572,26 @@ void CInput::HandleTouchMotionEvent(const SDL_TouchFingerEvent &Event)
} }
} }
void CInput::HandleTextEditingEvent(const char *pText, int Start, int Length)
{
if(pText[0] != '\0')
{
m_CompositionString = pText;
m_CompositionCursor = 0;
for(int i = 0; i < Start; i++)
{
m_CompositionCursor = str_utf8_forward(m_CompositionString.c_str(), m_CompositionCursor);
}
// Length is currently unused on Windows and will always be 0, so we don't support selecting composition text
AddTextEvent("");
}
else
{
m_CompositionString = "";
m_CompositionCursor = 0;
}
}
void CInput::SetCompositionWindowPosition(float X, float Y, float H) void CInput::SetCompositionWindowPosition(float X, float Y, float H)
{ {
SDL_Rect Rect; SDL_Rect Rect;
@ -658,29 +676,18 @@ int CInput::Update()
break; break;
case SDL_TEXTEDITING: case SDL_TEXTEDITING:
{ HandleTextEditingEvent(Event.edit.text, Event.edit.start, Event.edit.length);
m_CompositionLength = str_length(Event.edit.text);
if(m_CompositionLength)
{
str_copy(m_aComposition, Event.edit.text);
m_CompositionCursor = 0;
for(int i = 0; i < Event.edit.start; i++)
m_CompositionCursor = str_utf8_forward(m_aComposition, m_CompositionCursor);
// Event.edit.length is currently unused on Windows and will always be 0, so we don't support selecting composition text
AddTextEvent("");
}
else
{
m_aComposition[0] = '\0';
m_CompositionLength = 0;
m_CompositionCursor = 0;
}
break; break;
}
#if SDL_VERSION_ATLEAST(2, 0, 22)
case SDL_TEXTEDITING_EXT:
HandleTextEditingEvent(Event.editExt.text, Event.editExt.start, Event.editExt.length);
SDL_free(Event.editExt.text);
break;
#endif
case SDL_TEXTINPUT: case SDL_TEXTINPUT:
m_aComposition[0] = '\0'; m_CompositionString = "";
m_CompositionLength = COMP_LENGTH_INACTIVE;
m_CompositionCursor = 0; m_CompositionCursor = 0;
AddTextEvent(Event.text.text); AddTextEvent(Event.text.text);
break; break;
@ -861,9 +868,6 @@ int CInput::Update()
} }
} }
if(m_CompositionLength == 0)
m_CompositionLength = COMP_LENGTH_INACTIVE;
return 0; return 0;
} }

View file

@ -84,9 +84,8 @@ private:
#endif #endif
// IME support // IME support
char m_aComposition[MAX_COMPOSITION_ARRAY_SIZE]; std::string m_CompositionString;
int m_CompositionCursor; int m_CompositionCursor;
int m_CompositionLength;
std::vector<std::string> m_vCandidates; std::vector<std::string> m_vCandidates;
int m_CandidateSelectedIndex; int m_CandidateSelectedIndex;
@ -113,6 +112,7 @@ private:
void HandleTouchDownEvent(const SDL_TouchFingerEvent &Event); void HandleTouchDownEvent(const SDL_TouchFingerEvent &Event);
void HandleTouchUpEvent(const SDL_TouchFingerEvent &Event); void HandleTouchUpEvent(const SDL_TouchFingerEvent &Event);
void HandleTouchMotionEvent(const SDL_TouchFingerEvent &Event); void HandleTouchMotionEvent(const SDL_TouchFingerEvent &Event);
void HandleTextEditingEvent(const char *pText, int Start, int Length);
char m_aDropFile[IO_MAX_PATH_LENGTH]; char m_aDropFile[IO_MAX_PATH_LENGTH];
@ -155,10 +155,10 @@ public:
void StartTextInput() override; void StartTextInput() override;
void StopTextInput() override; void StopTextInput() override;
const char *GetComposition() const override { return m_aComposition; } const char *GetComposition() const override { return m_CompositionString.c_str(); }
bool HasComposition() const override { return m_CompositionLength != COMP_LENGTH_INACTIVE; } bool HasComposition() const override { return !m_CompositionString.empty(); }
int GetCompositionCursor() const override { return m_CompositionCursor; } int GetCompositionCursor() const override { return m_CompositionCursor; }
int GetCompositionLength() const override { return m_CompositionLength; } int GetCompositionLength() const override { return m_CompositionString.length(); }
const char *GetCandidate(int Index) const override { return m_vCandidates[Index].c_str(); } const char *GetCandidate(int Index) const override { return m_vCandidates[Index].c_str(); }
int GetCandidateCount() const override { return m_vCandidates.size(); } int GetCandidateCount() const override { return m_vCandidates.size(); }
int GetCandidateSelectedIndex() const override { return m_CandidateSelectedIndex; } int GetCandidateSelectedIndex() const override { return m_CandidateSelectedIndex; }

View file

@ -19,18 +19,13 @@ class IInput : public IInterface
{ {
MACRO_INTERFACE("input") MACRO_INTERFACE("input")
public: public:
enum
{
INPUT_TEXT_SIZE = 32 * UTF8_BYTE_LENGTH + 1,
};
class CEvent class CEvent
{ {
public: public:
int m_Flags; int m_Flags;
int m_Key; int m_Key;
uint32_t m_InputCount; uint32_t m_InputCount;
char m_aText[INPUT_TEXT_SIZE]; char m_aText[32]; // SDL_TEXTINPUTEVENT_TEXT_SIZE
}; };
enum enum
@ -45,12 +40,6 @@ public:
CURSOR_MOUSE, CURSOR_MOUSE,
CURSOR_JOYSTICK, CURSOR_JOYSTICK,
}; };
enum
{
MAX_COMPOSITION_ARRAY_SIZE = 32, // SDL2 limitation
COMP_LENGTH_INACTIVE = -1,
};
// events // events
virtual void ConsumeEvents(std::function<void(const CEvent &Event)> Consumer) const = 0; virtual void ConsumeEvents(std::function<void(const CEvent &Event)> Consumer) const = 0;