diff --git a/src/core/libraries/dialogs/ime_dialog.h b/src/core/libraries/dialogs/ime_dialog.h old mode 100644 new mode 100755 index 2211f0444..24914044a --- a/src/core/libraries/dialogs/ime_dialog.h +++ b/src/core/libraries/dialogs/ime_dialog.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" namespace Core::Loader { @@ -89,6 +90,8 @@ enum class OrbisImeDialogOption : u32 { NO_AUTO_COMPLETION = 4 }; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption) + enum class OrbisImeInputMethod : u32 { DEFAULT = 0 }; @@ -167,7 +170,7 @@ struct OrbisImeDialogResult { struct OrbisImeKeycode { u16 keycode; - wchar_t character; + char16_t character; u32 status; OrbisImeKeyboardType type; s32 userId; @@ -175,7 +178,7 @@ struct OrbisImeKeycode { u64 timestamp; }; -typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText, +typedef int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, const char16_t* srcText, u32 srcTextLength); typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode, diff --git a/src/core/libraries/dialogs/ime_dialog_ui.cpp b/src/core/libraries/dialogs/ime_dialog_ui.cpp old mode 100644 new mode 100755 index 3a1629685..6d14ee296 --- a/src/core/libraries/dialogs/ime_dialog_ui.cpp +++ b/src/core/libraries/dialogs/ime_dialog_ui.cpp @@ -7,45 +7,51 @@ #include #include "common/assert.h" +#include "common/logging/log.h" +#include "common/singleton.h" #include "core/libraries/dialogs/ime_dialog.h" #include "core/libraries/dialogs/ime_dialog_ui.h" +#include "core/linker.h" +#include "imgui/imgui_std.h" #ifndef _WIN32 -#define IME_NATIVE_ENCODING "WCHAR_T" +#define IME_UTF8_ENCODING "UTF-8" #define IME_ORBIS_ENCODING "UTF-16LE" +#else +#include #endif using namespace ImGui; +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + namespace Libraries::ImeDialog { ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, const OrbisImeParamExtended* extended) { if (!param) return; userId = param->userId; - isMultiLine = param->option & OrbisImeDialogOption::MULTILINE; + is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE); type = param->type; - enterLabel = param->enterLabel; - textFilter = param->filter; - extKeyboardFilter = extended ? extended->extKeyboardFilter : nullptr; - maxTextLength = param->maxTextLength; - textBuffer = param->inputTextBuffer; + enter_label = param->enterLabel; + text_filter = param->filter; + keyboard_filter = extended ? extended->extKeyboardFilter : nullptr; + max_text_length = param->maxTextLength; + text_buffer = param->inputTextBuffer; #ifndef _WIN32 - orbis_to_native = iconv_open(IME_NATIVE_ENCODING, IME_ORBIS_ENCODING); - native_to_orbis = iconv_open(IME_ORBIS_ENCODING , IME_NATIVE_ENCODING); + orbis_to_utf8 = iconv_open(IME_UTF8_ENCODING, IME_ORBIS_ENCODING); + utf8_to_orbis = iconv_open(IME_ORBIS_ENCODING , IME_UTF8_ENCODING); - ASSERT_MSG(orbis_to_native != (iconv_t)-1, "Failed to open iconv orbis_to_native"); - ASSERT_MSG(native_to_orbis != (iconv_t)-1, "Failed to open iconv native_to_orbis"); - - std::size_t title_len = std::char_traits::length(param->title) + 1; -#elif - std::size_t title_len = std::wcslen(reinterpret_cast(param->title)) + 1; + ASSERT_MSG(orbis_to_utf8 != (iconv_t)-1, "Failed to open iconv orbis_to_utf8"); + ASSERT_MSG(utf8_to_orbis != (iconv_t)-1, "Failed to open iconv utf8_to_orbis"); #endif - title = new wchar_t[title_len]; - if (!ConvertOrbisToNative(param->title, title_len, title, title_len)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert title to native encoding"); + std::size_t title_len = std::char_traits::length(param->title); + title = new char[title_len * 4 + 1]; + + if (!ConvertOrbisToUTF8(param->title, title_len, title, title_len)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding"); return; } @@ -53,26 +59,23 @@ ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, const OrbisImeP return; } -#ifndef _WIN32 - std::size_t placeholder_len = std::char_traits::length(param->placeholder) + 1; -#elif - std::size_t placeholder_len = std::wcslen(reinterpret_cast(param->placeholder)) + 1; -#endif - placeholder = new wchar_t[placeholder_len]; + std::size_t placeholder_len = std::char_traits::length(param->placeholder); + placeholder = new char[placeholder_len * 4 + 1]; - if (!ConvertOrbisToNative(param->placeholder, placeholder_len, placeholder, placeholder_len)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to native encoding"); + if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, placeholder, placeholder_len)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding"); } } ImeDialogState::~ImeDialogState() { #ifndef _WIN32 - if (orbis_to_native != (iconv_t)-1) { - iconv_close(orbis_to_native); + if (orbis_to_utf8 != (iconv_t)-1) { + iconv_close(orbis_to_utf8); } - if (native_to_orbis != (iconv_t)-1) { - iconv_close(native_to_orbis); + if (utf8_to_orbis != (iconv_t)-1) { + iconv_close(utf8_to_orbis); } +#endif if (title) { delete[] title; @@ -81,50 +84,343 @@ ImeDialogState::~ImeDialogState() { if (placeholder) { delete[] placeholder; } -#endif } bool ImeDialogState::CallTextFilter() { - if (!textFilter) { + if (!text_filter || !input_changed) { return true; } - //TODO + input_changed = false; + + char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 src_text_length = std::strlen(current_text); + char16_t out_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 out_text_length = ORBIS_IME_DIALOG_MAX_TEXT_LENGTH; + + if (!ConvertUTF8ToOrbis(current_text, src_text_length, src_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to orbis encoding"); + return false; + } + + auto* linker = Common::Singleton::Instance(); + int ret = linker->ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + + if (ret != 0) { + return false; + } + + if (!ConvertOrbisToUTF8(out_text, out_text_length, current_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + return false; + } + return true; } -bool ImeDialogState::ConvertOrbisToNative(const char16_t* orbis_text, std::size_t orbis_text_len, wchar_t* native_text, std::size_t native_text_len) { +bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status) { + if (!keyboard_filter) { + return true; + } + + auto* linker = Common::Singleton::Instance(); + int ret = linker->ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + + return ret == 0; +} + +bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, std::size_t utf8_text_len) { #ifndef _WIN32 std::size_t orbis_text_len_bytes = orbis_text_len * sizeof(char16_t); - std::size_t native_text_len_bytes = native_text_len * sizeof(wchar_t); + std::size_t utf8_text_len_bytes = utf8_text_len * sizeof(char); char16_t* orbis_text_ptr = const_cast(orbis_text); - wchar_t* native_text_ptr = native_text; + char* utf8_text_ptr = utf8_text; - std::size_t result = iconv(orbis_to_native, (char**)&orbis_text_ptr, &orbis_text_len_bytes, (char**)&native_text_ptr, &native_text_len_bytes); + std::size_t result = iconv(orbis_to_utf8, (char**)&orbis_text_ptr, &orbis_text_len_bytes, (char**)&utf8_text_ptr, &utf8_text_len_bytes); + + if (result == (std::size_t)-1) { + return false; + } + + *utf8_text_ptr = '\0'; // Null-terminate the string + return true; +#else + int required_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(orbis_text), orbis_text_len, nullptr, 0, nullptr, nullptr); + if (required_size > utf8_text_len) { + return false; + } + + int converted_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(orbis_text), orbis_text_len, utf8_text, utf8_text_len, nullptr, nullptr); + + if (required_size == 0) { + return false; + } + + utf8_text[converted_size] = '\0'; - return result != -1; -#elif - std::wcsncpy(native_text, reinterpret_cast(orbis_text), native_text_len); return true; #endif } -bool ImeDialogState::ConvertNativeToOrbis(const wchar_t* native_text, std::size_t native_text_len, char16_t* orbis_text, std::size_t orbis_text_len) { +bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, char16_t* orbis_text, std::size_t orbis_text_len) { #ifndef _WIN32 - std::size_t native_text_len_bytes = native_text_len * sizeof(wchar_t); + std::size_t utf8_text_len_bytes = utf8_text_len * sizeof(char); std::size_t orbis_text_len_bytes = orbis_text_len * sizeof(char16_t); - wchar_t* native_text_ptr = const_cast(native_text); + char* utf8_text_ptr = const_cast(utf8_text); char16_t* orbis_text_ptr = orbis_text; - std::size_t result = iconv(native_to_orbis, (char**)&native_text_ptr, &native_text_len_bytes, (char**)&orbis_text_ptr, &orbis_text_len_bytes); + std::size_t result = iconv(utf8_to_orbis, (char**)&utf8_text_ptr, &utf8_text_len_bytes, (char**)&orbis_text_ptr, &orbis_text_len_bytes); + + if (result == (std::size_t)-1) { + return false; + } + + *orbis_text_ptr = u'\0'; // Null-terminate the string + return true; +#else + int required_size = MultiByteToWideChar(CP_UTF8, 0, utf8_text, utf8_text_len, nullptr, 0); + if (required_size > orbis_text_len) { + return false; + } + + int converted_size = MultiByteToWideChar(CP_UTF8, 0, utf8_text, utf8_text_len, reinterpret_cast(orbis_text), orbis_text_len); + + if (required_size == 0) { + return false; + } + + orbis_text[converted_size] = u'\0'; - return result != -1; -#elif - std::wcsncpy(reinterpret_cast(orbis_text), native_text, orbis_text_len); return true; #endif } +bool ImeDialogState::ConvertOrbisCharToUTF8(const char16_t orbis_char, char* utf8_char, std::size_t& utf8_char_len) { + std::fill(utf8_char, utf8_char + 4, '\0'); +#ifndef _WIN32 + std::size_t orbis_char_len_bytes = sizeof(char16_t); + std::size_t utf8_char_len_bytes = utf8_char_len; + + char16_t orbis_char_ptr = orbis_char; + char* utf8_char_ptr = utf8_char; + + std::size_t result = iconv(orbis_to_utf8, (char**)&orbis_char_ptr, &orbis_char_len_bytes, (char**)&utf8_char_ptr, &utf8_char_len_bytes); + + if (result == (std::size_t)-1) { + utf8_char_len = 0; + return false; + } + + utf8_char_len = 4 - utf8_char_len_bytes; + return true; +#else + int required_size = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(&orbis_char), 1, nullptr, 0, nullptr, nullptr); + if (required_size > 4) { + UNREACHABLE_MSG("UTF-8 character is never more than 4 bytes"); + } + + *utf8_char_len = WideCharToMultiByte(CP_UTF8, 0, reinterpret_cast(&orbis_char), 1, utf8_char, 4, nullptr, nullptr); + + return *utf8_char_len != 0; +#endif +} + +bool ImeDialogState::ConvertUTF8CharToOrbis(const char* utf8_char, char16_t& orbis_char) { +#ifndef _WIN32 + std::size_t utf8_char_len_bytes = 4 * sizeof(char); + std::size_t orbis_char_len_bytes = sizeof(char16_t); + + char* utf8_char_ptr = const_cast(utf8_char); + char16_t* orbis_char_ptr = &orbis_char; + + std::size_t result = iconv(utf8_to_orbis, (char**)&utf8_char_ptr, &utf8_char_len_bytes, (char**)&orbis_char_ptr, &orbis_char_len_bytes); + + if (result == (std::size_t)-1) { + return false; + } + + return true; +#else + int required_size = MultiByteToWideChar(CP_UTF8, 0, utf8_char, std::strlen(utf8_char), reinterpret_cast(&orbis_char), 1); + return required_size != 0; +#endif +} + +ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status, OrbisImeDialogResult* result) + : state(state), status(status), result(result) { + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi::~ImeDialogUi() { + RemoveLayer(this); +} + +ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept + : state(other.state), status(other.status), result(other.result) { + + if (state) std::scoped_lock lock(state->mutex); + if (other.state) std::scoped_lock lock2(other.state->mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi other) { + if (state) std::scoped_lock lock(state->mutex); + if (other.state) std::scoped_lock lock2(other.state->mutex); + std::swap(state, other.state); + std::swap(status, other.status); + std::swap(result, other.result); + + if (state) { + AddLayer(this); + } + + return *this; +} + +void ImeDialogUi::Draw() { + if (!state) { + return; + } + + std::unique_lock lock{state->mutex}; + + if (!status || *status != OrbisImeDialogStatus::RUNNING) { + return; + } + + std::scoped_lock lock2{state->mutex}; + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->is_multiLine) { + window_size = {400.0f, 200.0f}; + } else { + window_size = {400.0f, 100.0f}; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + first_render = false; + + if (Begin("IME Dialog#ImeDialog", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + Separator(); + + if (state->title) { + SetWindowFontScale(1.7f); + TextUnformatted(state->title); + SetWindowFontScale(1.0f); + Separator(); + } + + if (state->is_multiLine) { + DrawMultiLineInputText(); + } else { + DrawInputText(); + } + + Separator(); + + const char* button_text; + + switch (state->enter_label) { + case OrbisImeEnterLabel::GO: + button_text = "Go#ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEARCH: + button_text = "Search#ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEND: + button_text = "Send#ImeDialogOK"; + break; + case OrbisImeEnterLabel::DEFAULT: + default: + button_text = "OK#ImeDialogOK"; + break; + } + + if (Button(button_text, BUTTON_SIZE)) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::OK; + } + + if (Button("Cancel#ImeDialogCancel", BUTTON_SIZE)) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED; + + } + } + End(); +} + +void ImeDialogUi::DrawInputText() { + if (InputTextEx("##ImeDialogInput", state->placeholder, state->current_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH, ImVec2(0, 0), ImGuiInputTextFlags_CallbackCharFilter, InputTextCallback, this)) { + state->input_changed = true; + } +} + +void ImeDialogUi::DrawMultiLineInputText() { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion" + if (InputTextEx("##ImeDialogInput", state->placeholder, state->current_text, ORBIS_IME_DIALOG_MAX_TEXT_LENGTH, ImVec2(380.0f, 100.0f), ImGuiInputTextFlags_CallbackCharFilter | ImGuiInputTextFlags_Multiline, InputTextCallback, this)) { + state->input_changed = true; + } +#pragma clang diagnostic pop +} + +int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeDialogUi* ui = static_cast(data->UserData); + + ASSERT(ui); + + // ImGui encodes ImWchar32 as multi-byte UTF-8 characters + char* event_char = reinterpret_cast(data->EventChar); + + // Call the keyboard filter + OrbisImeKeycode src_keycode = { + .keycode = 0, + .character = 0, + .status = 1, // ??? 1 = key pressed, 0 = key released + .type = OrbisImeKeyboardType::ENGLISH_US, //TODO set this to the correct value (maybe use the current language?) + .userId = ui->state->userId, + .resourceId = 0, + .timestamp = 0 + }; + + if (!ui->state->ConvertUTF8CharToOrbis(event_char, src_keycode.character)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8"); + return 0; + } + src_keycode.keycode = src_keycode.character; //TODO set this to the correct value + + u16 out_keycode; + u32 out_status; + + ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); + + //TODO. set the keycode + + return 0; +} + } // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.h b/src/core/libraries/dialogs/ime_dialog_ui.h old mode 100644 new mode 100755 index 25ef65105..fba0f4808 --- a/src/core/libraries/dialogs/ime_dialog_ui.h +++ b/src/core/libraries/dialogs/ime_dialog_ui.h @@ -3,6 +3,7 @@ #pragma once +#include #ifndef _WIN32 #include #endif @@ -18,22 +19,26 @@ class ImeDialogUi; class ImeDialogState final { friend ImeDialogUi; - OrbisImeDialogStatus status = OrbisImeDialogStatus::NONE; + bool input_changed = false; s32 userId{}; - bool isMultiLine{}; + bool is_multiLine{}; OrbisImeType type{}; - OrbisImeEnterLabel enterLabel{}; - OrbisImeTextFilter textFilter{}; - OrbisImeExtKeyboardFilter extKeyboardFilter{}; - u32 maxTextLength{}; - char16_t* textBuffer{}; - wchar_t* title = nullptr; - wchar_t* placeholder = nullptr; - wchar_t currentText[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH] = {0}; + OrbisImeEnterLabel enter_label{}; + OrbisImeTextFilter text_filter{}; + OrbisImeExtKeyboardFilter keyboard_filter{}; + u32 max_text_length{}; + char16_t* text_buffer{}; + char* title = nullptr; + char* placeholder = nullptr; + + // A character can hold up to 4 bytes in UTF-8 + char current_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4] = {0}; + + std::mutex mutex; #ifndef _WIN32 - iconv_t orbis_to_native = (iconv_t)-1; - iconv_t native_to_orbis = (iconv_t)-1; + iconv_t orbis_to_utf8 = (iconv_t)-1; + iconv_t utf8_to_orbis = (iconv_t)-1; #endif public: ImeDialogState(const OrbisImeDialogParam* param = nullptr, const OrbisImeParamExtended* extended = nullptr); @@ -42,23 +47,36 @@ public: ImeDialogState() = default; bool CallTextFilter(); - private: - bool ConvertOrbisToNative(const char16_t* orbis_text, std::size_t orbis_text_len, wchar_t* native_text, std::size_t native_text_len); - bool ConvertNativeToOrbis(const wchar_t* native_text, std::size_t native_text_len, char16_t* orbis_text, std::size_t orbis_text_len); + bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status); + + bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, std::size_t native_text_len); + bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len, char16_t* orbis_text, std::size_t orbis_text_len); + bool ConvertOrbisCharToUTF8(const char16_t orbis_char, char* utf8_char, std::size_t& utf8_char_len); + bool ConvertUTF8CharToOrbis(const char* utf8_char, char16_t& orbis_char); }; class ImeDialogUi final : public ImGui::Layer { - -public: - explicit ImeDialogUi(); + ImeDialogState* state{}; + OrbisImeDialogStatus* status{}; + OrbisImeDialogResult* result{}; + bool first_render = true; + +public: + explicit ImeDialogUi(ImeDialogState* state = nullptr, OrbisImeDialogStatus* status = nullptr, OrbisImeDialogResult* result = nullptr); ~ImeDialogUi() override; ImeDialogUi(const ImeDialogUi& other) = delete; ImeDialogUi(ImeDialogUi&& other) noexcept; ImeDialogUi& operator=(ImeDialogUi other); void Draw() override; + +private: + void DrawInputText(); + void DrawMultiLineInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); }; } // namespace Libraries::ImeDialog