From 66f1bb937f056a899d7e7e6be8090fe98b194d0f Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Fri, 11 Oct 2024 03:53:32 +0200 Subject: [PATCH] ime_dialog: Initial implementation (#1267) * Add C string types * Prepare existing enums * Added missing enums * Types update * State base * Compile Ime Dialog UI * UI implementation * Scoped lock * Functional implementation * Link against iconv on macOS * Fix building on windows * Better UI * clang-format * Some fixes and cleanup * Enable reserved checks * clang-format * Fix default text encoding max size * clang-format (again) * Some review changes * Use std::vector for dynamic strings * Use CString * Accept dialog on enter press * clang-format * Use ImGUI for encoding/decoding --- CMakeLists.txt | 2 + src/common/cstring.h | 22 +- src/core/libraries/dialogs/ime_dialog.cpp | 207 ++++++++-- src/core/libraries/dialogs/ime_dialog.h | 280 +++++++------ src/core/libraries/dialogs/ime_dialog_ui.cpp | 390 +++++++++++++++++++ src/core/libraries/dialogs/ime_dialog_ui.h | 84 ++++ src/imgui/imgui_config.h | 5 +- 7 files changed, 847 insertions(+), 143 deletions(-) mode change 100644 => 100755 CMakeLists.txt create mode 100644 src/core/libraries/dialogs/ime_dialog_ui.cpp create mode 100644 src/core/libraries/dialogs/ime_dialog_ui.h diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 8a41e1ac..7c58af6b --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,8 @@ set(LIBC_SOURCES src/core/libraries/libc_internal/libc_internal.cpp set(DIALOGS_LIB src/core/libraries/dialogs/error_dialog.cpp src/core/libraries/dialogs/error_dialog.h + src/core/libraries/dialogs/ime_dialog_ui.cpp + src/core/libraries/dialogs/ime_dialog_ui.h src/core/libraries/dialogs/ime_dialog.cpp src/core/libraries/dialogs/ime_dialog.h src/core/libraries/dialogs/error_codes.h diff --git a/src/common/cstring.h b/src/common/cstring.h index fb29443e..45c291c1 100644 --- a/src/common/cstring.h +++ b/src/common/cstring.h @@ -81,34 +81,42 @@ public: return std::basic_string_view{data}; } - char* begin() { + T* begin() { if (this == nullptr) { return nullptr; } return data; } - const char* begin() const { + const T* begin() const { if (this == nullptr) { return nullptr; } return data; } - char* end() { + T* end() { if (this == nullptr) { return nullptr; } return data + N; } - const char* end() const { + const T* end() const { if (this == nullptr) { return nullptr; } return data + N; } + constexpr std::size_t capacity() const { + return N; + } + + std::size_t size() const { + return std::char_traits::length(data); + } + T& operator[](size_t idx) { return data[idx]; } @@ -152,6 +160,12 @@ public: static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array static_assert(std::weakly_incrementable::Iterator>); +template +using CWString = CString; + +template +using CU16String = CString; + #pragma clang diagnostic pop } // namespace Common \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog.cpp b/src/core/libraries/dialogs/ime_dialog.cpp index ddb1a89f..3d168bb7 100644 --- a/src/core/libraries/dialogs/ime_dialog.cpp +++ b/src/core/libraries/dialogs/ime_dialog.cpp @@ -1,28 +1,75 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "ime_dialog.h" +#include "ime_dialog_ui.h" + +static constexpr std::array MAX_X_POSITIONS = {3840.0f, 1920.0f}; +static constexpr std::array MAX_Y_POSITIONS = {2160.0f, 1080.0f}; namespace Libraries::ImeDialog { -static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; +static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::NONE; +static OrbisImeDialogResult g_ime_dlg_result{}; +static ImeDialogState g_ime_dlg_state{}; +static ImeDialogUi g_ime_dlg_ui; -int PS4_SYSV_ABI sceImeDialogAbort() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) { + if (False(~option & + (OrbisImeDialogOption::MULTILINE | OrbisImeDialogOption::NO_AUTO_COMPLETION))) { + return false; + } + + if (True(option & OrbisImeDialogOption::MULTILINE) && type != OrbisImeType::DEFAULT && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + if (True(option & OrbisImeDialogOption::NO_AUTO_COMPLETION) && type != OrbisImeType::NUMBER && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + return true; } -int PS4_SYSV_ABI sceImeDialogForceClose() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogAbort() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status != OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog not running"); + return Error::DIALOG_NOT_RUNNING; + } + + g_ime_dlg_status = OrbisImeDialogStatus::FINISHED; + g_ime_dlg_result.endstatus = OrbisImeDialogEndStatus::ABORTED; + + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogForTestFunction() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogForceClose() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; +} + +Error PS4_SYSV_ABI sceImeDialogForTestFunction() { + return Error::INTERNAL; } int PS4_SYSV_ABI sceImeDialogGetCurrentStarState() { @@ -45,26 +92,118 @@ int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { - result->endstatus = OrbisImeDialogEndStatus::ORBIS_IME_DIALOG_END_STATUS_OK; - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is not running"); + return Error::DIALOG_NOT_IN_USE; + } + + if (result == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with result (NULL)"); + return Error::INVALID_ADDRESS; + } + + result->endstatus = g_ime_dlg_result.endstatus; + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_state.CopyTextToOrbisBuffer(); + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogGetStatus() { - if (g_ime_dlg_status == OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING) { - return OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_FINISHED; +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() { + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + g_ime_dlg_state.CallTextFilter(); } + return g_ime_dlg_status; } -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - const std::wstring_view text = L"shadPS4"; - param->maxTextLength = text.size(); - std::memcpy(param->inputTextBuffer, text.data(), text.size() * sizeof(wchar_t)); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { + if (g_ime_dlg_status != OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is already running"); + return Error::BUSY; + } + + if (param == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with param (NULL)"); + return Error::INVALID_ADDRESS; + } + + if (!magic_enum::enum_contains(param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->type"); + return Error::INVALID_ADDRESS; + } + + // TODO: do correct param->option validation + // TODO: do correct param->supportedLanguages validation + + if (param->posx < 0.0f || + param->posx >= + MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posx"); + return Error::INVALID_POSX; + } + + if (param->posy < 0.0f || + param->posy >= + MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posy"); + return Error::INVALID_POSY; + } + + if (!magic_enum::enum_contains(param->horizontalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->horizontalAlignment"); + return Error::INVALID_HORIZONTALIGNMENT; + } + + if (!magic_enum::enum_contains(param->verticalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->verticalAlignment"); + return Error::INVALID_VERTICALALIGNMENT; + } + + if (!IsValidOption(param->option, param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->option"); + return Error::INVALID_PARAM; + } + + if (param->inputTextBuffer == nullptr) { + LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer"); + return Error::INVALID_INPUT_TEXT_BUFFER; + } + + if (extended) { + if (magic_enum::enum_contains(extended->priority)) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->priority"); + return Error::INVALID_EXTENDED; + } + + // TODO: do correct extended->option validation + + if ((extended->extKeyboardMode & 0xe3fffffc) != 0) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->extKeyboardMode"); + return Error::INVALID_EXTENDED; + } + + if (extended->disableDevice > 7) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->disableDevice"); + return Error::INVALID_EXTENDED; + } + } + + if (param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) { + LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength"); + return Error::INVALID_MAX_TEXT_LENGTH; + } + + g_ime_dlg_result = {}; + g_ime_dlg_state = ImeDialogState(param, extended); + g_ime_dlg_status = OrbisImeDialogStatus::RUNNING; + g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result); + + return Error::OK; } int PS4_SYSV_ABI sceImeDialogInitInternal() { @@ -87,10 +226,22 @@ int PS4_SYSV_ABI sceImeDialogSetPanelPosition() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogTerm() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogTerm() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog is still running"); + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; } void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/dialogs/ime_dialog.h b/src/core/libraries/dialogs/ime_dialog.h index ffe42b31..66cf9fb9 100644 --- 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 { @@ -11,71 +12,150 @@ class SymbolsResolver; namespace Libraries::ImeDialog { -enum OrbisImeDialogStatus { - ORBIS_IME_DIALOG_STATUS_NONE = 0, - ORBIS_IME_DIALOG_STATUS_RUNNING = 1, - ORBIS_IME_DIALOG_STATUS_FINISHED = 2 +constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 0x78; + +enum class Error : u32 { + OK = 0x0, + BUSY = 0x80bc0001, + NOT_OPENED = 0x80bc0002, + NO_MEMORY = 0x80bc0003, + CONNECTION_FAILED = 0x80bc0004, + TOO_MANY_REQUESTS = 0x80bc0005, + INVALID_TEXT = 0x80bc0006, + EVENT_OVERFLOW = 0x80bc0007, + NOT_ACTIVE = 0x80bc0008, + IME_SUSPENDING = 0x80bc0009, + DEVICE_IN_USE = 0x80bc000a, + INVALID_USER_ID = 0x80bc0010, + INVALID_TYPE = 0x80bc0011, + INVALID_SUPPORTED_LANGUAGES = 0x80bc0012, + INVALID_ENTER_LABEL = 0x80bc0013, + INVALID_INPUT_METHOD = 0x80bc0014, + INVALID_OPTION = 0x80bc0015, + INVALID_MAX_TEXT_LENGTH = 0x80bc0016, + INVALID_INPUT_TEXT_BUFFER = 0x80bc0017, + INVALID_POSX = 0x80bc0018, + INVALID_POSY = 0x80bc0019, + INVALID_HORIZONTALIGNMENT = 0x80bc001a, + INVALID_VERTICALALIGNMENT = 0x80bc001b, + INVALID_EXTENDED = 0x80bc001c, + INVALID_KEYBOARD_TYPE = 0x80bc001d, + INVALID_WORK = 0x80bc0020, + INVALID_ARG = 0x80bc0021, + INVALID_HANDLER = 0x80bc0022, + NO_RESOURCE_ID = 0x80bc0023, + INVALID_MODE = 0x80bc0024, + INVALID_PARAM = 0x80bc0030, + INVALID_ADDRESS = 0x80bc0031, + INVALID_RESERVED = 0x80bc0032, + INVALID_TIMING = 0x80bc0033, + INTERNAL = 0x80bc00ff, + DIALOG_INVALID_TITLE = 0x80bc0101, + DIALOG_NOT_RUNNING = 0x80bc0105, + DIALOG_NOT_FINISHED = 0x80bc0106, + DIALOG_NOT_IN_USE = 0x80bc0107, }; -enum OrbisImeDialogEndStatus { - ORBIS_IME_DIALOG_END_STATUS_OK = 0, - ORBIS_IME_DIALOG_END_STATUS_USER_CANCELED = 1, - ORBIS_IME_DIALOG_END_STATUS_ABORTED = 2 +enum class OrbisImeDialogStatus : u32 { + NONE = 0, + RUNNING = 1, + FINISHED = 2, }; -struct OrbisImeDialogResult { - OrbisImeDialogEndStatus endstatus; - s32 reserved[12]; +enum class OrbisImeDialogEndStatus : u32 { + OK = 0, + USER_CANCELED = 1, + ABORTED = 2, }; -enum OrbisImeType { - ORBIS_IME_TYPE_DEFAULT = 0, - ORBIS_IME_TYPE_BASIC_LATIN = 1, - ORBIS_IME_TYPE_URL = 2, - ORBIS_IME_TYPE_MAIL = 3, - ORBIS_IME_TYPE_NUMBER = 4 +enum class OrbisImeType : u32 { + DEFAULT = 0, + BASIC_LATIN = 1, + URL = 2, + MAIL = 3, + NUMBER = 4, }; -enum OrbisImeEnterLabel { - ORBIS_IME_ENTER_LABEL_DEFAULT = 0, - ORBIS_IME_ENTER_LABEL_SEND = 1, - ORBIS_IME_ENTER_LABEL_SEARCH = 2, - ORBIS_IME_ENTER_LABEL_GO = 3 -}; -enum OrbiImeInputMethod { ORBIS_IME_INPUT_METHOD_DEFAULT = 0 }; - -typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText, - u32 srcTextLength); - -enum OrbisImeHorizontalAlignment { - ORBIS_IME_HALIGN_LEFT = 0, - ORBIS_IME_HALIGN_CENTER = 1, - ORBIS_IME_HALIGN_RIGHT = 2 +enum class OrbisImeEnterLabel : u32 { + DEFAULT = 0, + SEND = 1, + SEARCH = 2, + GO = 3, }; -enum OrbisImeVerticalAlignment { - ORBIS_IME_VALIGN_TOP = 0, - ORBIS_IME_VALIGN_CENTER = 1, - ORBIS_IME_VALIGN_BOTTOM = 2 +enum class OrbisImeDialogOption : u32 { + DEFAULT = 0, + MULTILINE = 1, + NO_AUTO_CORRECTION = 2, + NO_AUTO_COMPLETION = 4, + // TODO: Document missing options + LARGE_RESOLUTION = 1024, }; -struct OrbisImeDialogParam { - s32 userId; - OrbisImeType type; - u64 supportedLanguages; - OrbisImeEnterLabel enterLabel; - OrbiImeInputMethod inputMethod; - OrbisImeTextFilter filter; - u32 option; - u32 maxTextLength; - wchar_t* inputTextBuffer; - float posx; - float posy; - OrbisImeHorizontalAlignment horizontalAlignment; - OrbisImeVerticalAlignment verticalAlignment; - const wchar_t* placeholder; - const wchar_t* title; - s8 reserved[16]; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption) + +enum class OrbisImeInputMethod : u32 { + DEFAULT = 0, +}; + +enum class OrbisImeHorizontalAlignment : u32 { + LEFT = 0, + CENTER = 1, + RIGHT = 2, +}; + +enum class OrbisImeVerticalAlignment : u32 { + TOP = 0, + CENTER = 1, + BOTTOM = 2, +}; + +enum class OrbisImePanelPriority : u32 { + DEFAULT = 0, + ALPHABET = 1, + SYMBOL = 2, + ACCENT = 3, +}; + +enum class OrbisImeKeyboardType : u32 { + NONE = 0, + DANISH = 1, + GERMAN = 2, + GERMAN_SW = 3, + ENGLISH_US = 4, + ENGLISH_GB = 5, + SPANISH = 6, + SPANISH_LA = 7, + FINNISH = 8, + FRENCH = 9, + FRENCH_BR = 10, + FRENCH_CA = 11, + FRENCH_SW = 12, + ITALIAN = 13, + DUTCH = 14, + NORWEGIAN = 15, + POLISH = 16, + PORTUGUESE_BR = 17, + PORTUGUESE_PT = 18, + RUSSIAN = 19, + SWEDISH = 20, + TURKISH = 21, + JAPANESE_ROMAN = 22, + JAPANESE_KANA = 23, + KOREAN = 24, + SM_CHINESE = 25, + TR_CHINESE_ZY = 26, + TR_CHINESE_PY_HK = 27, + TR_CHINESE_PY_TW = 28, + TR_CHINESE_CG = 29, + ARABIC_AR = 30, + THAI = 31, + CZECH = 32, + GREEK = 33, + INDONESIAN = 34, + VIETNAMESE = 35, + ROMANIAN = 36, + HUNGARIAN = 37, }; struct OrbisImeColor { @@ -85,57 +165,14 @@ struct OrbisImeColor { u8 a; }; -enum OrbisImePanelPriority { - ORBIS_IME_PANEL_PRIORITY_DEFAULT = 0, - ORBIS_IME_PANEL_PRIORITY_ALPHABET = 1, - ORBIS_IME_PANEL_PRIORITY_SYMBOL = 2, - ORBIS_IME_PANEL_PRIORITY_ACCENT = 3 -}; - -enum OrbisImeKeyboardType { - ORBIS_IME_KEYBOARD_TYPE_NONE = 0, - ORBIS_IME_KEYBOARD_TYPE_DANISH = 1, - ORBIS_IME_KEYBOARD_TYPE_GERMAN = 2, - ORBIS_IME_KEYBOARD_TYPE_GERMAN_SW = 3, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_US = 4, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_GB = 5, - ORBIS_IME_KEYBOARD_TYPE_SPANISH = 6, - ORBIS_IME_KEYBOARD_TYPE_SPANISH_LA = 7, - ORBIS_IME_KEYBOARD_TYPE_FINNISH = 8, - ORBIS_IME_KEYBOARD_TYPE_FRENCH = 9, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_BR = 10, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_CA = 11, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_SW = 12, - ORBIS_IME_KEYBOARD_TYPE_ITALIAN = 13, - ORBIS_IME_KEYBOARD_TYPE_DUTCH = 14, - ORBIS_IME_KEYBOARD_TYPE_NORWEGIAN = 15, - ORBIS_IME_KEYBOARD_TYPE_POLISH = 16, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_BR = 17, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_PT = 18, - ORBIS_IME_KEYBOARD_TYPE_RUSSIAN = 19, - ORBIS_IME_KEYBOARD_TYPE_SWEDISH = 20, - ORBIS_IME_KEYBOARD_TYPE_TURKISH = 21, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_ROMAN = 22, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_KANA = 23, - ORBIS_IME_KEYBOARD_TYPE_KOREAN = 24, - ORBIS_IME_KEYBOARD_TYPE_SM_CHINESE = 25, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_ZY = 26, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_HK = 27, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_TW = 28, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_CG = 29, - ORBIS_IME_KEYBOARD_TYPE_ARABIC_AR = 30, - ORBIS_IME_KEYBOARD_TYPE_THAI = 31, - ORBIS_IME_KEYBOARD_TYPE_CZECH = 32, - ORBIS_IME_KEYBOARD_TYPE_GREEK = 33, - ORBIS_IME_KEYBOARD_TYPE_INDONESIAN = 34, - ORBIS_IME_KEYBOARD_TYPE_VIETNAMESE = 35, - ORBIS_IME_KEYBOARD_TYPE_ROMANIAN = 36, - ORBIS_IME_KEYBOARD_TYPE_HUNGARIAN = 37 +struct OrbisImeDialogResult { + OrbisImeDialogEndStatus endstatus; + s32 reserved[12]; }; struct OrbisImeKeycode { u16 keycode; - wchar_t character; + char16_t character; u32 status; OrbisImeKeyboardType type; s32 userId; @@ -143,11 +180,34 @@ struct OrbisImeKeycode { u64 timestamp; }; -typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode, - u32* outStatus, void* reserved); +typedef PS4_SYSV_ABI int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, + const char16_t* srcText, u32 srcTextLength); + +typedef PS4_SYSV_ABI int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, + u16* outKeycode, u32* outStatus, + void* reserved); + +struct OrbisImeDialogParam { + s32 userId; + OrbisImeType type; + u64 supportedLanguages; + OrbisImeEnterLabel enterLabel; + OrbisImeInputMethod inputMethod; + OrbisImeTextFilter filter; + OrbisImeDialogOption option; + u32 maxTextLength; + char16_t* inputTextBuffer; + float posx; + float posy; + OrbisImeHorizontalAlignment horizontalAlignment; + OrbisImeVerticalAlignment verticalAlignment; + const char16_t* placeholder; + const char16_t* title; + s8 reserved[16]; +}; struct OrbisImeParamExtended { - u32 option; + u32 option; // OrbisImeDialogOptionExtended OrbisImeColor colorBase; OrbisImeColor colorLine; OrbisImeColor colorTextField; @@ -165,21 +225,21 @@ struct OrbisImeParamExtended { int8_t reserved[60]; }; -int PS4_SYSV_ABI sceImeDialogAbort(); -int PS4_SYSV_ABI sceImeDialogForceClose(); -int PS4_SYSV_ABI sceImeDialogForTestFunction(); +Error PS4_SYSV_ABI sceImeDialogAbort(); +Error PS4_SYSV_ABI sceImeDialogForceClose(); +Error PS4_SYSV_ABI sceImeDialogForTestFunction(); int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(); int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(); int PS4_SYSV_ABI sceImeDialogGetPanelSize(); int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(); -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); -/*OrbisImeDialogStatus*/ int PS4_SYSV_ABI sceImeDialogGetStatus(); -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus(); +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); int PS4_SYSV_ABI sceImeDialogInitInternal(); int PS4_SYSV_ABI sceImeDialogInitInternal2(); int PS4_SYSV_ABI sceImeDialogInitInternal3(); int PS4_SYSV_ABI sceImeDialogSetPanelPosition(); -int PS4_SYSV_ABI sceImeDialogTerm(); +Error PS4_SYSV_ABI sceImeDialogTerm(); void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.cpp b/src/core/libraries/dialogs/ime_dialog_ui.cpp new file mode 100644 index 00000000..48f5d75d --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.cpp @@ -0,0 +1,390 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#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" + +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; + is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE); + is_numeric = param->type == OrbisImeType::NUMBER; + type = param->type; + enter_label = param->enterLabel; + text_filter = param->filter; + keyboard_filter = extended ? extended->extKeyboardFilter : nullptr; + max_text_length = param->maxTextLength; + text_buffer = param->inputTextBuffer; + + if (param->title) { + std::size_t title_len = std::char_traits::length(param->title); + title.resize(title_len * 4 + 1); + title[title_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding"); + } + } + + if (param->placeholder) { + std::size_t placeholder_len = std::char_traits::length(param->placeholder); + placeholder.resize(placeholder_len * 4 + 1); + placeholder[placeholder_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0], + placeholder_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding"); + } + } + + std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } +} + +ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept + : input_changed(other.input_changed), userId(other.userId), is_multiLine(other.is_multiLine), + is_numeric(other.is_numeric), type(other.type), enter_label(other.enter_label), + text_filter(other.text_filter), keyboard_filter(other.keyboard_filter), + max_text_length(other.max_text_length), text_buffer(other.text_buffer), + title(std::move(other.title)), placeholder(std::move(other.placeholder)), + current_text(other.current_text) { + + other.text_buffer = nullptr; +} + +ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) { + if (this != &other) { + input_changed = other.input_changed; + userId = other.userId; + is_multiLine = other.is_multiLine; + is_numeric = other.is_numeric; + type = other.type; + enter_label = other.enter_label; + text_filter = other.text_filter; + keyboard_filter = other.keyboard_filter; + max_text_length = other.max_text_length; + text_buffer = other.text_buffer; + title = std::move(other.title); + placeholder = std::move(other.placeholder); + current_text = other.current_text; + + other.text_buffer = nullptr; + } + + return *this; +} + +bool ImeDialogState::CopyTextToOrbisBuffer() { + if (!text_buffer) { + return false; + } + + return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer, + max_text_length); +} + +bool ImeDialogState::CallTextFilter() { + if (!text_filter || !input_changed) { + return true; + } + + input_changed = false; + + char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 src_text_length = current_text.size(); + 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.begin(), 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.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + return false; + } + + return true; +} + +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) { + + std::fill(utf8_text, utf8_text + utf8_text_len, '\0'); + const ImWchar* orbis_text_ptr = reinterpret_cast(orbis_text); + ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len); + + return true; +} + +bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len) { + + std::fill(orbis_text, orbis_text + orbis_text_len, u'\0'); + ImTextStrFromUtf8(reinterpret_cast(orbis_text), orbis_text_len, utf8_text, nullptr); + + return true; +} + +ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status, + OrbisImeDialogResult* result) + : state(state), status(status), result(result) { + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi::~ImeDialogUi() { + std::scoped_lock lock(draw_mutex); + + Free(); +} + +ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept + : state(other.state), status(other.status), result(other.result), + first_render(other.first_render) { + + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi&& other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + Free(); + + state = other.state; + status = other.status; + result = other.result; + first_render = other.first_render; + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } + + return *this; +} + +void ImeDialogUi::Free() { + RemoveLayer(this); +} + +void ImeDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (!state) { + return; + } + + if (!status || *status != OrbisImeDialogStatus::RUNNING) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->is_multiLine) { + window_size = {500.0f, 300.0f}; + } else { + window_size = {500.0f, 150.0f}; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + if (Begin("IME Dialog##ImeDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + if (!state->title.empty()) { + SetWindowFontScale(1.7f); + TextUnformatted(state->title.data()); + SetWindowFontScale(1.0f); + } + + if (state->is_multiLine) { + DrawMultiLineInputText(); + } else { + DrawInputText(); + } + + SetCursorPosY(GetCursorPosY() + 10.0f); + + 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; + } + + float button_spacing = 10.0f; + float total_button_width = BUTTON_SIZE.x * 2 + button_spacing; + float button_start_pos = (window_size.x - total_button_width) / 2.0f; + + SetCursorPosX(button_start_pos); + + if (Button(button_text, BUTTON_SIZE) || + (!state->is_multiLine && IsKeyPressed(ImGuiKey_Enter))) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::OK; + } + + SameLine(0.0f, button_spacing); + + if (Button("Cancel##ImeDialogCancel", BUTTON_SIZE)) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED; + } + } + End(); + + first_render = false; +} + +void ImeDialogUi::DrawInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; + SetCursorPosX(20.0f); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter, + InputTextCallback, this)) { + state->input_changed = true; + } +} + +void ImeDialogUi::DrawMultiLineInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 200.0f}; + SetCursorPosX(20.0f); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackCharFilter | + static_cast(ImGuiInputTextFlags_Multiline); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, flags, InputTextCallback, this)) { + state->input_changed = true; + } +} + +int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeDialogUi* ui = static_cast(data->UserData); + + ASSERT(ui); + + // Should we filter punctuation? + if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') && + data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') { + return 1; + } + + if (!ui->state->keyboard_filter) { + return 0; + } + + // 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->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { + 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 new file mode 100644 index 00000000..96c83954 --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/cstring.h" +#include "common/types.h" +#include "core/libraries/dialogs/ime_dialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::ImeDialog { + +class ImeDialogUi; + +class ImeDialogState final { + friend ImeDialogUi; + + bool input_changed = false; + + s32 userId{}; + bool is_multiLine{}; + bool is_numeric{}; + OrbisImeType type{}; + OrbisImeEnterLabel enter_label{}; + OrbisImeTextFilter text_filter{}; + OrbisImeExtKeyboardFilter keyboard_filter{}; + u32 max_text_length{}; + char16_t* text_buffer{}; + std::vector title; + std::vector placeholder; + + // A character can hold up to 4 bytes in UTF-8 + Common::CString current_text; + +public: + ImeDialogState(const OrbisImeDialogParam* param = nullptr, + const OrbisImeParamExtended* extended = nullptr); + ImeDialogState(const ImeDialogState& other) = delete; + ImeDialogState(ImeDialogState&& other) noexcept; + ImeDialogState& operator=(ImeDialogState&& other); + + bool CopyTextToOrbisBuffer(); + bool CallTextFilter(); + +private: + 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); +}; + +class ImeDialogUi final : public ImGui::Layer { + ImeDialogState* state{}; + OrbisImeDialogStatus* status{}; + OrbisImeDialogResult* result{}; + + bool first_render = true; + std::mutex draw_mutex; + +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 Free(); + + void DrawInputText(); + void DrawMultiLineInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); +}; + +} // namespace Libraries::ImeDialog diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 66d2b25c..ccb084d9 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -21,7 +21,6 @@ extern void assert_fail_debug_msg(const char* msg); } \ }()) -#define IMGUI_USE_WCHAR32 #define IMGUI_ENABLE_STB_TRUETYPE #define IMGUI_DEFINE_MATH_OPERATORS @@ -30,3 +29,7 @@ extern void assert_fail_debug_msg(const char* msg); #define IM_VEC4_CLASS_EXTRA \ constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} + +#ifdef IMGUI_USE_WCHAR32 +#error "This project uses 16 bits wchar standard like Orbis" +#endif \ No newline at end of file