feat(libSceKeyboard): implement the keyboard library

Keyboard input follows libSceMouse: the video out event loop forwards SDL
key and focus events, whose scancodes are USB HID usages, and the library
keeps the pressed keys, modifiers and lock LEDs in a 16-entry history for
sceKeyboardRead and a snapshot for sceKeyboardReadState.
sceKeyboardGetKey2Char translates the 101-key arrangement; the 106-key
one throws and is listed as technical debt.

KeyboardData holds 16 key codes (96 bytes), not 6.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Adrià Franch 2026-10-01 19:00:23 +02:00
parent bc51b337ad
commit 63501b6d73
12 changed files with 488 additions and 41 deletions

View File

@ -974,7 +974,7 @@ struct MouseData {
std::uint8_t reserved[8];
};
constexpr std::uint32_t KEYBOARD_MAX_KEYCODES = 6;
constexpr std::uint32_t KEYBOARD_MAX_KEYCODES = 16;
struct KeyboardData {
std::uint64_t timestamp;

View File

@ -0,0 +1,22 @@
add_library(libSceKeyboard SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/keyboard_impl.cpp
)
target_include_directories(libSceKeyboard PRIVATE ${LIBS_INCLUDE_DIR})
set_target_properties(libSceKeyboard PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(libSceKeyboard)
if(BUILD_TESTING)
add_test_executable(keyboard_tests
${CMAKE_CURRENT_SOURCE_DIR}/tests/Keyboard.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/KeyboardInput.cpp
)
target_include_directories(keyboard_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include)
target_link_libraries(keyboard_tests PRIVATE libSceKeyboard libkernel libc)
configure_windows_unwind(keyboard_tests)
add_test(NAME keyboard_input COMMAND keyboard_tests)
if(WIN32)
set_tests_properties(keyboard_input PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceKeyboard>")
endif()
endif()

View File

@ -1,54 +1,34 @@
#include <cstdint>
#include <cstddef>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libSceKeyboard/include/KeyboardState.hpp"
extern "C" {
int APS5_VABI sceKeyboardClose(int32_t handle) {
(void)handle;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKeyboardGetKey2Char(int32_t handle, int32_t arrange, uint32_t led, uint32_t modifier_key, uint16_t key_code, KeyboardCharData* char_data) {
(void)handle;
(void)arrange;
(void)led;
(void)modifier_key;
(void)key_code;
(void)char_data;
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceKeyboardInit(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return Keyboard::Initialize();
}
int APS5_VABI sceKeyboardOpen(int user_id, int32_t type, int32_t index, const void* param) {
(void)user_id;
(void)type;
(void)index;
(void)param;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)param;
return Keyboard::Open(user_id, type, index);
}
int APS5_VABI sceKeyboardClose(int32_t handle) {
return Keyboard::Close(handle);
}
int APS5_VABI sceKeyboardRead(int32_t handle, KeyboardData* data, int32_t num) {
(void)handle;
(void)data;
(void)num;
NotImplemented_nid_no_patch(__func__);
return 0;
return Keyboard::Read(handle, data, num);
}
int APS5_VABI sceKeyboardReadState(int32_t handle, KeyboardData* data) {
(void)handle;
(void)data;
NotImplemented_nid_no_patch(__func__);
return 0;
return Keyboard::ReadState(handle, data);
}
int APS5_VABI sceKeyboardGetKey2Char(int32_t handle, int32_t arrange, uint32_t led, uint32_t modifier_key, uint16_t key_code, KeyboardCharData* char_data) {
return Keyboard::GetKey2Char(handle, arrange, led, modifier_key, key_code, char_data);
}
void KeyboardPublishInput_nid_postfix(const KeyboardInputEvent& event) {
Keyboard::Publish(event);
}
}

View File

@ -0,0 +1,27 @@
#ifndef CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARDSTATE_HPP
#define CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARDSTATE_HPP
#include "prx/libSceKeyboard/include/keyboard_structs.h"
struct KeyboardInputEvent {
std::uint16_t keyCode = 0;
bool pressed = false;
std::uint32_t led = 0;
bool resetKeys = false;
bool connectionChange = false;
bool connected = true;
};
namespace Keyboard {
int Initialize();
int Open(int userId, std::int32_t type, std::int32_t index);
int Close(std::int32_t handle);
int Read(std::int32_t handle, KeyboardData* data, std::int32_t num);
int ReadState(std::int32_t handle, KeyboardData* data);
int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData);
void Publish(const KeyboardInputEvent& event);
}
extern "C" void KeyboardPublishInput_nid_postfix(const KeyboardInputEvent& event);
#endif

View File

@ -0,0 +1,41 @@
#ifndef CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARD_STRUCTS_H
#define CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARD_STRUCTS_H
#include <cstddef>
#include <cstdint>
#include "SceTypes.hpp"
constexpr int KEYBOARD_OK = 0;
constexpr int KEYBOARD_ERROR_INVALID_ARG = static_cast<int>(0x80da0001u);
constexpr int KEYBOARD_ERROR_INVALID_HANDLE = static_cast<int>(0x80da0003u);
constexpr int KEYBOARD_ERROR_ALREADY_OPENED = static_cast<int>(0x80da0004u);
constexpr int KEYBOARD_ERROR_NOT_INITIALIZED = static_cast<int>(0x80da0005u);
constexpr int KEYBOARD_MAX_DATA_NUM = 16;
constexpr int KEYBOARD_HANDLE = 1;
constexpr std::int32_t KEYBOARD_ARRANGEMENT_101 = 0;
constexpr std::int32_t KEYBOARD_ARRANGEMENT_106 = 1;
constexpr std::uint32_t KEYBOARD_LED_NUM_LOCK = 1;
constexpr std::uint32_t KEYBOARD_LED_CAPS_LOCK = 2;
constexpr std::uint32_t KEYBOARD_LED_SCROLL_LOCK = 4;
constexpr std::uint32_t KEYBOARD_MOD_LEFT_SHIFT = 2;
constexpr std::uint32_t KEYBOARD_MOD_RIGHT_SHIFT = 32;
constexpr std::uint16_t KEYBOARD_KEY_LEFT_CTRL = 0xe0;
constexpr std::uint16_t KEYBOARD_KEY_RIGHT_GUI = 0xe7;
static_assert(sizeof(KeyboardData) == 96);
static_assert(alignof(KeyboardData) == 8);
static_assert(offsetof(KeyboardData, timestamp) == 0);
static_assert(offsetof(KeyboardData, intercepted) == 8);
static_assert(offsetof(KeyboardData, connected) == 16);
static_assert(offsetof(KeyboardData, length) == 20);
static_assert(offsetof(KeyboardData, led) == 24);
static_assert(offsetof(KeyboardData, modifier_key) == 28);
static_assert(offsetof(KeyboardData, key_code) == 32);
static_assert(offsetof(KeyboardData, reserve2) == 64);
static_assert(sizeof(KeyboardCharData) == 20);
static_assert(offsetof(KeyboardCharData, length) == 4);
static_assert(offsetof(KeyboardCharData, char_code) == 8);
#endif

View File

@ -0,0 +1,189 @@
#include "prx/libSceKeyboard/include/KeyboardState.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include <algorithm>
#include <array>
#include <mutex>
namespace {
std::mutex keyboardMutex;
std::array<KeyboardData, KEYBOARD_MAX_DATA_NUM> history{};
int head = 0;
int count = 0;
bool initialized = false;
bool opened = false;
bool connected = true;
std::uint32_t led = 0;
std::uint32_t modifiers = 0;
std::array<std::uint16_t, KEYBOARD_MAX_KEYCODES> keys{};
int keyCount = 0;
constexpr char US_NORMAL[] = "1234567890\n\0\b\t -=[]\\\0;'`,./";
constexpr char US_SHIFTED[] = "!@#$%^&*()\n\0\b\t _+{}|\0:\"~<>?";
constexpr char KEYPAD_DIGITS[] = "1234567890.";
KeyboardData snapshot() {
KeyboardData data{};
data.timestamp = sceKernelGetProcessTime();
data.connected = connected;
data.length = keyCount;
data.led = led;
data.modifier_key = modifiers;
std::copy_n(keys.begin(), keyCount, data.key_code);
return data;
}
void enqueue(const KeyboardData& data) {
if (count == KEYBOARD_MAX_DATA_NUM) {
head = (head + 1) % KEYBOARD_MAX_DATA_NUM;
--count;
}
history[(head + count) % KEYBOARD_MAX_DATA_NUM] = data;
++count;
}
void clearKeys() {
modifiers = 0;
keyCount = 0;
}
bool updateKey(std::uint16_t keyCode, bool pressed) {
if (keyCode >= KEYBOARD_KEY_LEFT_CTRL && keyCode <= KEYBOARD_KEY_RIGHT_GUI) {
const std::uint32_t bit = 1u << (keyCode - KEYBOARD_KEY_LEFT_CTRL);
const std::uint32_t updated = pressed ? modifiers | bit : modifiers & ~bit;
if (updated == modifiers) return false;
modifiers = updated;
return true;
}
const auto end = keys.begin() + keyCount;
const auto found = std::find(keys.begin(), end, keyCode);
if (pressed) {
if (found != end || keyCount == static_cast<int>(KEYBOARD_MAX_KEYCODES)) return false;
keys[keyCount++] = keyCode;
return true;
}
if (found == end) return false;
std::copy(found + 1, end, found);
--keyCount;
return true;
}
std::uint16_t usCharacter(std::uint32_t ledState, std::uint32_t modifierKey, std::uint16_t keyCode) {
const bool shift = (modifierKey & (KEYBOARD_MOD_LEFT_SHIFT | KEYBOARD_MOD_RIGHT_SHIFT)) != 0;
if (keyCode >= 0x04 && keyCode <= 0x1d) {
const bool upper = shift != ((ledState & KEYBOARD_LED_CAPS_LOCK) != 0);
return static_cast<std::uint16_t>((upper ? 'A' : 'a') + keyCode - 0x04);
}
if (keyCode >= 0x1e && keyCode <= 0x38) {
return static_cast<unsigned char>((shift ? US_SHIFTED : US_NORMAL)[keyCode - 0x1e]);
}
switch (keyCode) {
case 0x54: return '/';
case 0x55: return '*';
case 0x56: return '-';
case 0x57: return '+';
case 0x58: return '\n';
default: break;
}
if (keyCode >= 0x59 && keyCode <= 0x63 && (ledState & KEYBOARD_LED_NUM_LOCK) != 0) {
return static_cast<unsigned char>(KEYPAD_DIGITS[keyCode - 0x59]);
}
return 0;
}
}
namespace Keyboard {
int Initialize() {
std::lock_guard lock(keyboardMutex);
initialized = true;
return KEYBOARD_OK;
}
int Open(int userId, std::int32_t type, std::int32_t index) {
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (userId < 0 || type != 0 || index != 0) return KEYBOARD_ERROR_INVALID_ARG;
if (opened) return KEYBOARD_ERROR_ALREADY_OPENED;
opened = true;
clearKeys();
head = 0;
count = 0;
enqueue(snapshot());
return KEYBOARD_HANDLE;
}
int Close(std::int32_t handle) {
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
opened = false;
clearKeys();
head = 0;
count = 0;
return KEYBOARD_OK;
}
int Read(std::int32_t handle, KeyboardData* data, std::int32_t num) {
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
if (data == nullptr || num <= 0 || num > KEYBOARD_MAX_DATA_NUM) return KEYBOARD_ERROR_INVALID_ARG;
const int available = std::min(count, num);
for (int i = 0; i < available; ++i) {
data[i] = history[head];
head = (head + 1) % KEYBOARD_MAX_DATA_NUM;
}
count -= available;
return available;
}
int ReadState(std::int32_t handle, KeyboardData* data) {
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
if (data == nullptr) return KEYBOARD_ERROR_INVALID_ARG;
*data = snapshot();
return KEYBOARD_OK;
}
int GetKey2Char(std::int32_t handle, std::int32_t arrange, std::uint32_t ledState, std::uint32_t modifierKey, std::uint16_t keyCode, KeyboardCharData* charData) {
{
std::lock_guard lock(keyboardMutex);
if (!initialized) return KEYBOARD_ERROR_NOT_INITIALIZED;
if (handle != KEYBOARD_HANDLE || !opened) return KEYBOARD_ERROR_INVALID_HANDLE;
}
if (charData == nullptr) return KEYBOARD_ERROR_INVALID_ARG;
if (arrange == KEYBOARD_ARRANGEMENT_106) {
NotImplemented_nid_no_patch(__func__);
return KEYBOARD_ERROR_INVALID_ARG;
}
if (arrange != KEYBOARD_ARRANGEMENT_101) return KEYBOARD_ERROR_INVALID_ARG;
*charData = {};
charData->char_code = usCharacter(ledState, modifierKey, keyCode);
charData->processed = charData->char_code != 0;
charData->length = charData->processed ? 1 : 0;
return KEYBOARD_OK;
}
void Publish(const KeyboardInputEvent& event) {
std::lock_guard lock(keyboardMutex);
if (event.connectionChange) {
if (connected == event.connected) return;
connected = event.connected;
if (!connected) clearKeys();
} else if (!connected) {
return;
}
if (!opened) return;
if (event.resetKeys) clearKeys();
if (event.keyCode != 0) {
const bool ledChanged = led != event.led;
led = event.led;
if (!updateKey(event.keyCode, event.pressed) && !ledChanged) return;
}
enqueue(snapshot());
}
}

View File

@ -0,0 +1,128 @@
#include "prx/libSceKeyboard/include/keyboard_structs.h"
#include "prx/libSceKeyboard/include/KeyboardState.hpp"
#include "prx/libSceVideoOut/include/KeyboardInput.hpp"
#include "SDL_events.h"
#include "SDL_keyboard.h"
#include <cstdlib>
extern "C" {
int APS5_VABI sceKeyboardInit(void);
int APS5_VABI sceKeyboardOpen(int, std::int32_t, std::int32_t, const void*);
int APS5_VABI sceKeyboardClose(std::int32_t);
int APS5_VABI sceKeyboardRead(std::int32_t, KeyboardData*, std::int32_t);
int APS5_VABI sceKeyboardReadState(std::int32_t, KeyboardData*);
int APS5_VABI sceKeyboardGetKey2Char(std::int32_t, std::int32_t, std::uint32_t, std::uint32_t, std::uint16_t, KeyboardCharData*);
}
static void Require(bool value) { if (!value) std::abort(); }
static SDL_Event KeyEvent(Uint32 type, SDL_Scancode scancode, Uint16 mod = 0, Uint8 repeat = 0) {
SDL_Event event{};
event.type = type;
event.key.windowID = 7;
event.key.repeat = repeat;
event.key.keysym.scancode = scancode;
event.key.keysym.mod = mod;
return event;
}
static std::uint16_t Char(std::uint32_t led, std::uint32_t modifierKey, std::uint16_t keyCode) {
KeyboardCharData data{};
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, led, modifierKey, keyCode, &data) == KEYBOARD_OK);
Require(data.processed == (data.char_code != 0) && data.length == (data.processed ? 1 : 0));
return data.char_code;
}
int main() {
KeyboardData data[16]{};
KeyboardCharData charData{};
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_NOT_INITIALIZED);
Require(sceKeyboardInit() == KEYBOARD_OK);
Require(sceKeyboardInit() == KEYBOARD_OK);
Require(sceKeyboardOpen(1, 0, 1, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardOpen(1, 1, 0, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_ERROR_ALREADY_OPENED);
Require(sceKeyboardRead(42, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardRead(KEYBOARD_HANDLE, nullptr, 1) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 17) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardReadState(KEYBOARD_HANDLE, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1);
Require(data[0].connected && data[0].length == 0 && data[0].modifier_key == 0);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
KeyboardInput input;
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_A), 8);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_LSHIFT, KMOD_NUM), 7);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_A, KMOD_NUM | KMOD_LSHIFT), 7);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_A, KMOD_NUM | KMOD_LSHIFT, 1), 7);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_RETURN, KMOD_NUM | KMOD_LSHIFT), 7);
input.HandleEvent(KeyEvent(SDL_KEYUP, SDL_SCANCODE_A, KMOD_NUM | KMOD_LSHIFT), 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 16) == 4);
Require(data[0].length == 0 && data[0].modifier_key == 2 && data[0].led == KEYBOARD_LED_NUM_LOCK);
Require(data[1].length == 1 && data[1].key_code[0] == 0x04);
Require(data[2].length == 2 && data[2].key_code[0] == 0x04 && data[2].key_code[1] == 0x28);
Require(data[3].length == 1 && data[3].key_code[0] == 0x28 && data[3].modifier_key == 2);
Require(data[0].timestamp <= data[3].timestamp);
Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_OK);
Require(data[0].connected && data[0].length == 1 && data[0].key_code[0] == 0x28 && data[0].modifier_key == 2);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_CAPSLOCK, KMOD_NUM | KMOD_CAPS | KMOD_LSHIFT), 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1);
Require(data[0].led == (KEYBOARD_LED_NUM_LOCK | KEYBOARD_LED_CAPS_LOCK));
SDL_Event window{};
window.type = SDL_WINDOWEVENT;
window.window.windowID = 7;
window.window.event = SDL_WINDOWEVENT_FOCUS_LOST;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1);
Require(data[0].connected && data[0].length == 0 && data[0].modifier_key == 0);
input.HandleEvent(KeyEvent(SDL_KEYDOWN, SDL_SCANCODE_B), 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
window.window.event = SDL_WINDOWEVENT_CLOSE;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && !data[0].connected);
window.window.event = SDL_WINDOWEVENT_FOCUS_GAINED;
input.HandleEvent(window, 7);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].connected);
for (std::uint16_t key = 0x04; key < 0x04 + 20; ++key) {
KeyboardInputEvent press{};
press.keyCode = key;
press.pressed = true;
KeyboardPublishInput_nid_postfix(press);
}
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 16) == 16);
Require(data[15].length == 16 && data[15].key_code[15] == 0x13);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 0);
Require(Char(0, 0, 0x04) == 'a' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x1d) == 'Z');
Require(Char(KEYBOARD_LED_CAPS_LOCK, 0, 0x04) == 'A' && Char(KEYBOARD_LED_CAPS_LOCK, KEYBOARD_MOD_RIGHT_SHIFT, 0x04) == 'a');
Require(Char(KEYBOARD_LED_CAPS_LOCK, 0, 0x1e) == '1' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x1e) == '!');
Require(Char(0, 0, 0x27) == '0' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x27) == ')');
Require(Char(0, 0, 0x28) == '\n' && Char(0, 0, 0x2a) == '\b' && Char(0, 0, 0x2b) == '\t' && Char(0, 0, 0x2c) == ' ');
Require(Char(0, 0, 0x29) == 0 && Char(0, 0, 0x32) == 0 && Char(0, 0, 0x3a) == 0 && Char(0, 0, 0xe1) == 0);
Require(Char(0, 0, 0x31) == '\\' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x31) == '|');
Require(Char(0, 0, 0x34) == '\'' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x34) == '"');
Require(Char(0, 0, 0x38) == '/' && Char(0, KEYBOARD_MOD_LEFT_SHIFT, 0x38) == '?');
Require(Char(0, 0, 0x59) == 0 && Char(KEYBOARD_LED_NUM_LOCK, 0, 0x59) == '1');
Require(Char(KEYBOARD_LED_NUM_LOCK, 0, 0x62) == '0' && Char(KEYBOARD_LED_NUM_LOCK, 0, 0x63) == '.');
Require(Char(0, 0, 0x54) == '/' && Char(0, 0, 0x57) == '+' && Char(0, 0, 0x58) == '\n');
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, KEYBOARD_ARRANGEMENT_101, 0, 0, 0x04, nullptr) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardGetKey2Char(KEYBOARD_HANDLE, 2, 0, 0, 0x04, &charData) == KEYBOARD_ERROR_INVALID_ARG);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardReadState(KEYBOARD_HANDLE, data) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_ERROR_INVALID_HANDLE);
Require(sceKeyboardOpen(1, 0, 0, nullptr) == KEYBOARD_HANDLE);
Require(sceKeyboardRead(KEYBOARD_HANDLE, data, 1) == 1 && data[0].length == 0);
Require(sceKeyboardClose(KEYBOARD_HANDLE) == KEYBOARD_OK);
}

