Merge pull request #73 from julienlgy/feat/lib-sce-mouse

feat(libSceMouse): implement stateful mouse API exports
Replace the libSceMouse stubs with an implementation that follows AnyPS5’s existing NID-based library integration. Mouse input comes from the host SDL event loop; this change does not add memory hooks or direct access to console HID devices.

Manage the mouse handle, button state, timestamps, and a thread-safe, 64-event history in src/mouse_impl.cpp. Reads are non-blocking.

Forward relative motion, supported buttons, vertical scrolling, and horizontal scrolling from libSceVideoOut’s SDL event loop.

Release pressed buttons when the window loses focus without reporting a hardware disconnection.
This commit is contained in:
boykopovar 2026-09-28 13:45:13 +03:00 committed by GitHub
commit 0fe554943a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 385 additions and 25 deletions

View File

@ -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:$<TARGET_FILE_DIR:libSceMouse>")
endif()
endif()

View File

@ -1,36 +1,25 @@
#include <cstdint>
#include <cstddef>
#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<const MouseOpenParam*>(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);
}
}

View File

@ -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

View File

@ -0,0 +1,34 @@
#ifndef CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H
#define CORE_LIBS_PRX_LIBSCEMOUSE_MOUSE_STRUCTS_H
#include <cstddef>
#include <cstdint>
#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<int>(0x80df0001u);
constexpr int MOUSE_ERROR_INVALID_HANDLE = static_cast<int>(0x80df0003u);
constexpr int MOUSE_ERROR_ALREADY_OPENED = static_cast<int>(0x80df0004u);
constexpr int MOUSE_ERROR_NOT_INITIALIZED = static_cast<int>(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

View File

@ -0,0 +1,106 @@
#include "prx/libSceMouse/include/MouseState.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include <algorithm>
#include <array>
#include <mutex>
namespace {
std::mutex mouseMutex;
std::array<MouseData, MOUSE_MAX_DATA_NUM> 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);
}
}

View File

@ -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 <cstdlib>
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);
}

View File

@ -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()

View File

@ -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

View File

@ -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);
}

View File

@ -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<FlipRequest> 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) {