mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-08-18 22:42:23 +00:00
1021 lines
33 KiB
C++
1021 lines
33 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 = 0x00000001;
|
|
constexpr uint32_t OPTION_PASSWORD = 0x00000004;
|
|
constexpr uint32_t OPTION_EXT_KEYBOARD = 0x00000010;
|
|
constexpr uint32_t OPTION_EXPANDED_PREEDIT = 0x00001000;
|
|
constexpr uint32_t OPTION_USE_OVER_2K = 0x00004000;
|
|
|
|
constexpr uint32_t EXT_OPTION_HIDE_KEY_PANEL = 0x00000400;
|
|
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;
|
|
uint32_t option = 0;
|
|
uint32_t max_text_length = 0;
|
|
uint32_t cursor = 0;
|
|
Param param {};
|
|
ExtendedParam extended {};
|
|
void* arg = nullptr;
|
|
EventHandler handler = nullptr;
|
|
std::u16string text;
|
|
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;
|
|
}
|
|
|
|
bool IsAllowedInput(char16_t value, Type type, uint32_t option) {
|
|
if (value == u'\r' || value == u'\0') {
|
|
return false;
|
|
}
|
|
if (value == u'\n') {
|
|
return (option & OPTION_MULTILINE) != 0;
|
|
}
|
|
if (type == Type::Number) {
|
|
return (value >= u'0' && value <= u'9') || value == u',' || value == u'-' || value == u'.';
|
|
}
|
|
if (type == Type::BasicLatin) {
|
|
return value >= u' ' && value <= u'~';
|
|
}
|
|
return true;
|
|
}
|
|
|
|
char16_t HidCharacter(uint16_t keycode, uint32_t status) {
|
|
const bool shift = (status & 0x00002200) != 0;
|
|
const bool caps = (status & 0x00020000) != 0;
|
|
if (keycode >= 4 && keycode <= 29) {
|
|
return static_cast<char16_t>(((shift != caps) ? u'A' : u'a') + keycode - 4);
|
|
}
|
|
if (keycode >= 30 && keycode <= 39) {
|
|
static constexpr char16_t plain[] = u"1234567890";
|
|
static constexpr char16_t shifted[] = u"!@#$%^&*()";
|
|
return (shift ? shifted : plain)[keycode - 30];
|
|
}
|
|
if (keycode == 44) {
|
|
return u' ';
|
|
}
|
|
if (keycode >= 45 && keycode <= 56) {
|
|
static constexpr char16_t plain[] = u"-=[]\\#;'`,./";
|
|
static constexpr char16_t shifted[] = u"_+{}|~:\"~<>?";
|
|
return (shift ? shifted : plain)[keycode - 45];
|
|
}
|
|
if (keycode >= 89 && keycode <= 98) {
|
|
static constexpr char16_t keypad[] = u"1234567890";
|
|
return keypad[keycode - 89];
|
|
}
|
|
return keycode == 99 ? u'.' : u'\0';
|
|
}
|
|
|
|
bool ApplyKeyboardFilterOutput(ExternalInput* input, uint16_t keycode, uint32_t status,
|
|
bool multiline) {
|
|
constexpr uint32_t KEYCODE_VALID = 0x00000001;
|
|
constexpr uint32_t CHARACTER_VALID = 0x00000002;
|
|
if (keycode == input->key.keycode && status == input->key.status) {
|
|
return true;
|
|
}
|
|
if ((status & KEYCODE_VALID) == 0 || keycode == 0) {
|
|
return input->action == ExternalAction::Text && (status & CHARACTER_VALID) != 0;
|
|
}
|
|
input->key.keycode = keycode;
|
|
input->key.status = status;
|
|
input->text.clear();
|
|
switch (keycode) {
|
|
case 40:
|
|
case 88:
|
|
case 158:
|
|
input->action = multiline ? ExternalAction::Newline : ExternalAction::Accept;
|
|
break;
|
|
case 41: input->action = ExternalAction::Cancel; break;
|
|
case 42:
|
|
case 187: input->action = ExternalAction::Backspace; break;
|
|
case 43:
|
|
case 186: input->action = ExternalAction::None; break;
|
|
case 79: input->action = ExternalAction::MoveRight; break;
|
|
case 80: input->action = ExternalAction::MoveLeft; break;
|
|
default: {
|
|
const char16_t character = HidCharacter(keycode, status);
|
|
if (character == u'\0') {
|
|
return false;
|
|
}
|
|
input->action = ExternalAction::Text;
|
|
input->key.character = character;
|
|
input->text.push_back(character);
|
|
break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint32_t NormalizeCursor(std::u16string_view text, uint32_t cursor) {
|
|
cursor = std::min<uint32_t>(cursor, static_cast<uint32_t>(text.size()));
|
|
if (cursor > 0 && cursor < text.size() && text[cursor - 1] >= 0xd800 &&
|
|
text[cursor - 1] <= 0xdbff && text[cursor] >= 0xdc00 && text[cursor] <= 0xdfff) {
|
|
cursor++;
|
|
}
|
|
return cursor;
|
|
}
|
|
|
|
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.max_text_length, g_state.text);
|
|
}
|
|
|
|
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.cursor;
|
|
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.text;
|
|
return queued;
|
|
}
|
|
|
|
bool CommitFilteredText(uint64_t generation, uint64_t revision, std::u16string old_text,
|
|
std::u16string candidate, uint32_t cursor, TextFilter filter,
|
|
uint32_t max_length, bool multiline) {
|
|
if (filter != nullptr) {
|
|
std::vector<char16_t> output(max_length + 1, u'\0');
|
|
uint32_t output_length = max_length;
|
|
if (filter(output.data(), &output_length, candidate.c_str(),
|
|
static_cast<uint32_t>(candidate.size())) == 0 &&
|
|
output_length <= max_length) {
|
|
uint32_t actual_length = 0;
|
|
if (ValidateText(output.data(), output_length, multiline, &actual_length) &&
|
|
actual_length == output_length) {
|
|
candidate.assign(output.data(), output.data() + output_length);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::scoped_lock lock(g_mutex);
|
|
if (!g_state.open || g_state.generation != generation || g_state.revision != revision ||
|
|
g_state.text != old_text || candidate == old_text) {
|
|
return false;
|
|
}
|
|
size_t prefix = 0;
|
|
while (prefix < old_text.size() && prefix < candidate.size() &&
|
|
old_text[prefix] == candidate[prefix]) {
|
|
prefix++;
|
|
}
|
|
size_t old_tail = old_text.size();
|
|
size_t new_tail = candidate.size();
|
|
while (old_tail > prefix && new_tail > prefix &&
|
|
old_text[old_tail - 1] == candidate[new_tail - 1]) {
|
|
old_tail--;
|
|
new_tail--;
|
|
}
|
|
const int32_t edit_length =
|
|
static_cast<int32_t>(new_tail - prefix) - static_cast<int32_t>(old_tail - prefix);
|
|
g_state.text = std::move(candidate);
|
|
g_state.cursor = NormalizeCursor(g_state.text, cursor);
|
|
QueueEventLocked(MakeTextEventLocked(1, static_cast<uint32_t>(prefix), 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.option = param->option;
|
|
g_state.max_text_length = param->max_text_length;
|
|
g_state.cursor = static_cast<uint32_t>(text.size());
|
|
g_state.param = *param;
|
|
if (extended != nullptr) {
|
|
g_state.extended = *extended;
|
|
}
|
|
g_state.arg = param->arg;
|
|
g_state.handler = param->handler;
|
|
g_state.text = 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.max_text_length,
|
|
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.max_text_length);
|
|
uint32_t actual_length = 0;
|
|
if (!ValidateText(text, clamped_length, (g_state.option & OPTION_MULTILINE) != 0,
|
|
&actual_length)) {
|
|
return ERROR_INVALID_TEXT;
|
|
}
|
|
g_state.text.assign(text, text + actual_length);
|
|
g_state.cursor = NormalizeCursor(g_state.text, g_state.cursor);
|
|
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.text.size()) {
|
|
return ERROR_INVALID_PARAM;
|
|
}
|
|
g_state.cursor = NormalizeCursor(g_state.text, 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.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(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.option;
|
|
snapshot->max_text_length = g_state.max_text_length;
|
|
snapshot->cursor = g_state.cursor;
|
|
snapshot->disable_device = g_state.extended.disable_device;
|
|
snapshot->key_panel_visible = (g_state.option & 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.option & OPTION_USE_OVER_2K) != 0) {
|
|
snapshot->panel_width *= 2;
|
|
snapshot->panel_height *= 2;
|
|
}
|
|
snapshot->text = g_state.text;
|
|
return true;
|
|
}
|
|
|
|
static bool ApplyInsertText(uint64_t generation, std::u16string_view text) {
|
|
uint64_t revision = 0;
|
|
uint32_t cursor = 0;
|
|
uint32_t max_length = 0;
|
|
bool multiline = false;
|
|
TextFilter filter = nullptr;
|
|
std::u16string old_text;
|
|
std::u16string candidate;
|
|
{
|
|
std::scoped_lock lock(g_mutex);
|
|
if (!g_state.open || g_state.generation != generation || text.empty()) {
|
|
return false;
|
|
}
|
|
uint32_t valid_length = 0;
|
|
multiline = (g_state.option & OPTION_MULTILINE) != 0;
|
|
if (!ValidateText(text.data(), static_cast<uint32_t>(text.size()), multiline,
|
|
&valid_length) ||
|
|
valid_length != text.size()) {
|
|
return false;
|
|
}
|
|
std::u16string allowed;
|
|
for (const char16_t value: text) {
|
|
if (IsAllowedInput(value, g_state.param.type, g_state.option)) {
|
|
allowed.push_back(value);
|
|
}
|
|
}
|
|
const size_t available = g_state.max_text_length - g_state.text.size();
|
|
if (allowed.empty() || available == 0) {
|
|
return false;
|
|
}
|
|
allowed.resize(std::min(allowed.size(), available));
|
|
if (!allowed.empty() && allowed.back() >= 0xd800 && allowed.back() <= 0xdbff) {
|
|
allowed.pop_back();
|
|
}
|
|
if (allowed.empty()) {
|
|
return false;
|
|
}
|
|
revision = g_state.revision;
|
|
max_length = g_state.max_text_length;
|
|
filter = g_state.param.filter;
|
|
old_text = g_state.text;
|
|
candidate = old_text;
|
|
candidate.insert(g_state.cursor, allowed);
|
|
cursor = g_state.cursor + static_cast<uint32_t>(allowed.size());
|
|
}
|
|
return CommitFilteredText(generation, revision, std::move(old_text), std::move(candidate),
|
|
cursor, filter, max_length, multiline);
|
|
}
|
|
|
|
static bool ApplyBackspace(uint64_t generation) {
|
|
uint64_t revision = 0;
|
|
uint32_t cursor = 0;
|
|
uint32_t max_length = 0;
|
|
bool multiline = false;
|
|
TextFilter filter = nullptr;
|
|
std::u16string old_text;
|
|
std::u16string candidate;
|
|
{
|
|
std::scoped_lock lock(g_mutex);
|
|
if (!g_state.open || g_state.generation != generation || g_state.cursor == 0) {
|
|
return false;
|
|
}
|
|
uint32_t first = g_state.cursor - 1;
|
|
if (first > 0 && g_state.text[first] >= 0xdc00 && g_state.text[first] <= 0xdfff &&
|
|
g_state.text[first - 1] >= 0xd800 && g_state.text[first - 1] <= 0xdbff) {
|
|
first--;
|
|
}
|
|
revision = g_state.revision;
|
|
max_length = g_state.max_text_length;
|
|
multiline = (g_state.option & OPTION_MULTILINE) != 0;
|
|
filter = g_state.param.filter;
|
|
old_text = g_state.text;
|
|
candidate = old_text;
|
|
candidate.erase(first, g_state.cursor - first);
|
|
cursor = first;
|
|
}
|
|
return CommitFilteredText(generation, revision, std::move(old_text), std::move(candidate),
|
|
cursor, filter, max_length, 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.cursor;
|
|
int next =
|
|
std::clamp(static_cast<int>(old_cursor) + delta, 0, static_cast<int>(g_state.text.size()));
|
|
if (delta < 0 && next > 0 && next < static_cast<int>(g_state.text.size()) &&
|
|
g_state.text[next] >= 0xdc00 && g_state.text[next] <= 0xdfff &&
|
|
g_state.text[next - 1] >= 0xd800 && g_state.text[next - 1] <= 0xdbff) {
|
|
next--;
|
|
} else if (delta > 0 && next > 0 && next < static_cast<int>(g_state.text.size()) &&
|
|
g_state.text[next - 1] >= 0xd800 && g_state.text[next - 1] <= 0xdbff &&
|
|
g_state.text[next] >= 0xdc00 && g_state.text[next] <= 0xdfff) {
|
|
next++;
|
|
}
|
|
g_state.cursor = static_cast<uint32_t>(next);
|
|
if (g_state.cursor == old_cursor) {
|
|
return false;
|
|
}
|
|
const uint32_t direction = g_state.cursor > old_cursor ? 2 : 1;
|
|
const uint32_t steps = static_cast<uint32_t>(
|
|
std::abs(static_cast<int>(g_state.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.option & 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 = 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
|
|
|
|
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);
|
|
}
|
|
|
|
} // namespace Libs::Ime
|