View File

@ -7,6 +7,7 @@ add_library(libSceVideoOut SHARED EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/src/DisplayWindow.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/PadInput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/MouseInput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/KeyboardInput.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/InputMapping.cpp
)
@ -15,7 +16,7 @@ target_include_directories(libSceVideoOut PRIVATE
${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include
${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include
)
target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libc libkernel SDL2-static)
target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libSceKeyboard libc libkernel SDL2-static)
if(WIN32)
target_link_libraries(libSceVideoOut PRIVATE comctl32)
endif()

View File

@ -0,0 +1,14 @@
#ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_KEYBOARDINPUT_HPP
#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_KEYBOARDINPUT_HPP
#include "SDL_events.h"
class KeyboardInput {
public:
void HandleEvent(const SDL_Event& event, unsigned windowId);
private:
bool focused = true;
};
#endif

View File

@ -0,0 +1,39 @@
#include "prx/libSceVideoOut/include/KeyboardInput.hpp"
#include "prx/libSceKeyboard/include/KeyboardState.hpp"
#include "SDL_keyboard.h"
void KeyboardInput::HandleEvent(const SDL_Event& event, unsigned windowId) {
KeyboardInputEvent input{};
switch (event.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
if (event.key.windowID != windowId || !focused || event.key.repeat != 0) return;
if (event.key.keysym.scancode <= SDL_SCANCODE_UNKNOWN || event.key.keysym.scancode > SDL_SCANCODE_RGUI) return;
input.keyCode = static_cast<std::uint16_t>(event.key.keysym.scancode);
input.pressed = event.type == SDL_KEYDOWN;
if ((event.key.keysym.mod & KMOD_NUM) != 0) input.led |= KEYBOARD_LED_NUM_LOCK;
if ((event.key.keysym.mod & KMOD_CAPS) != 0) input.led |= KEYBOARD_LED_CAPS_LOCK;
if ((event.key.keysym.mod & KMOD_SCROLL) != 0) input.led |= KEYBOARD_LED_SCROLL_LOCK;
break;
case SDL_WINDOWEVENT:
if (event.window.windowID != windowId) return;
if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
focused = false;
input.resetKeys = true;
} else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
focused = true;
input.connectionChange = true;
} else if (event.window.event == SDL_WINDOWEVENT_CLOSE) {
focused = false;
input.connectionChange = true;
input.connected = false;
} else {
return;
}
break;
default:
return;
}
KeyboardPublishInput_nid_postfix(input);
}

