mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-18 23:07:07 -05:00
Merge pull request #6504 from 0xRickx/feat/libsceimedialog-host-keyboard
feat(libSceImeDialog): take the dialog's text from the host keyboard
This commit is contained in:
6 files changed
+329
-39
No files matched your search
@@ -1,3 +1,4 @@
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -1,9 +1,11 @@
|
||||
#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 {
|
||||
|
||||
@@ -11,21 +13,105 @@ 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user