diff --git a/CMakeLists.txt b/CMakeLists.txt index 46d3249cd..9d3ea35c3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,8 +290,6 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/audio3d/audio3d_error.h src/core/libraries/audio3d/audio3d_impl.cpp src/core/libraries/audio3d/audio3d_impl.h - src/core/libraries/ime/ime.cpp - src/core/libraries/ime/ime.h src/core/libraries/game_live_streaming/gamelivestreaming.cpp src/core/libraries/game_live_streaming/gamelivestreaming.h src/core/libraries/remote_play/remoteplay.cpp @@ -311,13 +309,17 @@ set(LIBC_SOURCES src/core/libraries/libc_internal/libc_internal.cpp src/core/libraries/libc_internal/libc_internal.h ) -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 +set(IME_LIB src/core/libraries/ime/error_dialog.cpp + src/core/libraries/ime/error_dialog.h + src/core/libraries/ime/ime_common.h + src/core/libraries/ime/ime_dialog_ui.cpp + src/core/libraries/ime/ime_dialog_ui.h + src/core/libraries/ime/ime_dialog.cpp + src/core/libraries/ime/ime_dialog.h + src/core/libraries/ime/ime_ui.cpp + src/core/libraries/ime/ime_ui.h + src/core/libraries/ime/ime.cpp + src/core/libraries/ime/ime.h ) set(PAD_LIB src/core/libraries/pad/pad.cpp @@ -499,7 +501,7 @@ set(CORE src/core/aerolib/stubs.cpp ${RANDOM_LIB} ${USBD_LIB} ${MISC_LIBS} - ${DIALOGS_LIB} + ${IME_LIB} ${FIBER_LIB} ${DEV_TOOLS} src/core/debug_state.cpp diff --git a/src/core/libraries/dialogs/error_codes.h b/src/core/libraries/dialogs/error_codes.h deleted file mode 100644 index 587a2a973..000000000 --- a/src/core/libraries/dialogs/error_codes.h +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -constexpr int ORBIS_ERROR_DIALOG_ERROR_NOT_INITIALIZED = 0x80ED0001; // not initialized -constexpr int ORBIS_ERROR_DIALOG_ERROR_ALREADY_INITIALIZED = 0x80ED0002; // already initialized -constexpr int ORBIS_ERROR_DIALOG_ERROR_PARAM_INVALID = 0x80ED0003; // Parameter is invalid -constexpr int ORBIS_ERROR_DIALOG_ERROR_UNEXPECTED_FATAL = 0x80ED0004; // Unexpected fatal error -constexpr int ORBIS_ERROR_DIALOG_ERROR_INVALID_STATE = 0x80ED0005; // not in a callable state -constexpr int ORBIS_ERROR_DIALOG_ERROR_SERVICE_BUSY = 0x80ED0006; // Process is busy -constexpr int ORBIS_ERROR_DIALOG_ERROR_INVALID_USER_ID = 0x80ED0007; // Invalid user ID diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index dae285664..c3efe0e97 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -507,4 +507,49 @@ constexpr int ORBIS_FIBER_ERROR_ALIGNMENT = 0x80590002; constexpr int ORBIS_FIBER_ERROR_RANGE = 0x80590003; constexpr int ORBIS_FIBER_ERROR_INVALID = 0x80590004; constexpr int ORBIS_FIBER_ERROR_PERMISSION = 0x80590005; -constexpr int ORBIS_FIBER_ERROR_STATE = 0x80590006; \ No newline at end of file +constexpr int ORBIS_FIBER_ERROR_STATE = 0x80590006; + +// ImeDialog library +constexpr int ORBIS_ERROR_DIALOG_ERROR_NOT_INITIALIZED = 0x80ED0001; +constexpr int ORBIS_ERROR_DIALOG_ERROR_ALREADY_INITIALIZED = 0x80ED0002; +constexpr int ORBIS_ERROR_DIALOG_ERROR_PARAM_INVALID = 0x80ED0003; +constexpr int ORBIS_ERROR_DIALOG_ERROR_UNEXPECTED_FATAL = 0x80ED0004; +constexpr int ORBIS_ERROR_DIALOG_ERROR_INVALID_STATE = 0x80ED0005; +constexpr int ORBIS_ERROR_DIALOG_ERROR_SERVICE_BUSY = 0x80ED0006; +constexpr int ORBIS_ERROR_DIALOG_ERROR_INVALID_USER_ID = 0x80ED0007; + +// Ime library +constexpr int ORBIS_IME_ERROR_BUSY = 0x80BC0001; +constexpr int ORBIS_IME_ERROR_NOT_OPENED = 0x80BC0002; +constexpr int ORBIS_IME_ERROR_NO_MEMORY = 0x80BC0003; +constexpr int ORBIS_IME_ERROR_CONNECTION_FAILED = 0x80BC0004; +constexpr int ORBIS_IME_ERROR_TOO_MANY_REQUESTS = 0x80BC0005; +constexpr int ORBIS_IME_ERROR_INVALID_TEXT = 0x80BC0006; +constexpr int ORBIS_IME_ERROR_EVENT_OVERFLOW = 0x80BC0007; +constexpr int ORBIS_IME_ERROR_NOT_ACTIVE = 0x80BC0008; +constexpr int ORBIS_IME_ERROR_IME_SUSPENDING = 0x80BC0009; +constexpr int ORBIS_IME_ERROR_DEVICE_IN_USE = 0x80BC000A; +constexpr int ORBIS_IME_ERROR_INVALID_USER_ID = 0x80BC0010; +constexpr int ORBIS_IME_ERROR_INVALID_TYPE = 0x80BC0011; +constexpr int ORBIS_IME_ERROR_INVALID_SUPPORTED_LANGUAGES = 0x80BC0012; +constexpr int ORBIS_IME_ERROR_INVALID_ENTER_LABEL = 0x80BC0013; +constexpr int ORBIS_IME_ERROR_INVALID_INPUT_METHOD = 0x80BC0014; +constexpr int ORBIS_IME_ERROR_INVALID_OPTION = 0x80BC0015; +constexpr int ORBIS_IME_ERROR_INVALID_MAX_TEXT_LENGTH = 0x80BC0016; +constexpr int ORBIS_IME_ERROR_INVALID_INPUT_TEXT_BUFFER = 0x80BC0017; +constexpr int ORBIS_IME_ERROR_INVALID_POSX = 0x80BC0018; +constexpr int ORBIS_IME_ERROR_INVALID_POSY = 0x80BC0019; +constexpr int ORBIS_IME_ERROR_INVALID_HORIZONTAL_ALIGNMENT = 0x80BC001A; +constexpr int ORBIS_IME_ERROR_INVALID_VERTICAL_ALIGNMENT = 0x80BC001B; +constexpr int ORBIS_IME_ERROR_INVALID_EXTENDED = 0x80BC001C; +constexpr int ORBIS_IME_ERROR_INVALID_KEYBOARD_TYPE = 0x80BC001D; +constexpr int ORBIS_IME_ERROR_INVALID_WORK = 0x80BC0020; +constexpr int ORBIS_IME_ERROR_INVALID_ARG = 0x80BC0021; +constexpr int ORBIS_IME_ERROR_INVALID_HANDLER = 0x80BC0022; +constexpr int ORBIS_IME_ERROR_NO_RESOURCE_ID = 0x80BC0023; +constexpr int ORBIS_IME_ERROR_INVALID_MODE = 0x80BC0024; +constexpr int ORBIS_IME_ERROR_INVALID_PARAM = 0x80BC0030; +constexpr int ORBIS_IME_ERROR_INVALID_ADDRESS = 0x80BC0031; +constexpr int ORBIS_IME_ERROR_INVALID_RESERVED = 0x80BC0032; +constexpr int ORBIS_IME_ERROR_INVALID_TIMING = 0x80BC0033; +constexpr int ORBIS_IME_ERROR_INTERNAL = 0x80BC00FF; \ No newline at end of file diff --git a/src/core/libraries/dialogs/error_dialog.cpp b/src/core/libraries/ime/error_dialog.cpp similarity index 100% rename from src/core/libraries/dialogs/error_dialog.cpp rename to src/core/libraries/ime/error_dialog.cpp diff --git a/src/core/libraries/dialogs/error_dialog.h b/src/core/libraries/ime/error_dialog.h similarity index 100% rename from src/core/libraries/dialogs/error_dialog.h rename to src/core/libraries/ime/error_dialog.h diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 13a70acf7..0310c5153 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -1,14 +1,108 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include "ime.h" +#include "ime_ui.h" #include "common/logging/log.h" +#include "common/singleton.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" +#include "core/linker.h" namespace Libraries::Ime { +static std::queue g_ime_events; +static ImeState g_ime_state{}; +static ImeUi g_ime_ui; + +class ImeHandler { +public: + ImeHandler(const OrbisImeKeyboardParam* param) { + Init(param, false); + } + ImeHandler(const OrbisImeParam* param) { + Init(param, true); + } + ~ImeHandler() = default; + + void Init(const void* param, bool ime_mode) { + if (ime_mode) { + m_param.ime = *(OrbisImeParam*)param; + } else { + m_param.key = *(OrbisImeKeyboardParam*)param; + } + m_ime_mode = ime_mode; + + // Open an event to let the game know the IME has started + OrbisImeEvent openEvent{}; + openEvent.id = (ime_mode ? OrbisImeEventId::OPEN : OrbisImeEventId::KEYBOARD_OPEN); + + if (ime_mode) { + sceImeGetPanelSize(&m_param.ime, &openEvent.param.rect.width, + &openEvent.param.rect.height); + openEvent.param.rect.x = m_param.ime.posx; + openEvent.param.rect.y = m_param.ime.posy; + } else { + openEvent.param.resourceIdArray.userId = 1; + openEvent.param.resourceIdArray.resourceId[0] = 1; + } + + Execute(nullptr, &openEvent, true); + + if (ime_mode) { + g_ime_state = ImeState(&m_param.ime); + g_ime_ui = ImeUi(&g_ime_state, &m_param.ime); + } + } + + s32 Update(OrbisImeEventHandler handler) { + std::unique_lock lock{g_ime_state.queue_mutex}; + + while (!g_ime_state.event_queue.empty()) { + OrbisImeEvent event = g_ime_state.event_queue.front(); + g_ime_state.event_queue.pop(); + Execute(handler, &event, false); + } + + return ORBIS_OK; + } + + void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) { + const auto* linker = Common::Singleton::Instance(); + + if (m_ime_mode) { + OrbisImeParam param = m_param.ime; + if (use_param_handler) { + linker->ExecuteGuest(param.handler, param.arg, event); + } else { + linker->ExecuteGuest(handler, param.arg, event); + } + } else { + OrbisImeKeyboardParam param = m_param.key; + if (use_param_handler) { + linker->ExecuteGuest(param.handler, param.arg, event); + } else { + linker->ExecuteGuest(handler, param.arg, event); + } + } + } + + bool IsIme() { + return m_ime_mode; + } + +private: + union ImeParam { + OrbisImeKeyboardParam key; + OrbisImeParam ime; + } m_param{}; + bool m_ime_mode = false; +}; + +static std::unique_ptr g_ime_handler; + int PS4_SYSV_ABI FinalizeImeModule() { LOG_ERROR(Lib_Ime, "(STUBBED) called"); return ORBIS_OK; @@ -34,8 +128,19 @@ int PS4_SYSV_ABI sceImeCheckUpdateTextInfo() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeClose() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceImeClose() { + LOG_INFO(Lib_Ime, "(STUBBED) called"); + + if (!g_ime_handler) { + return ORBIS_IME_ERROR_NOT_OPENED; + } + if (!g_ime_handler->IsIme()) { + return ORBIS_IME_ERROR_NOT_OPENED; + } + + g_ime_handler.release(); + g_ime_ui = ImeUi(); + g_ime_state = ImeState(); return ORBIS_OK; } @@ -104,13 +209,42 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeGetPanelSize() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { + LOG_INFO(Lib_Ime, "called"); + + if (!width || !height) { + return ORBIS_IME_ERROR_INVALID_ADDRESS; + } + + switch (param->type) { + case OrbisImeType::DEFAULT: + case OrbisImeType::BASIC_LATIN: + case OrbisImeType::URL: + case OrbisImeType::MAIL: + // We set our custom sizes, commented sizes are the original ones + *width = 500; // 793 + *height = 100; // 408 + break; + case OrbisImeType::NUMBER: + *width = 370; + *height = 402; + break; + } + return ORBIS_OK; } -int PS4_SYSV_ABI sceImeKeyboardClose() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) { + LOG_INFO(Lib_Ime, "(STUBBED) called"); + + if (!g_ime_handler) { + return ORBIS_IME_ERROR_NOT_OPENED; + } + if (g_ime_handler->IsIme()) { + return ORBIS_IME_ERROR_NOT_OPENED; + } + + g_ime_handler.release(); return ORBIS_OK; } @@ -124,9 +258,19 @@ int PS4_SYSV_ABI sceImeKeyboardGetResourceId() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeKeyboardOpen() { +s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) { LOG_ERROR(Lib_Ime, "(STUBBED) called"); - return ORBIS_OK; + + if (!param) { + return ORBIS_IME_ERROR_INVALID_ADDRESS; + } + if (g_ime_handler) { + return ORBIS_IME_ERROR_BUSY; + } + + // g_ime_handler = std::make_unique(param); + // return ORBIS_OK; + return ORBIS_IME_ERROR_CONNECTION_FAILED; // Fixup } int PS4_SYSV_ABI sceImeKeyboardOpenInternal() { @@ -144,8 +288,19 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeOpen() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); +s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended) { + LOG_INFO(Lib_Ime, "called"); + + if (!g_ime_handler) { + g_ime_handler = std::make_unique(param); + } else { + if (g_ime_handler->IsIme()) { + return ORBIS_IME_ERROR_BUSY; + } + + g_ime_handler->Init((void*)param, true); + } + return ORBIS_OK; } @@ -154,9 +309,15 @@ int PS4_SYSV_ABI sceImeOpenInternal() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeParamInit() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); - return ORBIS_OK; +void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param) { + LOG_INFO(Lib_Ime, "called"); + + if (!param) { + return; + } + + memset(param, 0, sizeof(OrbisImeParam)); + param->userId = -1; } int PS4_SYSV_ABI sceImeSetCandidateIndex() { @@ -164,7 +325,7 @@ int PS4_SYSV_ABI sceImeSetCandidateIndex() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeSetCaret() { +int PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) { LOG_ERROR(Lib_Ime, "(STUBBED) called"); return ORBIS_OK; } @@ -179,9 +340,14 @@ int PS4_SYSV_ABI sceImeSetTextGeometry() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeUpdate() { - LOG_ERROR(Lib_Ime, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { + LOG_TRACE(Lib_Ime, "called"); + + if (!g_ime_handler) { + return ORBIS_IME_ERROR_NOT_OPENED; + } + + return g_ime_handler->Update(handler); } int PS4_SYSV_ABI sceImeVshClearPreedit() { diff --git a/src/core/libraries/ime/ime.h b/src/core/libraries/ime/ime.h index 807616f14..fc98d426a 100644 --- a/src/core/libraries/ime/ime.h +++ b/src/core/libraries/ime/ime.h @@ -5,12 +5,62 @@ #include "common/types.h" +#include "ime_common.h" + namespace Core::Loader { class SymbolsResolver; } namespace Libraries::Ime { +constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048; + +enum class OrbisImeKeyboardOption : u32 { + DEFAULT = 0, + REPEAT = 1, + REPEAT_EACH_KEY = 2, + ADD_OSK = 4, + EFFECTIVE_WITH_TIME = 8, + DISABLE_RESUME = 16, + DISABLE_CAPSLOCK_WITHOUT_SHIFT = 32, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption) + +struct OrbisImeKeyboardParam { + OrbisImeKeyboardOption option; + s8 reserved1[4]; + void* arg; + OrbisImeEventHandler handler; + s8 reserved2[8]; +}; + +struct OrbisImeParam { + s32 userId; + OrbisImeType type; + u64 supportedLanguages; + OrbisImeEnterLabel enterLabel; + OrbisImeInputMethod inputMethod; + OrbisImeTextFilter filter; + u32 option; + u32 maxTextLength; + char16_t* inputTextBuffer; + float posx; + float posy; + OrbisImeHorizontalAlignment horizontalAlignment; + OrbisImeVerticalAlignment verticalAlignment; + void* work; + void* arg; + OrbisImeEventHandler handler; + s8 reserved[8]; +}; + +struct OrbisImeCaret { + f32 x; + f32 y; + u32 height; + u32 index; +}; + int PS4_SYSV_ABI FinalizeImeModule(); int PS4_SYSV_ABI InitializeImeModule(); int PS4_SYSV_ABI sceImeCheckFilterText(); @@ -30,22 +80,22 @@ int PS4_SYSV_ABI sceImeDisableController(); int PS4_SYSV_ABI sceImeFilterText(); int PS4_SYSV_ABI sceImeForTestFunction(); int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(); -int PS4_SYSV_ABI sceImeGetPanelSize(); -int PS4_SYSV_ABI sceImeKeyboardClose(); +s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height); +s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId); int PS4_SYSV_ABI sceImeKeyboardGetInfo(); int PS4_SYSV_ABI sceImeKeyboardGetResourceId(); -int PS4_SYSV_ABI sceImeKeyboardOpen(); +s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param); int PS4_SYSV_ABI sceImeKeyboardOpenInternal(); int PS4_SYSV_ABI sceImeKeyboardSetMode(); int PS4_SYSV_ABI sceImeKeyboardUpdate(); -int PS4_SYSV_ABI sceImeOpen(); +s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended); int PS4_SYSV_ABI sceImeOpenInternal(); -int PS4_SYSV_ABI sceImeParamInit(); +void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param); int PS4_SYSV_ABI sceImeSetCandidateIndex(); -int PS4_SYSV_ABI sceImeSetCaret(); +s32 PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret); int PS4_SYSV_ABI sceImeSetText(); int PS4_SYSV_ABI sceImeSetTextGeometry(); -int PS4_SYSV_ABI sceImeUpdate(); +s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler); int PS4_SYSV_ABI sceImeVshClearPreedit(); int PS4_SYSV_ABI sceImeVshClose(); int PS4_SYSV_ABI sceImeVshConfirmPreedit(); diff --git a/src/core/libraries/ime/ime_common.h b/src/core/libraries/ime/ime_common.h new file mode 100644 index 000000000..7d38c03d7 --- /dev/null +++ b/src/core/libraries/ime/ime_common.h @@ -0,0 +1,184 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/enum.h" +#include "common/types.h" +#include "core/libraries/rtc/rtc.h" + +enum class OrbisImeType : u32 { + DEFAULT = 0, + BASIC_LATIN = 1, + URL = 2, + MAIL = 3, + NUMBER = 4, +}; + +enum class OrbisImeHorizontalAlignment : u32 { + LEFT = 0, + CENTER = 1, + RIGHT = 2, +}; + +enum class OrbisImeVerticalAlignment : u32 { + TOP = 0, + CENTER = 1, + BOTTOM = 2, +}; + +enum class OrbisImeEnterLabel : u32 { + DEFAULT = 0, + SEND = 1, + SEARCH = 2, + GO = 3, +}; + +enum class OrbisImeInputMethod : u32 { + DEFAULT = 0, +}; + +enum class OrbisImeEventId : u32 { + OPEN = 0, + UPDATE_TEXT = 1, + UPDATE_CARET = 2, + PRESS_CLOSE = 4, + PRESS_ENTER = 5, + ABORT = 6, + CANDIDATE_LIST_START = 7, + CANDIDATE_LIST_END = 8, + CANDIDATE_WORD = 9, + CANDIDATE_INDEX = 10, + CANDIDATE_DONE = 11, + CANDIDATE_CANCEL = 12, + CHANGE_DEVICE = 14, + CHANGE_INPUT_METHOD_STATE = 18, + + KEYBOARD_OPEN = 256, + KEYBOARD_KEYCODE_DOWN = 257, + KEYBOARD_KEYCODE_UP = 258, + KEYBOARD_KEYCODE_REPEAT = 259, + KEYBOARD_CONNECTION = 260, + KEYBOARD_DISCONNECTION = 261, + KEYBOARD_ABORT = 262, +}; + +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, +}; + +enum class OrbisImeDeviceType : u32 { + NONE = 0, + CONTROLLER = 1, + EXT_KEYBOARD = 2, + REMOTE_OSK = 3, +}; + +struct OrbisImeRect { + f32 x; + f32 y; + u32 width; + u32 height; +}; + +struct OrbisImeTextAreaProperty { + u32 mode; // OrbisImeTextAreaMode + u32 index; + s32 length; +}; + +struct OrbisImeEditText { + char16_t* str; + u32 caretIndex; + u32 areaNum; + OrbisImeTextAreaProperty textArea[4]; +}; + +struct OrbisImeKeycode { + u16 keycode; + char16_t character; + u32 status; + OrbisImeKeyboardType type; + s32 userId; + u32 resourceId; + Libraries::Rtc::OrbisRtcTick timestamp; +}; + +struct OrbisImeKeyboardResourceIdArray { + s32 userId; + u32 resourceId[6]; +}; + +enum class OrbisImeCaretMovementDirection : u32 { + STILL = 0, + LEFT = 1, + RIGHT = 2, + UP = 3, + DOWN = 4, + HOME = 5, + END = 6, + PAGE_UP = 7, + PAGE_DOWN = 8, + TOP = 9, + BOTTOM = 10, +}; + +union OrbisImeEventParam { + OrbisImeRect rect; + OrbisImeEditText text; + OrbisImeCaretMovementDirection caretMove; + OrbisImeKeycode keycode; + OrbisImeKeyboardResourceIdArray resourceIdArray; + char16_t* candidateWord; + s32 candidateIndex; + OrbisImeDeviceType deviceType; + u32 inputMethodState; + s8 reserved[64]; +}; + +struct OrbisImeEvent { + OrbisImeEventId id; + OrbisImeEventParam param; +}; + +typedef PS4_SYSV_ABI int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, + const char16_t* srcText, u32 srcTextLength); + +typedef PS4_SYSV_ABI void (*OrbisImeEventHandler)(void* arg, const OrbisImeEvent* e); \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog.cpp b/src/core/libraries/ime/ime_dialog.cpp similarity index 100% rename from src/core/libraries/dialogs/ime_dialog.cpp rename to src/core/libraries/ime/ime_dialog.cpp diff --git a/src/core/libraries/dialogs/ime_dialog.h b/src/core/libraries/ime/ime_dialog.h similarity index 76% rename from src/core/libraries/dialogs/ime_dialog.h rename to src/core/libraries/ime/ime_dialog.h index 66cf9fb93..e99d29613 100644 --- a/src/core/libraries/dialogs/ime_dialog.h +++ b/src/core/libraries/ime/ime_dialog.h @@ -5,6 +5,7 @@ #include "common/enum.h" #include "common/types.h" +#include "ime_common.h" namespace Core::Loader { class SymbolsResolver; @@ -68,21 +69,6 @@ enum class OrbisImeDialogEndStatus : u32 { ABORTED = 2, }; -enum class OrbisImeType : u32 { - DEFAULT = 0, - BASIC_LATIN = 1, - URL = 2, - MAIL = 3, - NUMBER = 4, -}; - -enum class OrbisImeEnterLabel : u32 { - DEFAULT = 0, - SEND = 1, - SEARCH = 2, - GO = 3, -}; - enum class OrbisImeDialogOption : u32 { DEFAULT = 0, MULTILINE = 1, @@ -91,25 +77,8 @@ enum class OrbisImeDialogOption : u32 { // TODO: Document missing options LARGE_RESOLUTION = 1024, }; - 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, @@ -117,47 +86,6 @@ enum class OrbisImePanelPriority : u32 { 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 { u8 r; u8 g; @@ -180,9 +108,6 @@ struct OrbisImeKeycode { u64 timestamp; }; -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); diff --git a/src/core/libraries/dialogs/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp similarity index 98% rename from src/core/libraries/dialogs/ime_dialog_ui.cpp rename to src/core/libraries/ime/ime_dialog_ui.cpp index 9d50d2fbb..bba45d0fe 100644 --- a/src/core/libraries/dialogs/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -9,8 +9,8 @@ #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/libraries/ime/ime_dialog.h" +#include "core/libraries/ime/ime_dialog_ui.h" #include "core/linker.h" #include "imgui/imgui_std.h" @@ -22,8 +22,9 @@ namespace Libraries::ImeDialog { ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, const OrbisImeParamExtended* extended) { - if (!param) + if (!param) { return; + } userId = param->userId; is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE); @@ -344,7 +345,6 @@ void ImeDialogUi::DrawMultiLineInputText() { int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { ImeDialogUi* ui = static_cast(data->UserData); - ASSERT(ui); // Should we filter punctuation? diff --git a/src/core/libraries/dialogs/ime_dialog_ui.h b/src/core/libraries/ime/ime_dialog_ui.h similarity index 98% rename from src/core/libraries/dialogs/ime_dialog_ui.h rename to src/core/libraries/ime/ime_dialog_ui.h index 96c83954a..a4cf6e9f7 100644 --- a/src/core/libraries/dialogs/ime_dialog_ui.h +++ b/src/core/libraries/ime/ime_dialog_ui.h @@ -8,7 +8,7 @@ #include #include "common/cstring.h" #include "common/types.h" -#include "core/libraries/dialogs/ime_dialog.h" +#include "core/libraries/ime/ime_dialog.h" #include "imgui/imgui_layer.h" namespace Libraries::ImeDialog { diff --git a/src/core/libraries/ime/ime_ui.cpp b/src/core/libraries/ime/ime_ui.cpp new file mode 100644 index 000000000..09d361263 --- /dev/null +++ b/src/core/libraries/ime/ime_ui.cpp @@ -0,0 +1,252 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "ime_ui.h" +#include "imgui/imgui_std.h" + +namespace Libraries::Ime { + +using namespace ImGui; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + +ImeState::ImeState(const OrbisImeParam* param) { + if (!param) { + return; + } + + work_buffer = param->work; + text_buffer = param->inputTextBuffer; + + std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } +} + +ImeState::ImeState(ImeState&& other) noexcept + : input_changed(other.input_changed), work_buffer(other.work_buffer), + text_buffer(other.text_buffer), current_text(std::move(other.current_text)), + event_queue(std::move(other.event_queue)) { + other.text_buffer = nullptr; +} + +ImeState& ImeState::operator=(ImeState&& other) noexcept { + if (this != &other) { + input_changed = other.input_changed; + work_buffer = other.work_buffer; + text_buffer = other.text_buffer; + current_text = std::move(other.current_text); + event_queue = std::move(other.event_queue); + + other.text_buffer = nullptr; + } + return *this; +} + +void ImeState::SendEvent(OrbisImeEvent* event) { + std::unique_lock lock{queue_mutex}; + event_queue.push(*event); +} + +void ImeState::SendEnterEvent() { + OrbisImeEvent enterEvent{}; + enterEvent.id = OrbisImeEventId::PRESS_ENTER; + SendEvent(&enterEvent); +} + +void ImeState::SendCloseEvent() { + OrbisImeEvent closeEvent{}; + closeEvent.id = OrbisImeEventId::PRESS_CLOSE; + closeEvent.param.text.str = reinterpret_cast(work_buffer); + SendEvent(&closeEvent); +} + +bool ImeState::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 ImeState::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; +} + +ImeUi::ImeUi(ImeState* state, const OrbisImeParam* param) : state(state), ime_param(param) { + if (param) { + AddLayer(this); + } +} + +ImeUi::~ImeUi() { + std::scoped_lock lock(draw_mutex); + Free(); +} + +ImeUi& ImeUi::operator=(ImeUi&& other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + Free(); + + state = other.state; + ime_param = other.ime_param; + first_render = other.first_render; + other.state = nullptr; + other.ime_param = nullptr; + + AddLayer(this); + return *this; +} + +void ImeUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (!state) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + // TODO: Figure out how to properly translate the positions - + // for example, if a game wants to center the IME panel, + // we have to translate the panel position in a way that it + // still becomes centered, as the game normally calculates + // the position assuming a it's running on a 1920x1080 screen, + // whereas we are running on a 1280x720 window size (by default). + // + // e.g. Panel position calculation from a game: + // param.posx = (1920 / 2) - (panelWidth / 2); + // param.posy = (1080 / 2) - (panelHeight / 2); + const auto size = GetIO().DisplaySize; + f32 pos_x = (ime_param->posx / 1920.0f * (float)size.x); + f32 pos_y = (ime_param->posy / 1080.0f * (float)size.y); + + ImVec2 window_pos = {pos_x, pos_y}; + ImVec2 window_size = {500.0f, 100.0f}; + + // SetNextWindowPos(window_pos); + SetNextWindowPos(ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * 0.5f), + ImGuiCond_FirstUseEver, ImVec2(0.5f, 0.5f)); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + if (Begin("IME##Ime", nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + DrawInputText(); + SetCursorPosY(GetCursorPosY() + 10.0f); + + const char* button_text; + button_text = "Done##ImeDone"; + + 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) || (IsKeyPressed(ImGuiKey_Enter))) { + state->SendEnterEvent(); + } + + SameLine(0.0f, button_spacing); + + if (Button("Close##ImeClose", BUTTON_SIZE)) { + state->SendCloseEvent(); + } + } + End(); + + first_render = false; +} + +void ImeUi::DrawInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; + SetCursorPosX(20.0f); + if (first_render) { + SetKeyboardFocusHere(); + } + if (InputTextEx("##ImeInput", nullptr, state->current_text.begin(), ime_param->maxTextLength, + input_size, ImGuiInputTextFlags_CallbackAlways, InputTextCallback, this)) { + state->input_changed = true; + } +} + +int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeUi* ui = static_cast(data->UserData); + ASSERT(ui); + + static int lastCaretPos = -1; + if (lastCaretPos == -1) { + lastCaretPos = data->CursorPos; + } else if (data->CursorPos != lastCaretPos) { + OrbisImeCaretMovementDirection caretDirection = OrbisImeCaretMovementDirection::STILL; + if (data->CursorPos < lastCaretPos) { + caretDirection = OrbisImeCaretMovementDirection::LEFT; + } else if (data->CursorPos > lastCaretPos) { + caretDirection = OrbisImeCaretMovementDirection::RIGHT; + } + + OrbisImeEvent event{}; + event.id = OrbisImeEventId::UPDATE_CARET; + event.param.caretMove = caretDirection; + + lastCaretPos = data->CursorPos; + ui->state->SendEvent(&event); + } + + static std::string lastText; + std::string currentText(data->Buf, data->BufTextLen); + if (currentText != lastText) { + OrbisImeEditText eventParam{}; + eventParam.str = reinterpret_cast(ui->ime_param->work); + eventParam.caretIndex = data->CursorPos; + eventParam.areaNum = 1; + + eventParam.textArea[0].mode = 1; // Edit mode + eventParam.textArea[0].index = data->CursorPos; + eventParam.textArea[0].length = data->BufTextLen; + + if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, eventParam.str, + ui->ime_param->maxTextLength)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); + return 0; + } + + if (!ui->state->ConvertUTF8ToOrbis(data->Buf, data->BufTextLen, + ui->ime_param->inputTextBuffer, + ui->ime_param->maxTextLength)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert Orbis char to UTF-8"); + return 0; + } + + OrbisImeEvent event{}; + event.id = OrbisImeEventId::UPDATE_TEXT; + event.param.text = eventParam; + + lastText = currentText; + ui->state->SendEvent(&event); + } + + return 0; +} + +void ImeUi::Free() { + RemoveLayer(this); +} + +}; // namespace Libraries::Ime \ No newline at end of file diff --git a/src/core/libraries/ime/ime_ui.h b/src/core/libraries/ime/ime_ui.h new file mode 100644 index 000000000..ebd70a7c8 --- /dev/null +++ b/src/core/libraries/ime/ime_ui.h @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "imgui/imgui_layer.h" + +#include "common/cstring.h" +#include "common/types.h" + +#include "ime.h" + +namespace Libraries::Ime { + +class ImeHandler; +class ImeUi; + +class ImeState { + friend class ImeHandler; + friend class ImeUi; + + bool input_changed = false; + + void* work_buffer{}; + + char16_t* text_buffer{}; + + // A character can hold up to 4 bytes in UTF-8 + Common::CString current_text; + + std::queue event_queue; + std::mutex queue_mutex; + +public: + ImeState(const OrbisImeParam* param = nullptr); + ImeState(ImeState&& other) noexcept; + ImeState& operator=(ImeState&& other) noexcept; + + void SendEvent(OrbisImeEvent* event); + void SendEnterEvent(); + void SendCloseEvent(); + +private: + 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 ImeUi : public ImGui::Layer { + ImeState* state{}; + const OrbisImeParam* ime_param{}; + + bool first_render = true; + std::mutex draw_mutex; + +public: + explicit ImeUi(ImeState* state = nullptr, const OrbisImeParam* param = nullptr); + ~ImeUi() override; + ImeUi(const ImeUi& other) = delete; + ImeUi& operator=(ImeUi&& other); + + void Draw() override; + +private: + void Free(); + + void DrawInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); +}; + +}; // namespace Libraries::Ime \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index caa254fd8..10f425c13 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -8,12 +8,12 @@ #include "core/libraries/audio/audioout.h" #include "core/libraries/audio3d/audio3d.h" #include "core/libraries/avplayer/avplayer.h" -#include "core/libraries/dialogs/error_dialog.h" -#include "core/libraries/dialogs/ime_dialog.h" #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" +#include "core/libraries/ime/error_dialog.h" #include "core/libraries/ime/ime.h" +#include "core/libraries/ime/ime_dialog.h" #include "core/libraries/kernel/libkernel.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libpng/pngdec.h"