Files
AnyPS5/core/libs/tests/GuestImeOpen.cpp
T

351 lines
14 KiB
C++

#include "SceTypes.hpp"
#include "prx/libSceIme/include/ImeInput.hpp"
#include "prx/libSceVideoOut/include/ImeTextInput.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <string>
#include <vector>
extern "C" {
int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended);
int APS5_VABI sceImeClose_nid_postfix(void);
int APS5_VABI sceImeUpdate(EventHandler handler);
int APS5_VABI sceImeSetText(const char16_t* text, uint32_t length);
int APS5_VABI sceImeSetCaret(const Caret* caret);
int APS5_VABI sceImeGetPanelPositionAndForm(PositionAndForm* form);
void APS5_VABI sceImeParamInit(Param* param);
}
namespace {
constexpr int Busy = static_cast<int>(0x80bc0001u);
constexpr int NotOpened = static_cast<int>(0x80bc0002u);
constexpr int InvalidText = static_cast<int>(0x80bc0006u);
constexpr int InvalidUserId = static_cast<int>(0x80bc0010u);
constexpr int InvalidType = static_cast<int>(0x80bc0011u);
constexpr int InvalidEnterLabel = static_cast<int>(0x80bc0013u);
constexpr int InvalidInputMethod = static_cast<int>(0x80bc0014u);
constexpr int InvalidOption = static_cast<int>(0x80bc0015u);
constexpr int InvalidMaxTextLength = static_cast<int>(0x80bc0016u);
constexpr int InvalidInputTextBuffer = static_cast<int>(0x80bc0017u);
constexpr int InvalidPosX = static_cast<int>(0x80bc0018u);
constexpr int InvalidPosY = static_cast<int>(0x80bc0019u);
constexpr int InvalidHorizontalAlignment = static_cast<int>(0x80bc001au);
constexpr int InvalidVerticalAlignment = static_cast<int>(0x80bc001bu);
constexpr int InvalidExtended = static_cast<int>(0x80bc001cu);
constexpr int InvalidWork = static_cast<int>(0x80bc0020u);
constexpr int InvalidHandler = static_cast<int>(0x80bc0022u);
constexpr int InvalidParam = static_cast<int>(0x80bc0030u);
constexpr int InvalidAddress = static_cast<int>(0x80bc0031u);
constexpr int InvalidReserved = static_cast<int>(0x80bc0032u);
constexpr uint32_t EventOpen = 0;
constexpr uint32_t EventUpdateText = 1;
constexpr uint32_t EventUpdateCaret = 2;
constexpr uint32_t EventPressClose = 4;
constexpr uint32_t EventPressEnter = 5;
struct Recorded {
uint32_t id;
void* arg;
ImeEventParam param;
std::u16string text;
};
std::vector<Recorded> g_events;
bool g_closeOnPressClose = false;
int g_reentrantClose = 1;
void APS5_VABI Record(void* arg, const ImeEvent* event) {
Recorded recorded{event->id, arg, event->param, {}};
if (event->id == EventUpdateText || event->id == EventPressEnter || event->id == EventPressClose) {
recorded.text = event->param.text.str;
}
g_events.push_back(recorded);
if (g_closeOnPressClose && event->id == EventPressClose) g_reentrantClose = sceImeClose_nid_postfix();
}
void APS5_VABI Other(void*, const ImeEvent*) {
std::abort();
}
void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}
struct Buffers {
char16_t text[2049] = {};
alignas(16) unsigned char work[0x5000] = {};
int arg = 0;
};
Param MakeParam(Buffers& buffers, uint32_t maxLength) {
Param param;
sceImeParamInit(&param);
param.user_id = 1;
param.max_text_length = maxLength;
param.input_text_buffer = buffers.text;
param.posx = 100.0f;
param.posy = 200.0f;
param.work = buffers.work;
param.arg = &buffers.arg;
param.handler = Record;
return param;
}
std::vector<Recorded> Drain() {
g_events.clear();
Require(sceImeUpdate(Record) == 0, "update failed");
return g_events;
}
void Publish(ImeHostInputKind kind, const char* text = "") {
ImeHostInput input{};
input.kind = kind;
std::strncpy(input.text, text, sizeof(input.text) - 1);
ImePublishInput_nid_postfix(input);
}
void RequireText(const Recorded& event, uint32_t id, const std::u16string& text, uint32_t caret, uint32_t index, int32_t delta) {
Require(event.id == id, "event id");
Require(event.text == text, "event text");
Require(event.param.text.caret_index == caret, "event caret");
Require(event.param.text.area_num == 1, "event area count");
Require(event.param.text.text_area[0].mode == TextAreaMode::Edit, "event area mode");
Require(event.param.text.text_area[0].index == index, "event edit index");
Require(event.param.text.text_area[0].length == delta, "event edit length");
}
void RequireCaretSteps(const std::vector<Recorded>& events, uint32_t direction, std::size_t count) {
Require(events.size() == count, "caret step count");
for (const auto& event : events) {
Require(event.id == EventUpdateCaret && event.param.caret_move == direction, "caret step");
}
}
void CheckValidation() {
Buffers buffers;
Require(sceImeOpen_nid_postfix(nullptr, nullptr) == InvalidAddress, "null param");
auto expect = [&](auto change, int error, const char* message) {
Param param = MakeParam(buffers, 16);
change(param);
Require(sceImeOpen_nid_postfix(&param, nullptr) == error, message);
};
expect([](Param& p) { p.user_id = -1; }, InvalidUserId, "invalid user");
expect([](Param& p) { p.type = 5; }, InvalidType, "invalid type");
expect([](Param& p) { p.enter_label = 4; }, InvalidEnterLabel, "invalid enter label");
expect([](Param& p) { p.input_method = 1; }, InvalidInputMethod, "invalid input method");
expect([](Param& p) { p.option = 0x80000000u; }, InvalidOption, "invalid option");
expect([](Param& p) { p.max_text_length = 0; }, InvalidMaxTextLength, "zero length");
expect([](Param& p) { p.max_text_length = 2049; }, InvalidMaxTextLength, "long length");
expect([](Param& p) { p.input_text_buffer = nullptr; }, InvalidInputTextBuffer, "null buffer");
expect([](Param& p) { p.posx = -1.0f; }, InvalidPosX, "negative x");
expect([](Param& p) { p.posx = 1920.0f; }, InvalidPosX, "x past 1080p");
expect([](Param& p) { p.posy = 1080.0f; }, InvalidPosY, "y past 1080p");
expect([](Param& p) { p.horizontal_alignment = 3; }, InvalidHorizontalAlignment, "horizontal alignment");
expect([](Param& p) { p.vertical_alignment = 3; }, InvalidVerticalAlignment, "vertical alignment");
expect([](Param& p) { p.work = nullptr; }, InvalidWork, "null work");
expect([&](Param& p) { p.work = buffers.work + 2; }, InvalidWork, "misaligned work");
expect([](Param& p) { p.handler = nullptr; }, InvalidHandler, "null handler");
expect([](Param& p) { p.reserved[7] = 1; }, InvalidReserved, "reserved bytes");
buffers.text[0] = 0xdc00;
expect([](Param&) {}, InvalidText, "lone low surrogate");
buffers.text[0] = u'\n';
expect([](Param&) {}, InvalidText, "newline without multiline");
buffers.text[0] = 0;
Param param = MakeParam(buffers, 16);
ExtendedParam extended{};
extended.priority = 4;
Require(sceImeOpen_nid_postfix(&param, &extended) == InvalidExtended, "extended priority");
extended.priority = 0;
extended.reserved[59] = 1;
Require(sceImeOpen_nid_postfix(&param, &extended) == InvalidExtended, "extended reserved");
param.posx = 3000.0f;
param.option = 0x4000;
Require(sceImeOpen_nid_postfix(&param, nullptr) == 0, "over-2K coordinates");
Require(sceImeClose_nid_postfix() == 0, "close after over-2K open");
param = MakeParam(buffers, 16);
param.filter = reinterpret_cast<ImeTextFilter>(&Record);
bool threw = false;
try {
sceImeOpen_nid_postfix(&param, nullptr);
} catch (const std::exception&) {
threw = true;
}
Require(threw, "text filter must throw");
Require(!ImeCapturesKeyboard_nid_postfix(), "failed open left a session");
}
void CheckEditing() {
Buffers buffers;
std::memcpy(buffers.text, u"ab", sizeof(u"ab"));
Param param = MakeParam(buffers, 8);
Require(!ImeCapturesKeyboard_nid_postfix(), "keyboard captured before open");
Require(sceImeUpdate(Record) == 0 && g_events.empty(), "update before open");
Require(sceImeOpen_nid_postfix(&param, nullptr) == 0, "open");
Require(sceImeOpen_nid_postfix(&param, nullptr) == Busy, "second open");
Require(ImeCapturesKeyboard_nid_postfix(), "keyboard not captured");
Require(sceImeUpdate(Other) == 0, "update with another handler");
auto events = Drain();
Require(events.size() == 1 && events[0].id == EventOpen, "open event");
Require(events[0].arg == &buffers.arg, "handler argument");
Require(events[0].param.rect.x == 100.0f && events[0].param.rect.y == 200.0f, "open position");
Require(events[0].param.rect.width == 793 && events[0].param.rect.height == 408, "open size");
Require(Drain().empty(), "events delivered twice");
PositionAndForm form{};
Require(sceImeGetPanelPositionAndForm(nullptr) == InvalidAddress, "null panel form");
Require(sceImeGetPanelPositionAndForm(&form) == 0, "panel form");
Require(form.posx == 100.0f && form.posy == 200.0f && form.width == 793 && form.height == 408, "panel form values");
Publish(ImeHostInputKind::Text, "c");
events = Drain();
Require(events.size() == 1, "one text event");
RequireText(events[0], EventUpdateText, u"abc", 3, 2, 1);
Require(std::u16string(buffers.text) == u"abc", "input buffer");
Require(std::u16string(reinterpret_cast<const char16_t*>(buffers.work)) == u"abc", "work copy");
Publish(ImeHostInputKind::Text, "\xc3\xa9\xf0\x9f\x98\x80");
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abcé\U0001F600", 6, 3, 3);
Publish(ImeHostInputKind::Left);
RequireCaretSteps(Drain(), 1, 2);
Publish(ImeHostInputKind::Backspace);
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abc\U0001F600", 3, 3, -1);
Publish(ImeHostInputKind::Delete);
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abc", 3, 3, -2);
Publish(ImeHostInputKind::Delete);
Require(Drain().empty(), "delete at the end");
Publish(ImeHostInputKind::Home);
RequireCaretSteps(Drain(), 1, 3);
Publish(ImeHostInputKind::Left);
Require(Drain().empty(), "left at the start");
Publish(ImeHostInputKind::Backspace);
Require(Drain().empty(), "backspace at the start");
Publish(ImeHostInputKind::Right);
RequireCaretSteps(Drain(), 2, 1);
Publish(ImeHostInputKind::End);
RequireCaretSteps(Drain(), 2, 2);
Publish(ImeHostInputKind::Text, "x\ny");
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abcxy", 5, 3, 2);
Publish(ImeHostInputKind::Text, "12\xf0\x9f\x98\x80");
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abcxy12", 7, 5, 2);
Publish(ImeHostInputKind::Text, "\xf0\x9f\x98\x80");
Require(Drain().empty(), "a pair past the limit must not be split");
Publish(ImeHostInputKind::Text, "z");
events = Drain();
RequireText(events.at(0), EventUpdateText, u"abcxy12z", 8, 7, 1);
Publish(ImeHostInputKind::Text, "q");
Require(Drain().empty(), "text past the limit");
Require(sceImeSetText(nullptr, 1) == InvalidAddress, "null text");
Require(sceImeSetText(u"a\nb", 3) == InvalidText, "newline in single-line text");
Require(sceImeSetText(u"hello", 5) == 0, "set text");
Require(std::u16string(buffers.text) == u"hello", "set text buffer");
Require(Drain().empty(), "set text queued an event");
Caret caret{};
caret.index = 6;
Require(sceImeSetCaret(nullptr) == InvalidAddress, "null caret");
Require(sceImeSetCaret(&caret) == InvalidParam, "caret past the text");
caret.index = 1;
Require(sceImeSetCaret(&caret) == 0, "set caret");
Publish(ImeHostInputKind::Text, "X");
events = Drain();
RequireText(events.at(0), EventUpdateText, u"hXello", 2, 1, 1);
Publish(ImeHostInputKind::Enter);
events = Drain();
RequireText(events.at(0), EventPressEnter, u"hXello", 2, 2, 0);
g_closeOnPressClose = true;
Publish(ImeHostInputKind::Escape);
Publish(ImeHostInputKind::Enter);
events = Drain();
g_closeOnPressClose = false;
Require(events.size() == 1, "events after a close inside the handler");
RequireText(events[0], EventPressClose, u"hXello", 2, 2, 0);
Require(g_reentrantClose == 0, "close inside the handler");
Require(!ImeCapturesKeyboard_nid_postfix(), "keyboard captured after close");
Require(sceImeClose_nid_postfix() == NotOpened, "second close");
Publish(ImeHostInputKind::Text, "ignored");
Require(std::u16string(buffers.text) == u"hXello", "input after close");
Require(sceImeSetText(u"a", 1) == NotOpened, "set text after close");
}
void CheckMultiline() {
Buffers buffers;
Param param = MakeParam(buffers, 16);
param.option = 1;
Require(sceImeOpen_nid_postfix(&param, nullptr) == 0, "multiline open");
Drain();
Publish(ImeHostInputKind::Text, "a");
Publish(ImeHostInputKind::Enter);
Publish(ImeHostInputKind::Text, "b\nc");
const auto events = Drain();
Require(events.size() == 3, "multiline events");
RequireText(events[1], EventUpdateText, u"a\nb\nc", 2, 1, 1);
RequireText(events[2], EventUpdateText, u"a\nb\nc", 5, 2, 3);
Require(sceImeSetText(u"x\ny", 3) == 0, "multiline set text");
Require(sceImeClose_nid_postfix() == 0, "multiline close");
}
void CheckHostEvents() {
Buffers buffers;
Param param = MakeParam(buffers, 16);
Require(sceImeOpen_nid_postfix(&param, nullptr) == 0, "host events open");
Drain();
ImeTextInput input;
SDL_Event event{};
event.type = SDL_TEXTINPUT;
event.text.windowID = 7;
std::strcpy(event.text.text, "hi");
input.HandleEvent(event, 7);
event.text.windowID = 8;
input.HandleEvent(event, 7);
event = {};
event.type = SDL_KEYDOWN;
event.key.windowID = 7;
event.key.keysym.scancode = SDL_SCANCODE_BACKSPACE;
event.key.repeat = 1;
input.HandleEvent(event, 7);
event.key.keysym.scancode = SDL_SCANCODE_RETURN;
input.HandleEvent(event, 7);
event.key.repeat = 0;
input.HandleEvent(event, 7);
event.key.keysym.scancode = SDL_SCANCODE_A;
input.HandleEvent(event, 7);
event.type = SDL_KEYUP;
event.key.keysym.scancode = SDL_SCANCODE_ESCAPE;
input.HandleEvent(event, 7);
const auto events = Drain();
Require(events.size() == 3, "host event count");
RequireText(events[0], EventUpdateText, u"h", 2, 0, 2);
RequireText(events[1], EventUpdateText, u"h", 1, 1, -1);
Require(events[2].id == EventPressEnter, "host enter");
Require(sceImeClose_nid_postfix() == 0, "host events close");
}
}
int main() {
CheckValidation();
CheckEditing();
CheckMultiline();
CheckHostEvents();
}