KytyPS5/src/libs/ime.cpp

870 lines
28 KiB
C++

#include "libs/ime.h"
#include "libs/errno.h"
#include "libs/libs.h"
#include "loader/symbolDatabase.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstring>
#include <deque>
#include <mutex>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
namespace Libs::Ime {
namespace {
constexpr int Error(uint32_t value) {
return static_cast<int32_t>(value);
}
constexpr int ERROR_BUSY = Error(0x80bc0001);
constexpr int ERROR_NOT_OPENED = Error(0x80bc0002);
constexpr int ERROR_CONNECTION_FAILED = Error(0x80bc0004);
constexpr int ERROR_EVENT_OVERFLOW = Error(0x80bc0007);
constexpr int ERROR_INVALID_TEXT = Error(0x80bc0006);
constexpr int ERROR_INVALID_USER_ID = Error(0x80bc0010);
constexpr int ERROR_INVALID_TYPE = Error(0x80bc0011);
constexpr int ERROR_INVALID_LANGUAGES = Error(0x80bc0012);
constexpr int ERROR_INVALID_ENTER_LABEL = Error(0x80bc0013);
constexpr int ERROR_INVALID_INPUT_METHOD = Error(0x80bc0014);
constexpr int ERROR_INVALID_OPTION = Error(0x80bc0015);
constexpr int ERROR_INVALID_MAX_TEXT_LENGTH = Error(0x80bc0016);
constexpr int ERROR_INVALID_TEXT_BUFFER = Error(0x80bc0017);
constexpr int ERROR_INVALID_POSX = Error(0x80bc0018);
constexpr int ERROR_INVALID_POSY = Error(0x80bc0019);
constexpr int ERROR_INVALID_HALIGN = Error(0x80bc001a);
constexpr int ERROR_INVALID_VALIGN = Error(0x80bc001b);
constexpr int ERROR_INVALID_EXTENDED = Error(0x80bc001c);
constexpr int ERROR_INVALID_WORK = Error(0x80bc0020);
constexpr int ERROR_INVALID_HANDLER = Error(0x80bc0022);
constexpr int ERROR_NO_RESOURCE_ID = Error(0x80bc0023);
constexpr int ERROR_INVALID_MODE = Error(0x80bc0024);
constexpr int ERROR_INVALID_PARAM = Error(0x80bc0030);
constexpr int ERROR_INVALID_ADDRESS = Error(0x80bc0031);
constexpr int ERROR_INVALID_RESERVED = Error(0x80bc0032);
constexpr uint32_t VALID_OPTIONS = 0x00007bff;
constexpr uint32_t VALID_EXTENDED_OPTIONS = 0x00004fde;
constexpr uint32_t VALID_EXT_KEYBOARD_MODE = 0x1c000003;
constexpr uint32_t VALID_KEYBOARD_OPTIONS = 0x0000003f;
constexpr uint32_t VALID_KEYBOARD_MODE = 0x0000007f;
constexpr uint64_t VALID_LANGUAGES = 0x00000001ff1fffffULL;
constexpr uint32_t OPTION_MULTILINE = ImeCommon::OPTION_MULTILINE;
constexpr uint32_t OPTION_PASSWORD = ImeCommon::OPTION_PASSWORD;
constexpr uint32_t OPTION_EXT_KEYBOARD = ImeCommon::OPTION_EXT_KEYBOARD;
constexpr uint32_t OPTION_EXPANDED_PREEDIT = ImeCommon::OPTION_EXPANDED_PREEDIT;
constexpr uint32_t OPTION_USE_OVER_2K = ImeCommon::OPTION_USE_OVER_2K;
constexpr uint32_t EXT_OPTION_HIDE_KEY_PANEL = ImeCommon::EXT_OPTION_HIDE_KEY_PANEL;
constexpr size_t EVENT_QUEUE_CAPACITY = 128;
struct QueuedEvent {
Event event {};
bool text_payload = false;
std::u16string text;
};
struct State {
bool open = false;
bool event_overflow = false;
uint64_t generation = 0;
uint64_t revision = 0;
Param param {};
ExtendedParam extended {};
void* arg = nullptr;
EventHandler handler = nullptr;
ImeCommon::TextEditEngine editor;
std::deque<QueuedEvent> events;
std::vector<ExternalInput> external_inputs;
};
struct KeyboardState {
bool open = false;
void* arg = nullptr;
EventHandler handler = nullptr;
std::deque<QueuedEvent> events;
};
std::mutex g_mutex;
State g_state;
KeyboardState g_keyboard_state;
std::atomic<bool> g_open {false};
std::atomic<uint64_t> g_revision {0};
std::atomic<VisibilityCallback> g_visibility_callback {nullptr};
bool AllZero(const int8_t* data, size_t size) {
return std::all_of(data, data + size, [](int8_t value) { return value == 0; });
}
bool ValidateText(const char16_t* text, uint32_t length, bool multiline, uint32_t* actual_length) {
if (text == nullptr) {
return false;
}
*actual_length = 0;
for (uint32_t i = 0; i < length; i++) {
const char16_t value = text[i];
if (value == u'\0') {
return true;
}
if (!multiline && (value == u'\n' || value == u'\r')) {
return false;
}
if (value >= 0xd800 && value <= 0xdbff) {
if (++i >= length || text[i] < 0xdc00 || text[i] > 0xdfff) {
return false;
}
} else if (value >= 0xdc00 && value <= 0xdfff) {
return false;
}
*actual_length = i + 1;
}
return true;
}
void NotifyVisibility(bool visible, uint64_t generation) {
if (const auto callback = g_visibility_callback.load(std::memory_order_acquire);
callback != nullptr) {
callback(visible, generation);
}
}
void UpdateRevisionLocked() {
g_state.revision++;
g_revision.store(g_state.revision, std::memory_order_release);
}
void SyncGuestText(char16_t* work, char16_t* input, uint32_t max_length, std::u16string_view text) {
const size_t size = std::min<size_t>(text.size(), max_length);
if (work != nullptr) {
std::memcpy(work, text.data(), size * sizeof(char16_t));
work[size] = u'\0';
}
if (input != nullptr) {
std::memcpy(input, text.data(), size * sizeof(char16_t));
input[size] = u'\0';
}
}
void SyncTextBuffersLocked() {
SyncGuestText(static_cast<char16_t*>(g_state.param.work), g_state.param.input_text_buffer,
g_state.editor.GetMaxLength(), g_state.editor.GetText());
}
bool QueueEventLocked(QueuedEvent event) {
if (g_state.events.size() == EVENT_QUEUE_CAPACITY) {
g_state.event_overflow = true;
return false;
}
g_state.events.push_back(event);
return true;
}
QueuedEvent MakeTextEventLocked(uint32_t id, uint32_t edit_index, int32_t edit_length) {
QueuedEvent queued;
queued.event.id = id;
queued.event.param.text.str = static_cast<char16_t*>(g_state.param.work);
queued.event.param.text.caret_index = g_state.editor.GetCursor();
queued.event.param.text.area_num = 1;
queued.event.param.text.text_area[0] = {TextAreaMode::Edit, edit_index, edit_length};
queued.text_payload = true;
queued.text = g_state.editor.GetText();
return queued;
}
bool CommitFilteredText(uint64_t generation, uint64_t revision, std::u16string old_text,
ImeCommon::TextEditEngine candidate, TextFilter filter, bool multiline) {
if (filter != nullptr) {
std::u16string filtered;
if (ImeCommon::RunTextFilter(filter, candidate.GetText(), candidate.GetMaxLength(),
&filtered) &&
ImeCommon::IsValidInputText(filtered, multiline)) {
candidate.ReplaceText(std::move(filtered), candidate.GetCursor());
}
}
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation || g_state.revision != revision ||
g_state.editor.GetText() != old_text || candidate.GetText() == old_text) {
return false;
}
const auto edit = ImeCommon::ComputeEditDelta(old_text, candidate.GetText());
g_state.editor = std::move(candidate);
QueueEventLocked(MakeTextEventLocked(1, edit.index, edit.length));
UpdateRevisionLocked();
return true;
}
void ApplyExternalInputs();
bool ReadInitialText(const Param& param, std::u16string* text) {
uint32_t length = 0;
while (length <= param.max_text_length && param.input_text_buffer[length] != u'\0') {
length++;
}
if (length > param.max_text_length) {
return false;
}
uint32_t actual = 0;
if (!ValidateText(param.input_text_buffer, length, (param.option & OPTION_MULTILINE) != 0,
&actual)) {
return false;
}
text->assign(param.input_text_buffer, param.input_text_buffer + actual);
return true;
}
bool RangesOverlap(uintptr_t first, size_t first_size, uintptr_t second, size_t second_size) {
if (first > UINTPTR_MAX - first_size || second > UINTPTR_MAX - second_size) {
return true;
}
return first < second + second_size && second < first + first_size;
}
int ValidateExtended(const ExtendedParam* extended) {
if (extended == nullptr) {
return OK;
}
if ((extended->option & ~VALID_EXTENDED_OPTIONS) != 0 ||
((extended->option & 0x00004000) != 0 && (extended->option & 0x00000080) == 0) ||
extended->priority > 3 || extended->disable_device > 7 ||
(extended->ext_keyboard_mode & ~VALID_EXT_KEYBOARD_MODE) != 0 ||
!AllZero(extended->reserved, sizeof(extended->reserved))) {
return ERROR_INVALID_EXTENDED;
}
return OK;
}
int ValidateOpen(const Param* param, const ExtendedParam* extended, std::u16string* text) {
if (param == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (param->user_id < 0 || param->user_id == 0xff) {
return ERROR_INVALID_USER_ID;
}
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
return ERROR_INVALID_TYPE;
}
if ((param->supported_languages & ~VALID_LANGUAGES) != 0) {
return ERROR_INVALID_LANGUAGES;
}
if (static_cast<uint32_t>(param->enter_label) > static_cast<uint32_t>(EnterLabel::Go)) {
return ERROR_INVALID_ENTER_LABEL;
}
if (param->input_method != 0) {
return ERROR_INVALID_INPUT_METHOD;
}
if ((param->option & ~VALID_OPTIONS) != 0) {
return ERROR_INVALID_OPTION;
}
const bool multiline = (param->option & OPTION_MULTILINE) != 0;
const bool password = (param->option & OPTION_PASSWORD) != 0;
if ((multiline && password) ||
(multiline && param->type != Type::Default && param->type != Type::BasicLatin) ||
(password && param->type != Type::BasicLatin && param->type != Type::Number)) {
return ERROR_INVALID_PARAM;
}
if (param->max_text_length == 0 || param->max_text_length > MAX_TEXT_LENGTH) {
return ERROR_INVALID_MAX_TEXT_LENGTH;
}
if (param->input_text_buffer == nullptr) {
return ERROR_INVALID_TEXT_BUFFER;
}
const bool over_2k = (param->option & OPTION_USE_OVER_2K) != 0;
const float max_x = over_2k ? 3840.0f : 1920.0f;
const float max_y = over_2k ? 2160.0f : 1080.0f;
if (!std::isfinite(param->posx) || param->posx < 0.0f || param->posx >= max_x) {
return ERROR_INVALID_POSX;
}
if (!std::isfinite(param->posy) || param->posy < 0.0f || param->posy >= max_y) {
return ERROR_INVALID_POSY;
}
if (static_cast<uint32_t>(param->horizontal_alignment) > 2) {
return ERROR_INVALID_HALIGN;
}
if (static_cast<uint32_t>(param->vertical_alignment) > 2) {
return ERROR_INVALID_VALIGN;
}
const int extended_result = ValidateExtended(extended);
if (extended_result != OK) {
return extended_result;
}
if (param->work == nullptr || (reinterpret_cast<uintptr_t>(param->work) & 3u) != 0) {
return ERROR_INVALID_WORK;
}
if (param->handler == nullptr) {
return ERROR_INVALID_HANDLER;
}
if (!AllZero(param->reserved, sizeof(param->reserved))) {
return ERROR_INVALID_RESERVED;
}
const size_t text_size =
static_cast<size_t>(param->max_text_length +
((param->option & OPTION_EXPANDED_PREEDIT) != 0 ? 0x79u : 0x1fu)) *
sizeof(char16_t);
if (RangesOverlap(reinterpret_cast<uintptr_t>(param->input_text_buffer), text_size,
reinterpret_cast<uintptr_t>(param->work), WORK_BUFFER_SIZE)) {
return ERROR_INVALID_PARAM;
}
return ReadInitialText(*param, text) ? OK : ERROR_INVALID_TEXT;
}
} // namespace
void KYTY_SYSV_ABI ImeParamInit(Param* param) {
if (param == nullptr) {
return;
}
std::memset(param, 0, sizeof(*param));
param->user_id = -1;
}
int KYTY_SYSV_ABI ImeGetPanelSize(const Param* param, uint32_t* width, uint32_t* height) {
if (param == nullptr || width == nullptr || height == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
return ERROR_INVALID_TYPE;
}
if ((param->option & ~VALID_OPTIONS) != 0) {
return ERROR_INVALID_OPTION;
}
*width = param->type == Type::Number ? 370 : 793;
*height = param->type == Type::Number ? 402 : 408;
if ((param->option & OPTION_USE_OVER_2K) != 0) {
*width *= 2;
*height *= 2;
}
return OK;
}
int KYTY_SYSV_ABI ImeOpen(const Param* param, const ExtendedParam* extended) {
uint64_t generation = 0;
{
std::scoped_lock lock(g_mutex);
if (g_state.open) {
return ERROR_BUSY;
}
std::u16string text;
const int validation = ValidateOpen(param, extended, &text);
if (validation != OK) {
return validation;
}
const uint64_t next_generation = g_state.generation + 1;
const uint64_t next_revision = g_state.revision + 1;
g_state = {};
g_state.open = true;
g_state.generation = next_generation;
g_state.revision = next_revision;
g_state.param = *param;
if (extended != nullptr) {
g_state.extended = *extended;
}
g_state.arg = param->arg;
g_state.handler = param->handler;
g_state.editor.Reset(param->type, param->option, param->max_text_length, std::move(text));
std::memset(param->work, 0, WORK_BUFFER_SIZE);
SyncTextBuffersLocked();
QueuedEvent open;
open.event.id = 0;
open.event.param.rect.x = param->posx;
open.event.param.rect.y = param->posy;
open.event.param.rect.width = param->type == Type::Number ? 370 : 793;
open.event.param.rect.height = param->type == Type::Number ? 402 : 408;
if ((param->option & OPTION_USE_OVER_2K) != 0) {
open.event.param.rect.width *= 2;
open.event.param.rect.height *= 2;
}
QueueEventLocked(std::move(open));
generation = g_state.generation;
g_open.store(true, std::memory_order_release);
g_revision.store(g_state.revision, std::memory_order_release);
}
NotifyVisibility(true, generation);
return OK;
}
int KYTY_SYSV_ABI ImeUpdate(EventHandler handler) {
bool apply_inputs = false;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open && !g_keyboard_state.open) {
return ERROR_NOT_OPENED;
}
const bool update_ime = g_state.open && handler == g_state.handler;
const bool update_keyboard = g_keyboard_state.open && handler == g_keyboard_state.handler;
if (!update_ime && !update_keyboard) {
return ERROR_NOT_OPENED;
}
apply_inputs = update_ime;
}
if (apply_inputs) {
ApplyExternalInputs();
}
struct Dispatch {
void* arg;
QueuedEvent queued;
char16_t* work;
char16_t* input;
uint32_t max_length;
uint64_t generation;
};
std::vector<Dispatch> dispatch;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open && !g_keyboard_state.open) {
return ERROR_NOT_OPENED;
}
const bool update_ime = g_state.open && handler == g_state.handler;
const bool update_keyboard = g_keyboard_state.open && handler == g_keyboard_state.handler;
if (!update_ime && !update_keyboard) {
return ERROR_NOT_OPENED;
}
if (update_ime && std::exchange(g_state.event_overflow, false)) {
return ERROR_EVENT_OVERFLOW;
}
if (update_ime) {
while (!g_state.events.empty()) {
dispatch.push_back({g_state.arg, std::move(g_state.events.front()),
static_cast<char16_t*>(g_state.param.work),
g_state.param.input_text_buffer, g_state.editor.GetMaxLength(),
g_state.generation});
g_state.events.pop_front();
}
}
if (update_keyboard) {
while (!g_keyboard_state.events.empty()) {
dispatch.push_back({g_keyboard_state.arg,
std::move(g_keyboard_state.events.front()), nullptr, nullptr, 0,
0});
g_keyboard_state.events.pop_front();
}
}
}
for (auto& item: dispatch) {
if (item.generation != 0) {
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != item.generation) {
continue;
}
}
if (item.queued.text_payload) {
SyncGuestText(item.work, item.input, item.max_length, item.queued.text);
item.queued.event.param.text.str = item.work;
}
handler(item.arg, &item.queued.event);
}
return OK;
}
int KYTY_SYSV_ABI ImeClose() {
uint64_t generation = 0;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open) {
return ERROR_NOT_OPENED;
}
generation = g_state.generation;
const uint64_t revision = g_state.revision + 1;
g_state = {};
g_state.generation = generation;
g_state.revision = revision;
g_open.store(false, std::memory_order_release);
g_revision.store(revision, std::memory_order_release);
}
NotifyVisibility(false, generation);
return OK;
}
int KYTY_SYSV_ABI ImeSetText(const char16_t* text, uint32_t length) {
std::scoped_lock lock(g_mutex);
if (!g_state.open) {
return ERROR_NOT_OPENED;
}
if (text == nullptr) {
return ERROR_INVALID_ADDRESS;
}
const uint32_t clamped_length = std::min(length, g_state.editor.GetMaxLength());
uint32_t actual_length = 0;
if (!ValidateText(text, clamped_length, (g_state.editor.GetOption() & OPTION_MULTILINE) != 0,
&actual_length)) {
return ERROR_INVALID_TEXT;
}
g_state.editor.ReplaceText(std::u16string(text, text + actual_length),
g_state.editor.GetCursor());
SyncTextBuffersLocked();
UpdateRevisionLocked();
return OK;
}
int KYTY_SYSV_ABI ImeSetCaret(const Caret* caret) {
std::scoped_lock lock(g_mutex);
if (!g_state.open) {
return ERROR_NOT_OPENED;
}
if (caret == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (caret->index > g_state.editor.GetText().size()) {
return ERROR_INVALID_PARAM;
}
g_state.editor.SetCursor(caret->index);
UpdateRevisionLocked();
return OK;
}
int KYTY_SYSV_ABI ImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geometry) {
std::scoped_lock lock(g_mutex);
if (!g_state.open) {
return ERROR_NOT_OPENED;
}
if (geometry == nullptr) {
return ERROR_INVALID_ADDRESS;
}
const bool over_2k = (g_state.editor.GetOption() & OPTION_USE_OVER_2K) != 0;
const float max_x = over_2k ? 3840.0f : 1920.0f;
const float max_y = over_2k ? 2160.0f : 1080.0f;
if (!std::isfinite(geometry->x) || !std::isfinite(geometry->y) || geometry->x < 0.0f ||
geometry->x >= max_x || geometry->y < 0.0f || geometry->y >= max_y ||
(mode != TextAreaMode::Preedit && mode != TextAreaMode::Select)) {
return ERROR_INVALID_PARAM;
}
return OK;
}
int KYTY_SYSV_ABI ImeKeyboardOpen(int32_t user_id, const KeyboardParam* param) {
std::scoped_lock lock(g_mutex);
if (param == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (user_id == -1) {
return ERROR_INVALID_USER_ID;
}
if (param->handler == nullptr) {
return ERROR_INVALID_HANDLER;
}
if ((param->option & ~VALID_KEYBOARD_OPTIONS) != 0) {
return ERROR_INVALID_OPTION;
}
if (!AllZero(param->reserved1, sizeof(param->reserved1)) ||
!AllZero(param->reserved2, sizeof(param->reserved2))) {
return ERROR_INVALID_RESERVED;
}
if (g_keyboard_state.open) {
return ERROR_BUSY;
}
g_keyboard_state = {};
g_keyboard_state.open = true;
g_keyboard_state.arg = param->arg;
g_keyboard_state.handler = param->handler;
QueuedEvent open;
open.event.id = 256;
open.event.param.resource_id_array.user_id = user_id;
g_keyboard_state.events.push_back(std::move(open));
return OK;
}
int KYTY_SYSV_ABI ImeKeyboardClose(int32_t user_id) {
std::scoped_lock lock(g_mutex);
if (!g_keyboard_state.open) {
return ERROR_NOT_OPENED;
}
if (user_id == -1) {
return ERROR_INVALID_USER_ID;
}
g_keyboard_state = {};
return OK;
}
int KYTY_SYSV_ABI ImeKeyboardGetResourceId(int32_t user_id, KeyboardResourceIdArray* resource_ids) {
if (resource_ids == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (user_id == -1) {
return ERROR_INVALID_USER_ID;
}
std::memset(resource_ids, 0, sizeof(*resource_ids));
resource_ids->user_id = user_id;
std::scoped_lock lock(g_mutex);
return g_keyboard_state.open ? ERROR_CONNECTION_FAILED : ERROR_NOT_OPENED;
}
int KYTY_SYSV_ABI ImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info) {
if (info == nullptr) {
return ERROR_INVALID_ADDRESS;
}
std::scoped_lock lock(g_mutex);
if (!g_keyboard_state.open) {
return ERROR_NOT_OPENED;
}
(void)resource_id;
return ERROR_NO_RESOURCE_ID;
}
int KYTY_SYSV_ABI ImeKeyboardSetMode(int32_t user_id, uint32_t mode) {
if (user_id == -1) {
return ERROR_INVALID_USER_ID;
}
std::scoped_lock lock(g_mutex);
if (!g_keyboard_state.open) {
return ERROR_NOT_OPENED;
}
return (mode & ~VALID_KEYBOARD_MODE) == 0 ? OK : ERROR_INVALID_MODE;
}
VisualState GetVisualState() noexcept {
return {g_open.load(std::memory_order_acquire), g_revision.load(std::memory_order_acquire)};
}
void SetVisibilityCallback(VisibilityCallback callback) noexcept {
g_visibility_callback.store(callback, std::memory_order_release);
}
bool GetHostSnapshot(HostSnapshot* snapshot) {
if (snapshot == nullptr) {
return false;
}
std::scoped_lock lock(g_mutex);
if (!g_state.open) {
return false;
}
snapshot->generation = g_state.generation;
snapshot->type = g_state.param.type;
snapshot->enter_label = g_state.param.enter_label;
snapshot->option = g_state.editor.GetOption();
snapshot->max_text_length = g_state.editor.GetMaxLength();
snapshot->cursor = g_state.editor.GetCursor();
snapshot->disable_device = g_state.extended.disable_device;
snapshot->key_panel_visible = (g_state.editor.GetOption() & OPTION_EXT_KEYBOARD) == 0 ||
(g_state.extended.option & EXT_OPTION_HIDE_KEY_PANEL) == 0;
snapshot->posx = g_state.param.posx;
snapshot->posy = g_state.param.posy;
snapshot->horizontal_alignment = g_state.param.horizontal_alignment;
snapshot->vertical_alignment = g_state.param.vertical_alignment;
snapshot->panel_width = g_state.param.type == Type::Number ? 370 : 793;
snapshot->panel_height = g_state.param.type == Type::Number ? 402 : 408;
if ((g_state.editor.GetOption() & OPTION_USE_OVER_2K) != 0) {
snapshot->panel_width *= 2;
snapshot->panel_height *= 2;
}
snapshot->text = g_state.editor.GetText();
snapshot->title.clear();
snapshot->placeholder.clear();
return true;
}
static bool ApplyInsertText(uint64_t generation, std::u16string_view text) {
uint64_t revision = 0;
bool multiline = false;
TextFilter filter = nullptr;
std::u16string old_text;
ImeCommon::TextEditEngine candidate;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation || text.empty()) {
return false;
}
multiline = (g_state.editor.GetOption() & OPTION_MULTILINE) != 0;
if (!ImeCommon::IsValidInputText(text, multiline)) {
return false;
}
candidate = g_state.editor;
if (!candidate.Insert(text)) {
return false;
}
revision = g_state.revision;
filter = g_state.param.filter;
old_text = g_state.editor.GetText();
}
return CommitFilteredText(generation, revision, std::move(old_text), std::move(candidate),
filter, multiline);
}
static bool ApplyBackspace(uint64_t generation) {
uint64_t revision = 0;
bool multiline = false;
TextFilter filter = nullptr;
std::u16string old_text;
ImeCommon::TextEditEngine candidate;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation) {
return false;
}
candidate = g_state.editor;
if (!candidate.Backspace()) {
return false;
}
revision = g_state.revision;
multiline = (g_state.editor.GetOption() & OPTION_MULTILINE) != 0;
filter = g_state.param.filter;
old_text = g_state.editor.GetText();
}
return CommitFilteredText(generation, revision, std::move(old_text), std::move(candidate),
filter, multiline);
}
static bool ApplyMoveCursor(uint64_t generation, int delta) {
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation || delta == 0) {
return false;
}
const uint32_t old_cursor = g_state.editor.GetCursor();
if (!g_state.editor.MoveCursor(delta)) {
return false;
}
const uint32_t new_cursor = g_state.editor.GetCursor();
const uint32_t direction = new_cursor > old_cursor ? 2 : 1;
const uint32_t steps = static_cast<uint32_t>(
std::abs(static_cast<int>(new_cursor) - static_cast<int>(old_cursor)));
for (uint32_t i = 0; i < steps; i++) {
QueuedEvent event;
event.event.id = 2;
event.event.param.caret_move = direction;
QueueEventLocked(std::move(event));
}
UpdateRevisionLocked();
return true;
}
static bool ApplyAccept(uint64_t generation) {
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation) {
return false;
}
QueuedEvent event;
event.event.id = 5;
return QueueEventLocked(std::move(event));
}
static bool ApplyCancel(uint64_t generation) {
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation) {
return false;
}
QueuedEvent event;
event.event.id = 4;
return QueueEventLocked(std::move(event));
}
bool HostInsertText(uint64_t generation, std::u16string_view text) {
ExternalInput input;
input.action = ExternalAction::Text;
input.text = text;
return HostQueueExternalInput(generation, std::move(input));
}
bool HostBackspace(uint64_t generation) {
ExternalInput input;
input.action = ExternalAction::Backspace;
return HostQueueExternalInput(generation, std::move(input));
}
bool HostAccept(uint64_t generation) {
ExternalInput input;
input.action = ExternalAction::Accept;
return HostQueueExternalInput(generation, std::move(input));
}
bool HostCancel(uint64_t generation) {
ExternalInput input;
input.action = ExternalAction::Cancel;
return HostQueueExternalInput(generation, std::move(input));
}
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation) {
return false;
}
if (g_state.external_inputs.size() == EVENT_QUEUE_CAPACITY) {
g_state.event_overflow = true;
return false;
}
g_state.external_inputs.push_back(std::move(input));
return true;
}
namespace {
void ApplyExternalInputs() {
uint64_t generation = 0;
ExtKeyboardFilter filter = nullptr;
int32_t user_id = -1;
bool multiline = false;
std::vector<ExternalInput> inputs;
{
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.external_inputs.empty()) {
return;
}
generation = g_state.generation;
filter = g_state.extended.ext_keyboard_filter;
user_id = g_state.param.user_id;
multiline = (g_state.editor.GetOption() & OPTION_MULTILINE) != 0;
inputs.swap(g_state.external_inputs);
}
for (auto& input: inputs) {
{
std::scoped_lock lock(g_mutex);
if (!g_state.open || g_state.generation != generation) {
break;
}
}
input.key.user_id = user_id;
bool accepted = true;
if (filter != nullptr && input.key.status != 0) {
uint16_t keycode = input.key.keycode;
uint32_t status = input.key.status;
if (filter(&input.key, &keycode, &status, nullptr) == 0) {
accepted = ImeCommon::ApplyKeyboardFilterOutput(&input, keycode, status, multiline);
}
}
if (!accepted) {
continue;
}
switch (input.action) {
case ExternalAction::None: break;
case ExternalAction::Text: ApplyInsertText(generation, input.text); break;
case ExternalAction::Backspace: ApplyBackspace(generation); break;
case ExternalAction::MoveLeft: ApplyMoveCursor(generation, -1); break;
case ExternalAction::MoveRight: ApplyMoveCursor(generation, 1); break;
case ExternalAction::Cancel: ApplyCancel(generation); break;
case ExternalAction::Accept: ApplyAccept(generation); break;
case ExternalAction::Newline: ApplyInsertText(generation, u"\n"); break;
}
}
}
} // namespace
#if !defined(KYTY_IME_TESTS)
LIB_VERSION("Ime", 1, "Ime", 1, 1);
LIB_DEFINE(InitPlatform_1_Ime) {
LIB_FUNC("ieCNrVrzKd4", ImeSetText);
LIB_FUNC("WLxUN2WMim8", ImeSetCaret);
LIB_FUNC("WmYDzdC4EHI", ImeParamInit);
LIB_FUNC("ziPDcIjO0Vk", ImeGetPanelSize);
LIB_FUNC("RPydv-Jr1bc", ImeOpen);
LIB_FUNC("-4GCfYdNF1s", ImeUpdate);
LIB_FUNC("TmVP8LzcFcY", ImeClose);
LIB_FUNC("TXYHFRuL8UY", ImeSetTextGeometry);
LIB_FUNC("eaFXjfJv3xs", ImeKeyboardOpen);
LIB_FUNC("PMVehSlfZ94", ImeKeyboardClose);
LIB_FUNC("dKadqZFgKKQ", ImeKeyboardGetResourceId);
LIB_FUNC("VkqLPArfFdc", ImeKeyboardGetInfo);
LIB_FUNC("ua+13Hk9kKs", ImeKeyboardSetMode);
}
#endif
} // namespace Libs::Ime