diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index a31b2c658..2a3396280 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -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; diff --git a/core/libs/prx/libSceKeyboard/CMakeLists.txt b/core/libs/prx/libSceKeyboard/CMakeLists.txt new file mode 100644 index 000000000..0e3fe276f --- /dev/null +++ b/core/libs/prx/libSceKeyboard/CMakeLists.txt @@ -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:$") + endif() +endif() diff --git a/core/libs/prx/libSceKeyboard/Export.cpp b/core/libs/prx/libSceKeyboard/Export.cpp index fa393bc36..9333cf673 100644 --- a/core/libs/prx/libSceKeyboard/Export.cpp +++ b/core/libs/prx/libSceKeyboard/Export.cpp @@ -1,54 +1,34 @@ -#include -#include -#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); } } diff --git a/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp new file mode 100644 index 000000000..11c32a544 --- /dev/null +++ b/core/libs/prx/libSceKeyboard/include/KeyboardState.hpp @@ -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 diff --git a/core/libs/prx/libSceKeyboard/include/keyboard_structs.h b/core/libs/prx/libSceKeyboard/include/keyboard_structs.h new file mode 100644 index 000000000..326fa4b84 --- /dev/null +++ b/core/libs/prx/libSceKeyboard/include/keyboard_structs.h @@ -0,0 +1,41 @@ +#ifndef CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARD_STRUCTS_H +#define CORE_LIBS_PRX_LIBSCEKEYBOARD_KEYBOARD_STRUCTS_H + +#include +#include +#include "SceTypes.hpp" + +constexpr int KEYBOARD_OK = 0; +constexpr int KEYBOARD_ERROR_INVALID_ARG = static_cast(0x80da0001u); +constexpr int KEYBOARD_ERROR_INVALID_HANDLE = static_cast(0x80da0003u); +constexpr int KEYBOARD_ERROR_ALREADY_OPENED = static_cast(0x80da0004u); +constexpr int KEYBOARD_ERROR_NOT_INITIALIZED = static_cast(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 diff --git a/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp new file mode 100644 index 000000000..7bc369767 --- /dev/null +++ b/core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp @@ -0,0 +1,189 @@ +#include "prx/libSceKeyboard/include/KeyboardState.hpp" +#include "prx/libc/include/General.hpp" +#include "prx/libkernel/Time/include/Time.hpp" + +#include +#include +#include + +namespace { +std::mutex keyboardMutex; +std::array 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 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(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((upper ? 'A' : 'a') + keyCode - 0x04); + } + if (keyCode >= 0x1e && keyCode <= 0x38) { + return static_cast((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(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()); +} + +} diff --git a/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp new file mode 100644 index 000000000..3b633176d --- /dev/null +++ b/core/libs/prx/libSceKeyboard/tests/Keyboard.cpp @@ -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 + +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); +} diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index 992b1ae9c..d8e45a1df 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -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() diff --git a/core/libs/prx/libSceVideoOut/include/KeyboardInput.hpp b/core/libs/prx/libSceVideoOut/include/KeyboardInput.hpp new file mode 100644 index 000000000..f08af07ee --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/KeyboardInput.hpp @@ -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 diff --git a/core/libs/prx/libSceVideoOut/src/KeyboardInput.cpp b/core/libs/prx/libSceVideoOut/src/KeyboardInput.cpp new file mode 100644 index 000000000..241b61fa8 --- /dev/null +++ b/core/libs/prx/libSceVideoOut/src/KeyboardInput.cpp @@ -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(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); +} diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index fcc4fa910..19adde40e 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -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) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 4f89d4097..db057178a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -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.