Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions core/libs/prx/libSceIme/Export.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <atomic>
#include <algorithm>
#include <cstdint>
#include <cstddef>
Expand Down Expand Up @@ -67,6 +68,7 @@ std::mutex g_keyboardMutex;
std::set<int32_t> g_openKeyboards;
std::mutex g_imeMutex;
std::optional<Session> g_session;
std::atomic<ImeHostTextSink> g_textSink{nullptr};

void PanelSize(const Param& param, uint32_t* width, uint32_t* height) {
const uint32_t scale = (param.option & OptionUseOver2K) != 0 ? 2 : 1;
Expand Down Expand Up @@ -284,13 +286,31 @@ int ValidateOpen(const Param* param, const ExtendedParam* extended) {
extern "C" {

void ImePublishInput_nid_postfix(const ImeHostInput& input) {
std::lock_guard lock(g_imeMutex);
if (g_session) Apply(*g_session, input);
{
std::lock_guard lock(g_imeMutex);
if (g_session) {
Apply(*g_session, input);
return;
}
}
const ImeHostTextSink sink = g_textSink.load();
if (sink == nullptr) return;
std::u16string units;
if (input.kind == ImeHostInputKind::Text) {
std::size_t size = 0;
while (size < sizeof(input.text) && input.text[size] != '\0') ++size;
units = DecodeUtf8(input.text, size);
}
sink(input.kind, units.data(), units.size());
}

bool ImeCapturesKeyboard_nid_postfix() {
std::lock_guard lock(g_imeMutex);
return g_session.has_value();
return g_session.has_value() || g_textSink.load() != nullptr;
}

void ImeSetHostTextSink_nid_no_patch(ImeHostTextSink sink) {
g_textSink = sink;
}

int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended) {
Expand Down
5 changes: 5 additions & 0 deletions core/libs/prx/libSceIme/include/ImeInput.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMEINPUT_HPP
#define CORE_LIBS_PRX_LIBSCEIME_INCLUDE_IMEINPUT_HPP

#include <cstddef>
#include <cstdint>

enum class ImeHostInputKind : std::uint32_t {
Expand All @@ -23,4 +24,8 @@ struct ImeHostInput {
extern "C" void ImePublishInput_nid_postfix(const ImeHostInput& input);
extern "C" bool ImeCapturesKeyboard_nid_postfix();

using ImeHostTextSink = void (*)(ImeHostInputKind kind, const char16_t* text, std::size_t length);

extern "C" void ImeSetHostTextSink_nid_no_patch(ImeHostTextSink sink);

#endif
17 changes: 17 additions & 0 deletions core/libs/prx/libSceImeDialog/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
add_library(libSceImeDialog SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp)
target_include_directories(libSceImeDialog PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(libSceImeDialog PRIVATE libSceIme)
configure_windows_unwind(libSceImeDialog)

if(BUILD_TESTING)
add_test_executable(guest_ime_dialog_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeDialog.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/ImeTextInput.cpp)
target_include_directories(guest_ime_dialog_tests PRIVATE ${LIBS_INCLUDE_DIR} ${ANYPS5_SDL2_INCLUDE_DIR})
target_link_libraries(guest_ime_dialog_tests PRIVATE libSceImeDialog libSceIme libc)
configure_windows_unwind(guest_ime_dialog_tests)
add_test(NAME guest_ime_dialog COMMAND guest_ime_dialog_tests)
if(WIN32)
set_tests_properties(guest_ime_dialog PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceImeDialog>")
endif()
endif()
153 changes: 117 additions & 36 deletions core/libs/prx/libSceImeDialog/Export.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,117 @@
#include <atomic>
#include <algorithm>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <mutex>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceIme/include/ImeInput.hpp"

namespace {

constexpr int StatusNone = 0;
constexpr int StatusRunning = 1;
constexpr int StatusFinished = 2;
constexpr std::uint32_t EndStatusOk = 0;
constexpr std::uint32_t EndStatusUserCanceled = 1;
constexpr std::uint32_t EndStatusAborted = 2;
constexpr std::uint32_t MaxTextLength = 2048;
constexpr int ErrorBusy = static_cast<int>(0x80bc0001u);
constexpr int ErrorNotOpened = static_cast<int>(0x80bc0002u);
constexpr int ErrorInvalidMaxTextLength = static_cast<int>(0x80bc0016u);
constexpr int ErrorInvalidInputTextBuffer = static_cast<int>(0x80bc0017u);
constexpr int ErrorInvalidAddress = static_cast<int>(0x80bc0031u);

std::atomic<int> status{StatusNone};
std::atomic<int> polls{0};
std::atomic<std::uint32_t> endStatus{EndStatusOk};
struct Dialog {
int status = StatusNone;
std::uint32_t endStatus = EndStatusOk;
char16_t* buffer = nullptr;
std::uint32_t maxLength = 0;
std::u16string text;
std::size_t caret = 0;
};

std::mutex g_mutex;
Dialog g_dialog;

bool IsHighSurrogate(char16_t unit) { return unit >= 0xd800 && unit <= 0xdbff; }
bool IsLowSurrogate(char16_t unit) { return unit >= 0xdc00 && unit <= 0xdfff; }

std::size_t UnitsBefore(const Dialog& dialog) {
if (dialog.caret == 0) return 0;
return dialog.caret >= 2 && IsLowSurrogate(dialog.text[dialog.caret - 1]) && IsHighSurrogate(dialog.text[dialog.caret - 2]) ? 2 : 1;
}

std::size_t UnitsAfter(const Dialog& dialog) {
if (dialog.caret >= dialog.text.size()) return 0;
return dialog.caret + 1 < dialog.text.size() && IsHighSurrogate(dialog.text[dialog.caret]) && IsLowSurrogate(dialog.text[dialog.caret + 1]) ? 2 : 1;
}

void Finish(std::uint32_t endStatus) {
g_dialog.endStatus = endStatus;
g_dialog.status = StatusFinished;
ImeSetHostTextSink_nid_no_patch(nullptr);
}

void Insert(Dialog& dialog, const char16_t* units, std::size_t length) {
std::u16string accepted;
for (std::size_t i = 0; i < length; ++i) {
if (units[i] != u'\n' && units[i] != u'\r') accepted.push_back(units[i]);
}
std::size_t count = std::min<std::size_t>(accepted.size(), dialog.maxLength - dialog.text.size());
if (count > 0 && count < accepted.size() && IsHighSurrogate(accepted[count - 1])) --count;
dialog.text.insert(dialog.caret, accepted, 0, count);
dialog.caret += count;
}

void Receive(ImeHostInputKind kind, const char16_t* units, std::size_t length) {
std::lock_guard lock(g_mutex);
Dialog& dialog = g_dialog;
if (dialog.status != StatusRunning) return;
switch (kind) {
case ImeHostInputKind::Text:
Insert(dialog, units, length);
return;
case ImeHostInputKind::Backspace: {
const std::size_t count = UnitsBefore(dialog);
dialog.caret -= count;
dialog.text.erase(dialog.caret, count);
return;
}
case ImeHostInputKind::Delete:
dialog.text.erase(dialog.caret, UnitsAfter(dialog));
return;
case ImeHostInputKind::Left:
dialog.caret -= UnitsBefore(dialog);
return;
case ImeHostInputKind::Right:
dialog.caret += UnitsAfter(dialog);
return;
case ImeHostInputKind::Home:
dialog.caret = 0;
return;
case ImeHostInputKind::End:
dialog.caret = dialog.text.size();
return;
case ImeHostInputKind::Enter:
std::copy(dialog.text.begin(), dialog.text.end(), dialog.buffer);
dialog.buffer[dialog.text.size()] = u'\0';
Finish(EndStatusOk);
return;
case ImeHostInputKind::Escape:
Finish(EndStatusUserCanceled);
return;
}
}

}

extern "C" {

int APS5_VABI sceImeDialogAbort(void) {
if (status.load() != StatusRunning) return ErrorNotOpened;
endStatus = EndStatusAborted;
status = StatusFinished;
std::lock_guard lock(g_mutex);
if (g_dialog.status != StatusRunning) return ErrorNotOpened;
Finish(EndStatusAborted);
return 0;
}

Expand All @@ -50,47 +136,42 @@ int APS5_VABI sceImeDialogGetPanelSizeExtended(const Param* param, const Extende
}

int APS5_VABI sceImeDialogGetResult(Result* result) {
std::fprintf(stderr, "[ime] GetResult (status %d)\n", status.load());
if (result == nullptr || status.load() != StatusFinished) return ErrorNotOpened;
std::lock_guard lock(g_mutex);
if (result == nullptr || g_dialog.status != StatusFinished) return ErrorNotOpened;
*result = Result{};
result->endstatus = endStatus.load();
result->endstatus = g_dialog.endStatus;
return 0;
}

int APS5_VABI sceImeDialogGetStatus(void) {
if (status.load() == StatusRunning && ++polls > 1) status = StatusFinished;
static std::atomic<int> traced{0};
const int count = traced.fetch_add(1);
if (count < 8 || count % 2000 == 0) std::fprintf(stderr, "[ime] GetStatus #%d -> %d\n", count, status.load());
return status.load();
std::lock_guard lock(g_mutex);
return g_dialog.status;
}

int APS5_VABI sceImeDialogInit(const Param* param, const ExtendedParam* extended) {
(void)extended;
static std::atomic<int> reported{0};
bool filled = false;
if (param != nullptr && param->input_text_buffer != nullptr && param->max_text_length != 0 && param->input_text_buffer[0] == u'\0') {
static constexpr char16_t defaultText[] = u"Slayer";
std::uint32_t length = 0;
while (defaultText[length] != u'\0' && length < param->max_text_length) {
param->input_text_buffer[length] = defaultText[length];
++length;
}
param->input_text_buffer[length] = u'\0';
filled = true;
}
std::fprintf(stderr, "[ime] Init #%d\n", reported.load());
if (reported.fetch_add(1) == 0) std::fprintf(stderr, "[ime] dialog requested (max text length %u): finishing at once with %s\n", param != nullptr ? param->max_text_length : 0u, filled ? "a default text" : "the text unchanged");
polls = 0;
endStatus = EndStatusOk;
status = StatusRunning;
std::lock_guard lock(g_mutex);
if (g_dialog.status != StatusNone) return ErrorBusy;
if (param == nullptr) return ErrorInvalidAddress;
if (param->max_text_length == 0 || param->max_text_length > MaxTextLength) return ErrorInvalidMaxTextLength;
if (param->input_text_buffer == nullptr) return ErrorInvalidInputTextBuffer;
g_dialog = Dialog{};
g_dialog.buffer = param->input_text_buffer;
g_dialog.maxLength = param->max_text_length;
std::size_t length = 0;
while (length < param->max_text_length && param->input_text_buffer[length] != u'\0') ++length;
g_dialog.text.assign(param->input_text_buffer, length);
g_dialog.caret = length;
g_dialog.status = StatusRunning;
ImeSetHostTextSink_nid_no_patch(&Receive);
return 0;
}

int APS5_VABI sceImeDialogTerm(void) {
std::fprintf(stderr, "[ime] Term (status %d)\n", status.load());
if (status.load() == StatusNone) return ErrorNotOpened;
status = StatusNone;
std::lock_guard lock(g_mutex);
if (g_dialog.status == StatusNone) return ErrorNotOpened;
ImeSetHostTextSink_nid_no_patch(nullptr);
g_dialog = Dialog{};
return 0;
}

Expand Down
Loading
Loading