View File

@ -11,6 +11,7 @@
#include "SDL_vulkan.h"
#include "prx/libSceVideoOut/include/PadInput.hpp"
#include "prx/libSceVideoOut/include/MouseInput.hpp"
#include "prx/libSceVideoOut/include/KeyboardInput.hpp"
#include "prx/libScePad/include/PadState.hpp"
#include "prx/libkernel/Equeue/Equeue.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
@ -466,6 +467,7 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
try {
PadInput padInput;
MouseInput mouseInput;
KeyboardInput keyboardInput;
while (!token.stop_requested()) {
{
std::unique_lock lock(flipQueue->mutex);
@ -485,7 +487,10 @@ void VideoOutDriver::presentLoop(std::stop_token token) {
throw ProcessShutdown{};
}
padInput.HandleEvent(event, window);
if (window.Handle() != nullptr) mouseInput.HandleEvent(event, SDL_GetWindowID(window.Handle()));
if (window.Handle() != nullptr) {
mouseInput.HandleEvent(event, SDL_GetWindowID(window.Handle()));
keyboardInput.HandleEvent(event, SDL_GetWindowID(window.Handle()));
}
}
padInput.Update();
if (current) {

View File

@ -42,4 +42,5 @@ Throughout the project, every function at every stage either **does exactly what
- The Linux placement of the `--to-intel` stubs in the [ELF patcher](../../core/relinker/elfpatcher/src/linux/LinuxElfPatcher.cpp) is covered only by a synthetic test.
- The libc [SSE4a trap emulation](../../core/libs/prx/libc/src/specifics/windows/Sse4aEmulation.hpp) on Windows is superseded by `--to-intel` and remains only until the relinked title has been verified without it.
- `--to-intel` guest module trampolines are covered only by a synthetic relinker test; no game title has been verified with them on Linux or Windows.
- [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character.
- [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified.