mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-06-12 13:43:15 +00:00
core/libraries: IME implementation (#1436)
* core/libraries: IME implementation * Update ime_common.h --------- Co-authored-by: georgemoralis <giorgosmrls@gmail.com>
This commit is contained in:
parent
406041b7ad
commit
6e00121eb5
15 changed files with 818 additions and 130 deletions
212
src/core/libraries/ime/error_dialog.cpp
Normal file
212
src/core/libraries/ime/error_dialog.cpp
Normal file
|
@ -0,0 +1,212 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <utility>
|
||||
#include <imgui.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
#include "error_dialog.h"
|
||||
#include "imgui/imgui_layer.h"
|
||||
#include "imgui/imgui_std.h"
|
||||
|
||||
static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f};
|
||||
|
||||
namespace Libraries::ErrorDialog {
|
||||
|
||||
using CommonDialog::Error;
|
||||
using CommonDialog::Result;
|
||||
using CommonDialog::Status;
|
||||
|
||||
class ErrorDialogUi final : public ImGui::Layer {
|
||||
bool first_render{false};
|
||||
|
||||
Status* status{nullptr};
|
||||
std::string err_message{};
|
||||
|
||||
public:
|
||||
explicit ErrorDialogUi(Status* status = nullptr, std::string err_message = "")
|
||||
: status(status), err_message(std::move(err_message)) {
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
}
|
||||
~ErrorDialogUi() override {
|
||||
Finish();
|
||||
}
|
||||
ErrorDialogUi(const ErrorDialogUi& other) = delete;
|
||||
ErrorDialogUi(ErrorDialogUi&& other) noexcept
|
||||
: Layer(other), status(other.status), err_message(std::move(other.err_message)) {
|
||||
other.status = nullptr;
|
||||
}
|
||||
ErrorDialogUi& operator=(ErrorDialogUi other) {
|
||||
using std::swap;
|
||||
swap(status, other.status);
|
||||
swap(err_message, other.err_message);
|
||||
if (status && *status == Status::RUNNING) {
|
||||
first_render = true;
|
||||
AddLayer(this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Finish() {
|
||||
if (status) {
|
||||
*status = Status::FINISHED;
|
||||
}
|
||||
status = nullptr;
|
||||
RemoveLayer(this);
|
||||
}
|
||||
|
||||
void Draw() override {
|
||||
using namespace ImGui;
|
||||
if (status == nullptr || *status != Status::RUNNING) {
|
||||
return;
|
||||
}
|
||||
const auto& io = GetIO();
|
||||
|
||||
const ImVec2 window_size{
|
||||
std::min(io.DisplaySize.x, 500.0f),
|
||||
std::min(io.DisplaySize.y, 300.0f),
|
||||
};
|
||||
|
||||
CentralizeNextWindow();
|
||||
SetNextWindowSize(window_size);
|
||||
SetNextWindowCollapsed(false);
|
||||
if (first_render || !io.NavActive) {
|
||||
SetNextWindowFocus();
|
||||
}
|
||||
KeepNavHighlight();
|
||||
if (Begin("Error Dialog##ErrorDialog", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) {
|
||||
const auto ws = GetWindowSize();
|
||||
|
||||
DrawPrettyBackground();
|
||||
const char* begin = &err_message.front();
|
||||
const char* end = &err_message.back() + 1;
|
||||
SetWindowFontScale(1.3f);
|
||||
DrawCenteredText(begin, end,
|
||||
GetContentRegionAvail() - ImVec2{0.0f, 15.0f + BUTTON_SIZE.y});
|
||||
SetWindowFontScale(1.0f);
|
||||
|
||||
SetCursorPos({
|
||||
ws.x / 2.0f - BUTTON_SIZE.x / 2.0f,
|
||||
ws.y - 10.0f - BUTTON_SIZE.y,
|
||||
});
|
||||
if (Button("OK", BUTTON_SIZE)) {
|
||||
Finish();
|
||||
}
|
||||
if (first_render) {
|
||||
SetItemCurrentNavFocus();
|
||||
}
|
||||
}
|
||||
End();
|
||||
|
||||
first_render = false;
|
||||
}
|
||||
};
|
||||
|
||||
static auto g_status = Status::NONE;
|
||||
static ErrorDialogUi g_dialog_ui;
|
||||
|
||||
struct Param {
|
||||
s32 size;
|
||||
s32 errorCode;
|
||||
OrbisUserServiceUserId userId;
|
||||
s32 _reserved;
|
||||
};
|
||||
|
||||
Error PS4_SYSV_ABI sceErrorDialogClose() {
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||
if (g_status != Status::RUNNING) {
|
||||
return Error::NOT_RUNNING;
|
||||
}
|
||||
g_dialog_ui.Finish();
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Status PS4_SYSV_ABI sceErrorDialogGetStatus() {
|
||||
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceErrorDialogInitialize() {
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||
if (g_status != Status::NONE) {
|
||||
return Error::ALREADY_INITIALIZED;
|
||||
}
|
||||
g_status = Status::INITIALIZED;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceErrorDialogOpen(const Param* param) {
|
||||
if (g_status != Status::INITIALIZED && g_status != Status::FINISHED) {
|
||||
LOG_INFO(Lib_ErrorDialog, "called without initialize");
|
||||
return Error::INVALID_STATE;
|
||||
}
|
||||
if (param == nullptr) {
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called param:(NULL)");
|
||||
return Error::ARG_NULL;
|
||||
}
|
||||
const auto err = static_cast<u32>(param->errorCode);
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called param->errorCode = {:#x}", err);
|
||||
ASSERT(param->size == sizeof(Param));
|
||||
|
||||
const std::string err_message = fmt::format("An error has occurred. \nCode: {:#X}", err);
|
||||
g_status = Status::RUNNING;
|
||||
g_dialog_ui = ErrorDialogUi{&g_status, err_message};
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenDetail() {
|
||||
LOG_ERROR(Lib_ErrorDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenWithReport() {
|
||||
LOG_ERROR(Lib_ErrorDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceErrorDialogTerminate() {
|
||||
LOG_DEBUG(Lib_ErrorDialog, "called");
|
||||
if (g_status == Status::RUNNING) {
|
||||
sceErrorDialogClose();
|
||||
}
|
||||
if (g_status == Status::NONE) {
|
||||
return Error::NOT_INITIALIZED;
|
||||
}
|
||||
g_status = Status::NONE;
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Status PS4_SYSV_ABI sceErrorDialogUpdateStatus() {
|
||||
LOG_TRACE(Lib_ErrorDialog, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("ekXHb1kDBl0", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogClose);
|
||||
LIB_FUNCTION("t2FvHRXzgqk", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogGetStatus);
|
||||
LIB_FUNCTION("I88KChlynSs", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogInitialize);
|
||||
LIB_FUNCTION("M2ZF-ClLhgY", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogOpen);
|
||||
LIB_FUNCTION("jrpnVQfJYgQ", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogOpenDetail);
|
||||
LIB_FUNCTION("wktCiyWoDTI", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogOpenWithReport);
|
||||
LIB_FUNCTION("9XAxK2PMwk8", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogTerminate);
|
||||
LIB_FUNCTION("WWiGuh9XfgQ", "libSceErrorDialog", 1, "libSceErrorDialog", 1, 1,
|
||||
sceErrorDialogUpdateStatus);
|
||||
};
|
||||
|
||||
} // namespace Libraries::ErrorDialog
|
28
src/core/libraries/ime/error_dialog.h
Normal file
28
src/core/libraries/ime/error_dialog.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/system/commondialog.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
namespace Libraries::ErrorDialog {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
struct Param;
|
||||
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogClose();
|
||||
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogGetStatus();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogInitialize();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogOpen(const Param* param);
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenDetail();
|
||||
int PS4_SYSV_ABI sceErrorDialogOpenWithReport();
|
||||
CommonDialog::Error PS4_SYSV_ABI sceErrorDialogTerminate();
|
||||
CommonDialog::Status PS4_SYSV_ABI sceErrorDialogUpdateStatus();
|
||||
|
||||
void RegisterlibSceErrorDialog(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::ErrorDialog
|
|
@ -1,14 +1,108 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <queue>
|
||||
#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<OrbisImeEvent> 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<Core::Linker>::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<ImeHandler> 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<ImeHandler>(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<ImeHandler>(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() {
|
||||
|
|
|
@ -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();
|
||||
|
|
184
src/core/libraries/ime/ime_common.h
Normal file
184
src/core/libraries/ime/ime_common.h
Normal file
|
@ -0,0 +1,184 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
#include <sys/types.h>
|
||||
#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);
|
277
src/core/libraries/ime/ime_dialog.cpp
Normal file
277
src/core/libraries/ime/ime_dialog.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <magic_enum.hpp>
|
||||
#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<float, 2> MAX_X_POSITIONS = {3840.0f, 1920.0f};
|
||||
static constexpr std::array<float, 2> MAX_Y_POSITIONS = {2160.0f, 1080.0f};
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelSize() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() {
|
||||
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;
|
||||
}
|
||||
|
||||
OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() {
|
||||
if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) {
|
||||
g_ime_dlg_state.CallTextFilter();
|
||||
}
|
||||
|
||||
return g_ime_dlg_status;
|
||||
}
|
||||
|
||||
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() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal2() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogInitInternal3() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceImeDialogSetPanelPosition() {
|
||||
LOG_ERROR(Lib_ImeDialog, "(STUBBED) called");
|
||||
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) {
|
||||
LIB_FUNCTION("oBmw4xrmfKs", "libSceImeDialog", 1, "libSceImeDialog", 1, 1, sceImeDialogAbort);
|
||||
LIB_FUNCTION("bX4H+sxPI-o", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogForceClose);
|
||||
LIB_FUNCTION("UFcyYDf+e88", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogForTestFunction);
|
||||
LIB_FUNCTION("fy6ntM25pEc", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetCurrentStarState);
|
||||
LIB_FUNCTION("8jqzzPioYl8", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetPanelPositionAndForm);
|
||||
LIB_FUNCTION("wqsJvRXwl58", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetPanelSize);
|
||||
LIB_FUNCTION("CRD+jSErEJQ", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetPanelSizeExtended);
|
||||
LIB_FUNCTION("x01jxu+vxlc", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetResult);
|
||||
LIB_FUNCTION("IADmD4tScBY", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogGetStatus);
|
||||
LIB_FUNCTION("NUeBrN7hzf0", "libSceImeDialog", 1, "libSceImeDialog", 1, 1, sceImeDialogInit);
|
||||
LIB_FUNCTION("KR6QDasuKco", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogInitInternal);
|
||||
LIB_FUNCTION("oe92cnJQ9HE", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogInitInternal2);
|
||||
LIB_FUNCTION("IoKIpNf9EK0", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogInitInternal3);
|
||||
LIB_FUNCTION("-2WqB87KKGg", "libSceImeDialog", 1, "libSceImeDialog", 1, 1,
|
||||
sceImeDialogSetPanelPosition);
|
||||
LIB_FUNCTION("gyTyVn+bXMw", "libSceImeDialog", 1, "libSceImeDialog", 1, 1, sceImeDialogTerm);
|
||||
};
|
||||
|
||||
} // namespace Libraries::ImeDialog
|
170
src/core/libraries/ime/ime_dialog.h
Normal file
170
src/core/libraries/ime/ime_dialog.h
Normal file
|
@ -0,0 +1,170 @@
|
|||
// 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 "ime_common.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
}
|
||||
|
||||
namespace Libraries::ImeDialog {
|
||||
|
||||
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 class OrbisImeDialogStatus : u32 {
|
||||
NONE = 0,
|
||||
RUNNING = 1,
|
||||
FINISHED = 2,
|
||||
};
|
||||
|
||||
enum class OrbisImeDialogEndStatus : u32 {
|
||||
OK = 0,
|
||||
USER_CANCELED = 1,
|
||||
ABORTED = 2,
|
||||
};
|
||||
|
||||
enum class OrbisImeDialogOption : u32 {
|
||||
DEFAULT = 0,
|
||||
MULTILINE = 1,
|
||||
NO_AUTO_CORRECTION = 2,
|
||||
NO_AUTO_COMPLETION = 4,
|
||||
// TODO: Document missing options
|
||||
LARGE_RESOLUTION = 1024,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption)
|
||||
|
||||
enum class OrbisImePanelPriority : u32 {
|
||||
DEFAULT = 0,
|
||||
ALPHABET = 1,
|
||||
SYMBOL = 2,
|
||||
ACCENT = 3,
|
||||
};
|
||||
|
||||
struct OrbisImeColor {
|
||||
u8 r;
|
||||
u8 g;
|
||||
u8 b;
|
||||
u8 a;
|
||||
};
|
||||
|
||||
struct OrbisImeDialogResult {
|
||||
OrbisImeDialogEndStatus endstatus;
|
||||
s32 reserved[12];
|
||||
};
|
||||
|
||||
struct OrbisImeKeycode {
|
||||
u16 keycode;
|
||||
char16_t character;
|
||||
u32 status;
|
||||
OrbisImeKeyboardType type;
|
||||
s32 userId;
|
||||
u32 resourceId;
|
||||
u64 timestamp;
|
||||
};
|
||||
|
||||
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; // OrbisImeDialogOptionExtended
|
||||
OrbisImeColor colorBase;
|
||||
OrbisImeColor colorLine;
|
||||
OrbisImeColor colorTextField;
|
||||
OrbisImeColor colorPreedit;
|
||||
OrbisImeColor colorButtonDefault;
|
||||
OrbisImeColor colorButtonFunction;
|
||||
OrbisImeColor colorButtonSymbol;
|
||||
OrbisImeColor colorText;
|
||||
OrbisImeColor colorSpecial;
|
||||
OrbisImePanelPriority priority;
|
||||
char* additionalDictionaryPath;
|
||||
OrbisImeExtKeyboardFilter extKeyboardFilter;
|
||||
uint32_t disableDevice;
|
||||
uint32_t extKeyboardMode;
|
||||
int8_t reserved[60];
|
||||
};
|
||||
|
||||
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();
|
||||
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();
|
||||
Error PS4_SYSV_ABI sceImeDialogTerm();
|
||||
|
||||
void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym);
|
||||
} // namespace Libraries::ImeDialog
|
390
src/core/libraries/ime/ime_dialog_ui.cpp
Normal file
390
src/core/libraries/ime/ime_dialog_ui.cpp
Normal file
|
@ -0,0 +1,390 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cwchar>
|
||||
#include <string>
|
||||
#include <imgui.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.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"
|
||||
|
||||
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<char16_t>::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<char16_t>::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<char16_t>::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<Core::Linker>::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<Core::Linker>::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<const ImWchar*>(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<ImWchar*>(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};
|
||||
}
|
||||
|
||||
CentralizeNextWindow();
|
||||
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>(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<ImeDialogUi*>(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<char*>(&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
|
84
src/core/libraries/ime/ime_dialog_ui.h
Normal file
84
src/core/libraries/ime/ime_dialog_ui.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <imgui.h>
|
||||
#include "common/cstring.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/ime/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<char> title;
|
||||
std::vector<char> placeholder;
|
||||
|
||||
// A character can hold up to 4 bytes in UTF-8
|
||||
Common::CString<ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4> 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
|
252
src/core/libraries/ime/ime_ui.cpp
Normal file
252
src/core/libraries/ime/ime_ui.cpp
Normal file
|
@ -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<char16_t>::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<char16_t*>(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<const ImWchar*>(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<ImWchar*>(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<ImeUi*>(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<char16_t*>(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
|
76
src/core/libraries/ime/ime_ui.h
Normal file
76
src/core/libraries/ime/ime_ui.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <imgui.h>
|
||||
#include <queue>
|
||||
#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<ORBIS_IME_MAX_TEXT_LENGTH * 4> current_text;
|
||||
|
||||
std::queue<OrbisImeEvent> 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
|
Loading…
Add table
Add a link
Reference in a new issue