diff --git a/core/libs/prx/libSceMouse/CMakeLists.txt b/core/libs/prx/libSceMouse/CMakeLists.txt new file mode 100644 index 000000000..76b0c4fe8 --- /dev/null +++ b/core/libs/prx/libSceMouse/CMakeLists.txt @@ -0,0 +1,22 @@ +add_library(libSceMouse SHARED EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_SOURCE_DIR}/Export.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/mouse_impl.cpp +) +target_include_directories(libSceMouse PRIVATE ${LIBS_INCLUDE_DIR}) +set_target_properties(libSceMouse PROPERTIES CXX_EXTENSIONS OFF) +configure_windows_unwind(libSceMouse) + +if(BUILD_TESTING) + add_executable(mouse_tests + ${CMAKE_CURRENT_SOURCE_DIR}/tests/Mouse.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../libSceVideoOut/src/MouseInput.cpp + ) + target_include_directories(mouse_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include) + target_link_libraries(mouse_tests PRIVATE libSceMouse libkernel libc) + configure_windows_unwind(mouse_tests) + add_test(NAME mouse_input COMMAND mouse_tests) + if(WIN32) + set_tests_properties(mouse_input PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceMouse/Export.cpp b/core/libs/prx/libSceMouse/Export.cpp index 5e6120682..9d820d245 100644 --- a/core/libs/prx/libSceMouse/Export.cpp +++ b/core/libs/prx/libSceMouse/Export.cpp @@ -1,36 +1,25 @@ -#include -#include -#include "SceTypes.hpp" -#include "prx/libc/include/General.hpp" +#include "prx/libSceMouse/include/MouseState.hpp" extern "C" { -int APS5_VABI sceMouseClose(int32_t handle) { - (void)handle; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseInit() { + return Mouse::Initialize(); } -int APS5_VABI sceMouseInit(void) { - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseOpen(int userId, std::int32_t type, std::int32_t index, const void* param) { + return Mouse::Open(userId, type, index, static_cast(param)); } -int APS5_VABI sceMouseOpen(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; +int APS5_VABI sceMouseClose(std::int32_t handle) { + return Mouse::Close(handle); } -int APS5_VABI sceMouseRead(int32_t handle, MouseData* data, int32_t num) { - (void)handle; - (void)data; - (void)num; - NotImplemented_nid_no_patch(__func__); - return 0; +int APS5_VABI sceMouseRead(std::int32_t handle, MouseData* data, std::int32_t num) { + return Mouse::Read(handle, data, num); +} + +void MousePublishInput_nid_postfix(const MouseInputEvent& event) { + Mouse::Publish(event); } } diff --git a/core/libs/prx/libSceMouse/include/MouseState.hpp b/core/libs/prx/libSceMouse/include/MouseState.hpp new file mode 100644 index 000000000..bf7d57a09 --- /dev/null +++ b/core/libs/prx/libSceMouse/include/MouseState.hpp @@ -0,0 +1,28 @@ +#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSESTATE_HPP +#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSESTATE_HPP + +#include "prx/libSceMouse/include/mouse_structs.h" + +struct MouseInputEvent { + std::int32_t x = 0; + std::int32_t y = 0; + std::int32_t wheel = 0; + std::int32_t tilt = 0; + std::uint32_t button = 0; + bool pressed = false; + bool resetButtons = false; + bool connectionChange = false; + bool connected = true; +}; + +namespace Mouse { +int Initialize(); +int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param); +int Close(std::int32_t handle); +int Read(std::int32_t handle, MouseData* data, std::int32_t num); +void Publish(const MouseInputEvent& event); +} + +extern "C" void MousePublishInput_nid_postfix(const MouseInputEvent& event); + +#endif diff --git a/core/libs/prx/libSceMouse/include/mouse_structs.h b/core/libs/prx/libSceMouse/include/mouse_structs.h new file mode 100644 index 000000000..30c7d3a8f --- /dev/null +++ b/core/libs/prx/libSceMouse/include/mouse_structs.h @@ -0,0 +1,34 @@ +#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H +#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H + +#include +#include +#include "SceTypes.hpp" + +struct MouseOpenParam { + std::uint8_t behaviorFlag; + std::uint8_t reserved[7]; +}; + +constexpr int MOUSE_OK = 0; +constexpr int MOUSE_ERROR_INVALID_ARG = static_cast(0x80df0001u); +constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast(0x80df0003u); +constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast(0x80df0004u); +constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast(0x80df0005u); +constexpr int MOUSE_MAX_DATA_NUM = 64; +constexpr int MOUSE_HANDLE = 1; +constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1; + +static_assert(sizeof(MouseOpenParam) == 8); +static_assert(sizeof(MouseData) == 40); +static_assert(alignof(MouseData) == 8); +static_assert(offsetof(MouseData, timestamp) == 0); +static_assert(offsetof(MouseData, connected) == 8); +static_assert(offsetof(MouseData, buttons) == 12); +static_assert(offsetof(MouseData, x_axis) == 16); +static_assert(offsetof(MouseData, y_axis) == 20); +static_assert(offsetof(MouseData, wheel) == 24); +static_assert(offsetof(MouseData, tilt) == 28); +static_assert(offsetof(MouseData, reserved) == 32); + +#endif diff --git a/core/libs/prx/libSceMouse/src/mouse_impl.cpp b/core/libs/prx/libSceMouse/src/mouse_impl.cpp new file mode 100644 index 000000000..5bcf78e72 --- /dev/null +++ b/core/libs/prx/libSceMouse/src/mouse_impl.cpp @@ -0,0 +1,106 @@ +#include "prx/libSceMouse/include/MouseState.hpp" +#include "prx/libkernel/Time/include/Time.hpp" + +#include +#include +#include + +namespace { +std::mutex mouseMutex; +std::array history{}; +int head = 0; +int count = 0; +bool initialized = false; +bool opened = false; +bool connected = true; +std::uint32_t buttons = 0; + +void enqueue(const MouseData& data) { + if (count == MOUSE_MAX_DATA_NUM) { + head = (head + 1) % MOUSE_MAX_DATA_NUM; + --count; + } + history[(head + count) % MOUSE_MAX_DATA_NUM] = data; + ++count; +} +} + +namespace Mouse { + +int Initialize() { + std::lock_guard lock(mouseMutex); + initialized = true; + return MOUSE_OK; +} + +int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam* param) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (userId < 0 || type != 0 || index != 0 || + (param != nullptr && (param->behaviorFlag & ~MOUSE_OPEN_PARAM_MERGED) != 0)) { + return MOUSE_ERROR_INVALID_ARG; + } + if (opened) return MOUSE_ERROR_ALREADY_OPENED; + opened = true; + buttons = 0; + head = 0; + count = 0; + MouseData initial{}; + initial.timestamp = sceKernelGetProcessTime(); + initial.connected = connected; + enqueue(initial); + return MOUSE_HANDLE; +} + +int Close(std::int32_t handle) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + opened = false; + buttons = 0; + head = 0; + count = 0; + return MOUSE_OK; +} + +int Read(std::int32_t handle, MouseData* data, std::int32_t num) { + std::lock_guard lock(mouseMutex); + if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED; + if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE; + if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG; + const int available = std::min(count, num); + for (int i = 0; i < available; ++i) { + data[i] = history[head]; + head = (head + 1) % MOUSE_MAX_DATA_NUM; + } + count -= available; + return available; +} + +void Publish(const MouseInputEvent& event) { + std::lock_guard lock(mouseMutex); + if (event.connectionChange) { + if (connected == event.connected) return; + connected = event.connected; + if (!connected) buttons = 0; + } else if (!connected) { + return; + } + if (!opened) return; + if (event.resetButtons) buttons = 0; + if (event.button != 0) { + if (event.pressed) buttons |= event.button; + else buttons &= ~event.button; + } + MouseData data{}; + data.timestamp = sceKernelGetProcessTime(); + data.connected = connected; + data.buttons = buttons; + data.x_axis = event.x; + data.y_axis = event.y; + data.wheel = event.wheel; + data.tilt = event.tilt; + enqueue(data); +} + +} diff --git a/core/libs/prx/libSceMouse/tests/Mouse.cpp b/core/libs/prx/libSceMouse/tests/Mouse.cpp new file mode 100644 index 000000000..e01f3f9b4 --- /dev/null +++ b/core/libs/prx/libSceMouse/tests/Mouse.cpp @@ -0,0 +1,108 @@ +#include "prx/libSceMouse/include/mouse_structs.h" +#include "prx/libSceMouse/include/MouseState.hpp" +#include "prx/libSceVideoOut/include/MouseInput.hpp" +#include "SDL_events.h" +#include "SDL_mouse.h" + +#include + +extern "C" { +int APS5_VABI sceMouseInit(); +int APS5_VABI sceMouseOpen(int, std::int32_t, std::int32_t, const void*); +int APS5_VABI sceMouseClose(std::int32_t); +int APS5_VABI sceMouseRead(std::int32_t, MouseData*, std::int32_t); +} + +static void Require(bool value) { if (!value) std::abort(); } + +int main() { + MouseData data[64]{}; + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_ERROR_NOT_INITIALIZED); + Require(sceMouseInit() == MOUSE_OK); + Require(sceMouseInit() == MOUSE_OK); + Require(sceMouseOpen(1, 0, 1, nullptr) == MOUSE_ERROR_INVALID_ARG); + MouseOpenParam unsupported{}; + unsupported.behaviorFlag = 2; + Require(sceMouseOpen(1, 0, 0, &unsupported) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE); + Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_ERROR_ALREADY_OPENED); + Require(sceMouseRead(42, data, 1) == MOUSE_ERROR_INVALID_HANDLE); + Require(sceMouseRead(MOUSE_HANDLE, nullptr, 1) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseRead(MOUSE_HANDLE, data, 65) == MOUSE_ERROR_INVALID_ARG); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1); + Require(data[0].connected && data[0].buttons == 0); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + MouseInput input; + SDL_Event event{}; + event.type = SDL_MOUSEMOTION; + event.motion.windowID = 7; + event.motion.xrel = 9; + event.motion.yrel = -4; + input.HandleEvent(event, 8); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + input.HandleEvent(event, 7); + + event = {}; + event.type = SDL_MOUSEBUTTONDOWN; + event.button.windowID = 7; + event.button.button = SDL_BUTTON_RIGHT; + input.HandleEvent(event, 7); + event = {}; + event.type = SDL_MOUSEWHEEL; + event.wheel.windowID = 7; + event.wheel.x = 2; + event.wheel.y = -3; + event.wheel.direction = SDL_MOUSEWHEEL_FLIPPED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 3) == 3); + Require(data[0].x_axis == 9 && data[0].y_axis == -4 && data[0].buttons == 0); + Require(data[1].buttons == 2); + Require(data[2].buttons == 2 && data[2].wheel == 3 && data[2].tilt == -2); + Require(data[0].timestamp <= data[2].timestamp); + event = {}; + event.type = SDL_MOUSEBUTTONDOWN; + event.button.windowID = 7; + event.button.button = SDL_BUTTON_X1; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + + event = {}; + event.type = SDL_WINDOWEVENT; + event.window.windowID = 7; + event.window.event = SDL_WINDOWEVENT_FOCUS_LOST; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1); + Require(data[0].connected && data[0].buttons == 0); + event.type = SDL_MOUSEMOTION; + event.motion.xrel = 5; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + event.type = SDL_WINDOWEVENT; + event.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + event.window.event = SDL_WINDOWEVENT_CLOSE; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && !data[0].connected); + event.window.event = SDL_WINDOWEVENT_FOCUS_GAINED; + input.HandleEvent(event, 7); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].connected); + + for (int i = 0; i < 70; ++i) { + MouseInputEvent motion{}; + motion.x = i; + MousePublishInput_nid_postfix(motion); + } + Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 64); + Require(data[0].x_axis == 6 && data[63].x_axis == 69); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == MOUSE_ERROR_INVALID_HANDLE); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_ERROR_INVALID_HANDLE); + MouseOpenParam merged{}; + merged.behaviorFlag = MOUSE_OPEN_PARAM_MERGED; + Require(sceMouseOpen(1, 0, 0, &merged) == MOUSE_HANDLE); + Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].buttons == 0); + Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK); +} diff --git a/core/libs/prx/libSceVideoOut/CMakeLists.txt b/core/libs/prx/libSceVideoOut/CMakeLists.txt index e41cf34bd..e039c0529 100644 --- a/core/libs/prx/libSceVideoOut/CMakeLists.txt +++ b/core/libs/prx/libSceVideoOut/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(libSceVideoOut SHARED EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/src/VideoOutDriver.cpp ${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/InputMapping.cpp ) @@ -14,7 +15,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 libc libkernel SDL2-static) +target_link_libraries(libSceVideoOut PRIVATE libSceAgcDriver libScePad libSceMouse libc libkernel SDL2-static) if(WIN32) target_link_libraries(libSceVideoOut PRIVATE comctl32) endif() diff --git a/core/libs/prx/libSceVideoOut/include/MouseInput.hpp b/core/libs/prx/libSceVideoOut/include/MouseInput.hpp new file mode 100644 index 000000000..56ea31b6d --- /dev/null +++ b/core/libs/prx/libSceVideoOut/include/MouseInput.hpp @@ -0,0 +1,14 @@ +#ifndef CORE_LIBS_PRX_LIBSCEVIDEOOUT_MOUSEINPUT_HPP +#define CORE_LIBS_PRX_LIBSCEVIDEOOUT_MOUSEINPUT_HPP + +#include "SDL_events.h" + +class MouseInput { +public: + void HandleEvent(const SDL_Event& event, unsigned windowId); + +private: + bool focused = true; +}; + +#endif diff --git a/core/libs/prx/libSceVideoOut/src/MouseInput.cpp b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp new file mode 100644 index 000000000..3a5443e6e --- /dev/null +++ b/core/libs/prx/libSceVideoOut/src/MouseInput.cpp @@ -0,0 +1,55 @@ +#include "prx/libSceVideoOut/include/MouseInput.hpp" +#include "prx/libSceMouse/include/MouseState.hpp" + +#include "SDL_mouse.h" + +void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) { + MouseInputEvent input{}; + switch (event.type) { + case SDL_MOUSEMOTION: + if (event.motion.windowID != windowId || !focused) return; + input.x = event.motion.xrel; + input.y = event.motion.yrel; + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + if (event.button.windowID != windowId || !focused) return; + switch (event.button.button) { + case SDL_BUTTON_LEFT: input.button = 1; break; + case SDL_BUTTON_RIGHT: input.button = 2; break; + case SDL_BUTTON_MIDDLE: input.button = 4; break; + default: return; + } + input.pressed = event.type == SDL_MOUSEBUTTONDOWN; + break; + case SDL_MOUSEWHEEL: + if (event.wheel.windowID != windowId || !focused) return; + input.wheel = event.wheel.y; + input.tilt = event.wheel.x; + if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) { + input.wheel = -input.wheel; + input.tilt = -input.tilt; + } + if (input.wheel == 0 && input.tilt == 0) return; + break; + case SDL_WINDOWEVENT: + if (event.window.windowID != windowId) return; + if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) { + focused = false; + input.resetButtons = 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; + } + MousePublishInput_nid_postfix(input); +} diff --git a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp index ffc1ac470..c2d62978b 100644 --- a/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp +++ b/core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp @@ -10,6 +10,7 @@ #include "SDL.h" #include "SDL_vulkan.h" #include "prx/libSceVideoOut/include/PadInput.hpp" +#include "prx/libSceVideoOut/include/MouseInput.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) { std::shared_ptr current; try { PadInput padInput; + MouseInput mouseInput; while (!token.stop_requested()) { { std::unique_lock lock(flipQueue->mutex); @@ -481,6 +483,7 @@ void VideoOutDriver::presentLoop(std::stop_token token) { while (SDL_PollEvent(&event)) { require(event.type != SDL_QUIT, "window was closed"); padInput.HandleEvent(event, window); + if (window.Handle() != nullptr) mouseInput.HandleEvent(event, SDL_GetWindowID(window.Handle())); } padInput.Update(); if (current) {