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:
Adrià authored and GitHub committed 2026-10-19 04:39:46 +02:00
commit 8a526d4495
6 files changed
+329 -39

No files matched your search

+23 -3
View File
@@ -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()
+117 -36
View File
@@ -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;
}