mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-11-04 13:07:32 +00:00
Drive ImeTextInput with injected SDL events and an injected clipboard provider, no window and no real input method: text editing events become the shown preedit and a commit ends it; Ctrl+V, Shift+Insert and Ctrl+Shift+V paste while a dialog or session captures the keyboard; repeats, Alt, other modifiers, key releases, other windows and Ctrl+C, Ctrl+X read nothing; nothing reads the clipboard while no dialog captures the keyboard or no provider is installed; a clipboard of megabytes stops at the dialog limit.
252 lines
8.7 KiB
C++
252 lines
8.7 KiB
C++
#include "prx/libSceImeDialog/include/Api.hpp"
|
|
#include "prx/libSceIme/include/Api.hpp"
|
|
#include "SceTypes.hpp"
|
|
#include "prx/libSceIme/include/ImeInput.hpp"
|
|
#include "prx/libSceIme/include/ImeKeyboardView.hpp"
|
|
#include "prx/libSceVideoOut/include/ImeTextInput.hpp"
|
|
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
namespace {
|
|
|
|
constexpr unsigned WindowId = 7;
|
|
constexpr int StatusRunning = 1;
|
|
#ifdef __APPLE__
|
|
constexpr unsigned PasteModifier = KMOD_LGUI;
|
|
constexpr unsigned WrongModifier = KMOD_LCTRL;
|
|
#else
|
|
constexpr unsigned PasteModifier = KMOD_LCTRL;
|
|
constexpr unsigned WrongModifier = KMOD_LGUI;
|
|
#endif
|
|
|
|
std::string g_clipboard;
|
|
bool g_clipboardAvailable = true;
|
|
int g_reads = 0;
|
|
ImeTextInput g_window;
|
|
|
|
bool ReadClipboard(std::string& text) {
|
|
++g_reads;
|
|
if (!g_clipboardAvailable) return false;
|
|
text = g_clipboard;
|
|
return true;
|
|
}
|
|
|
|
void APS5_VABI Ignore(void*, const ImeEvent*) {}
|
|
|
|
void Require(bool value, const char* message) {
|
|
if (value) return;
|
|
std::fprintf(stderr, "%s\n", message);
|
|
std::abort();
|
|
}
|
|
|
|
void Edit(const char* utf8, std::int32_t start, std::int32_t length, unsigned windowId = WindowId) {
|
|
SDL_Event event{};
|
|
event.type = SDL_TEXTEDITING;
|
|
event.edit.windowID = windowId;
|
|
std::strncpy(event.edit.text, utf8, sizeof(event.edit.text) - 1);
|
|
event.edit.start = start;
|
|
event.edit.length = length;
|
|
g_window.HandleEvent(event, WindowId);
|
|
}
|
|
|
|
void Type(const char* utf8) {
|
|
SDL_Event event{};
|
|
event.type = SDL_TEXTINPUT;
|
|
event.text.windowID = WindowId;
|
|
std::strncpy(event.text.text, utf8, sizeof(event.text.text) - 1);
|
|
g_window.HandleEvent(event, WindowId);
|
|
}
|
|
|
|
void Key(SDL_Scancode scancode, SDL_Keycode sym, unsigned modifiers, bool repeat = false, unsigned type = SDL_KEYDOWN, unsigned windowId = WindowId) {
|
|
SDL_Event event{};
|
|
event.type = type;
|
|
event.key.windowID = windowId;
|
|
event.key.keysym.scancode = scancode;
|
|
event.key.keysym.sym = sym;
|
|
event.key.keysym.mod = static_cast<Uint16>(modifiers);
|
|
event.key.repeat = repeat ? 1 : 0;
|
|
g_window.HandleEvent(event, WindowId);
|
|
}
|
|
|
|
Param DialogParam(char16_t* buffer, std::uint32_t maxLength) {
|
|
Param param{};
|
|
param.max_text_length = maxLength;
|
|
param.input_text_buffer = buffer;
|
|
return param;
|
|
}
|
|
|
|
ImeKeyboardView View() {
|
|
ImeKeyboardView view;
|
|
Require(ImeReadKeyboardView_nid_postfix(view), "the dialog shows no keyboard");
|
|
return view;
|
|
}
|
|
|
|
void Open(const Param& param) {
|
|
Require(sceImeDialogInit(¶m, nullptr) == 0, "init");
|
|
Require(sceImeDialogGetStatus() == StatusRunning, "not running");
|
|
}
|
|
|
|
void Close() {
|
|
Require(sceImeDialogTerm() == 0, "term");
|
|
}
|
|
|
|
void CheckEditingEvents() {
|
|
char16_t buffer[16] = u"x";
|
|
const Param param = DialogParam(buffer, 8);
|
|
Open(param);
|
|
Edit("k", 1, 0);
|
|
Require(View().preedit == u"k" && View().preeditCursor == 1, "an SDL text editing event is not shown as a preedit");
|
|
Edit("ka", 2, 0);
|
|
Require(View().preedit == u"ka" && View().preeditCursor == 2, "a second edit does not replace the first");
|
|
Edit("\xe3\x81\x8b", 1, 1, WindowId + 1);
|
|
Require(View().preedit == u"ka", "an edit for another window was shown");
|
|
Type("\xe3\x81\x8b");
|
|
Edit("", 0, 0);
|
|
Require(View().preedit.empty() && View().text == u"x\u304B" && std::u16string(buffer) == u"x", "the commit is not the only text and the preedit is not gone");
|
|
Edit("", 0, 0);
|
|
Require(View().text == u"x\u304B", "an empty edit changed the text");
|
|
Edit("ni", 2, 0);
|
|
Edit("", 0, 0);
|
|
Require(View().preedit.empty(), "an empty edit does not clear the composition");
|
|
Key(SDL_SCANCODE_ESCAPE, SDLK_ESCAPE, 0);
|
|
Require(sceImeDialogGetStatus() == 2, "Escape with no composition does not cancel");
|
|
Close();
|
|
|
|
Open(param);
|
|
Edit("ni", 2, 0);
|
|
Key(SDL_SCANCODE_ESCAPE, SDLK_ESCAPE, 0);
|
|
Require(sceImeDialogGetStatus() == StatusRunning && View().preedit.empty(), "Escape from the keyboard with a composition cancels the dialog");
|
|
Close();
|
|
|
|
Edit("late", 0, 0);
|
|
Open(param);
|
|
Require(View().preedit.empty(), "an edit with no dialog was kept");
|
|
Close();
|
|
}
|
|
|
|
void CheckPasteShortcuts() {
|
|
char16_t buffer[64] = u"";
|
|
const Param param = DialogParam(buffer, 32);
|
|
g_window.SetClipboardProvider(&ReadClipboard);
|
|
g_clipboard = "ab";
|
|
Open(param);
|
|
|
|
g_reads = 0;
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(g_reads == 1 && View().text == u"ab", "Ctrl+V does not paste");
|
|
Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT);
|
|
Require(g_reads == 2 && View().text == u"abab", "Shift+Insert does not paste");
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier | KMOD_LSHIFT);
|
|
Require(g_reads == 3 && View().text == u"ababab", "Ctrl+Shift+V does not paste");
|
|
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, true);
|
|
Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT, true);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier | KMOD_LALT);
|
|
Key(SDL_SCANCODE_V, SDLK_v, 0);
|
|
Key(SDL_SCANCODE_V, SDLK_v, WrongModifier);
|
|
Key(SDL_SCANCODE_INSERT, SDLK_INSERT, 0);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, false, SDL_KEYUP);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier, false, SDL_KEYDOWN, WindowId + 1);
|
|
Key(SDL_SCANCODE_C, SDLK_c, PasteModifier);
|
|
Key(SDL_SCANCODE_X, SDLK_x, PasteModifier);
|
|
Key(SDL_SCANCODE_A, SDLK_a, PasteModifier);
|
|
Require(g_reads == 3 && View().text == u"ababab", "a key that is not a paste read the clipboard or changed the text");
|
|
|
|
g_clipboardAvailable = false;
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(g_reads == 4 && View().text == u"ababab", "an unavailable clipboard changed the text");
|
|
g_clipboardAvailable = true;
|
|
g_clipboard.clear();
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(g_reads == 5 && View().text == u"ababab", "an empty clipboard changed the text");
|
|
g_clipboard = "cd";
|
|
|
|
Edit("ni", 2, 0);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(View().text == u"ababab", "Ctrl+V pasted while a composition was shown");
|
|
Edit("", 0, 0);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(View().text == u"abababcd", "Ctrl+V does not paste once the composition ends");
|
|
Require(std::u16string(buffer).empty(), "a paste reached the buffer before accept");
|
|
Close();
|
|
}
|
|
|
|
void CheckNothingCaptured() {
|
|
g_window.SetClipboardProvider(&ReadClipboard);
|
|
g_clipboard = "secret";
|
|
g_reads = 0;
|
|
Require(!ImeCapturesKeyboard_nid_postfix(), "something is capturing");
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Key(SDL_SCANCODE_INSERT, SDLK_INSERT, KMOD_LSHIFT);
|
|
Require(g_reads == 0, "the clipboard was read while no dialog captures the keyboard");
|
|
|
|
char16_t buffer[16] = u"";
|
|
const Param param = DialogParam(buffer, 16);
|
|
Open(param);
|
|
Require(sceImeDialogAbort() == 0, "abort");
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(g_reads == 0, "the clipboard was read after the dialog finished");
|
|
Close();
|
|
|
|
ImeTextInput bare;
|
|
Open(param);
|
|
SDL_Event event{};
|
|
event.type = SDL_KEYDOWN;
|
|
event.key.windowID = WindowId;
|
|
event.key.keysym.scancode = SDL_SCANCODE_V;
|
|
event.key.keysym.sym = SDLK_v;
|
|
event.key.keysym.mod = static_cast<Uint16>(PasteModifier);
|
|
bare.HandleEvent(event, WindowId);
|
|
Require(View().text.empty() && g_reads == 0, "a window with no clipboard provider pasted");
|
|
Close();
|
|
}
|
|
|
|
void CheckHugeClipboard() {
|
|
char16_t buffer[2049] = u"";
|
|
const Param param = DialogParam(buffer, 2048);
|
|
g_window.SetClipboardProvider(&ReadClipboard);
|
|
g_clipboard.assign(4u * 1024u * 1024u, 'x');
|
|
Open(param);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(View().text.size() == 2048, "a huge clipboard is not cut at the dialog limit");
|
|
Close();
|
|
}
|
|
|
|
void CheckSession() {
|
|
struct Buffers {
|
|
char16_t text[2049] = {};
|
|
alignas(16) unsigned char work[0x5000] = {};
|
|
} buffers;
|
|
Param param;
|
|
sceImeParamInit(¶m);
|
|
param.user_id = 1;
|
|
param.max_text_length = 16;
|
|
param.input_text_buffer = buffers.text;
|
|
param.option = 0x00000001;
|
|
param.work = buffers.work;
|
|
param.handler = Ignore;
|
|
g_window.SetClipboardProvider(&ReadClipboard);
|
|
g_clipboard = "a\r\nb";
|
|
Require(sceImeOpen_nid_postfix(¶m, nullptr) == 0, "open");
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Edit("ni", 2, 0);
|
|
Key(SDL_SCANCODE_V, SDLK_v, PasteModifier);
|
|
Require(sceImeUpdate(param.handler) == 0, "update");
|
|
Require(std::u16string(buffers.text) == u"a\nb", "the session text is not the single paste with a normalised break");
|
|
Require(sceImeClose_nid_postfix() == 0, "close");
|
|
}
|
|
|
|
}
|
|
|
|
int main() {
|
|
CheckEditingEvents();
|
|
CheckPasteShortcuts();
|
|
CheckNothingCaptured();
|
|
CheckHugeClipboard();
|
|
CheckSession();
|
|
}
|