Merge pull request #12958 from zaryabzip/feat/mouse-absolute-position

feat(libSceMouse): implement the absolute position calls as firmware 13.42 does
This commit is contained in:
Dean authored and GitHub committed 2026-11-03 06:25:57 +13:00
commit c47ee4e034
10 files changed
+537 -19

No files matched your search

+12
View File
@@ -7,6 +7,18 @@ target_include_directories(libSceMouse PRIVATE ${LIBS_INCLUDE_DIR})
set_target_properties(libSceMouse PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(libSceMouse)
if(BUILD_TESTING)
add_test_executable(mouse_absolute_tests ${CMAKE_CURRENT_SOURCE_DIR}/tests/MouseAbsolute.cpp)
target_include_directories(mouse_absolute_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(mouse_absolute_tests PRIVATE libSceMouse libkernel libc ${CMAKE_DL_LIBS})
configure_windows_unwind(mouse_absolute_tests)
add_test(NAME mouse_absolute_position COMMAND mouse_absolute_tests)
if(WIN32)
set_tests_properties(mouse_absolute_position PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceMouse>")
endif()
endif()
if(BUILD_TESTING)
add_test_executable(mouse_tests
${CMAKE_CURRENT_SOURCE_DIR}/tests/Mouse.cpp
+6 -9
View File
@@ -82,19 +82,16 @@ int APS5_VABI sceMouseSetProcessPrivilege() {
return 0;
}
int APS5_VABI sceMouseReadAbsolutePosition() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num) {
return Mouse::ReadAbsolutePosition(handle, data, num);
}
int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode) {
return Mouse::SetAbsoluteHighPrecisionMode(handle, mode);
}
int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size) {
return Mouse::SetAbsoluteVirtualScreenSize(handle, size);
}
}
+4 -3
View File
@@ -1,6 +1,7 @@
#pragma once
#include "SceTypes.hpp"
#include "prx/libSceMouse/include/mouse_structs.h"
extern "C" {
@@ -19,8 +20,8 @@ int APS5_VABI sceMouseMbusInit();
int APS5_VABI sceMouseSetHandType();
int APS5_VABI sceMouseSetPointerSpeed();
int APS5_VABI sceMouseSetProcessPrivilege();
int APS5_VABI sceMouseReadAbsolutePosition();
int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode();
int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize();
int APS5_VABI sceMouseReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num);
int APS5_VABI sceMouseSetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode);
int APS5_VABI sceMouseSetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size);
}
@@ -6,6 +6,8 @@
struct MouseInputEvent {
std::int32_t x = 0;
std::int32_t y = 0;
std::int32_t countsX = 0;
std::int32_t countsY = 0;
std::int32_t wheel = 0;
std::int32_t tilt = 0;
std::uint32_t button = 0;
@@ -22,6 +24,9 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam
int Close(std::int32_t handle);
int Read(std::int32_t handle, MouseData* data, std::int32_t num);
int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y);
int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size);
int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode);
int ReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num);
void Publish(const MouseInputEvent& event);
}
@@ -10,6 +10,18 @@ struct MouseOpenParam {
std::uint8_t reserved[7];
};
struct MouseAbsoluteData {
std::uint64_t timestamp;
std::uint8_t connected;
std::uint8_t padding[3];
std::uint32_t buttons;
std::uint16_t x;
std::uint16_t y;
std::int32_t wheel;
std::int32_t tilt;
std::uint8_t reserved[12];
};
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);
@@ -20,6 +32,13 @@ constexpr int MOUSE_HANDLE = 1;
constexpr std::uint8_t MOUSE_OPEN_PARAM_MERGED = 1;
static_assert(sizeof(MouseOpenParam) == 8);
static_assert(sizeof(MouseAbsoluteData) == 40);
static_assert(offsetof(MouseAbsoluteData, connected) == 8);
static_assert(offsetof(MouseAbsoluteData, buttons) == 12);
static_assert(offsetof(MouseAbsoluteData, x) == 16);
static_assert(offsetof(MouseAbsoluteData, y) == 18);
static_assert(offsetof(MouseAbsoluteData, wheel) == 20);
static_assert(offsetof(MouseAbsoluteData, tilt) == 24);
static_assert(sizeof(MouseData) == 40);
static_assert(alignof(MouseData) == 8);
static_assert(offsetof(MouseData, timestamp) == 0);
+184 -2
View File
@@ -3,6 +3,8 @@
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <mutex>
namespace {
@@ -14,6 +16,74 @@ bool initialized = false;
bool opened = false;
bool connected = true;
std::uint32_t buttons = 0;
constexpr std::array<std::int64_t, 4> SCREEN_WIDTHS{1280, 1920, 2560, 3840};
constexpr std::array<std::int64_t, 4> SCREEN_HEIGHTS{720, 1080, 1440, 2160};
constexpr std::array<float, 5> CURVE_SPEEDS{0.0f, 0.4300079345703125f, 1.25f, 3.8600006103515625f, 40.0f};
constexpr std::array<float, 5> CURVE_MOVES{0.0f, 1.0702667236328125f, 4.140625f, 18.984375f, 443.75f};
struct AbsoluteSample {
std::uint64_t timestamp;
std::uint32_t buttons;
std::int32_t x;
std::int32_t y;
std::int32_t wheel;
std::int32_t tilt;
bool connected;
};
std::array<AbsoluteSample, MOUSE_MAX_DATA_NUM> absoluteHistory{};
int absoluteHead = 0;
int absoluteCount = 0;
std::uint32_t screenSize = 1;
std::int64_t absoluteX = 0;
std::int64_t absoluteY = 0;
std::uint8_t highPrecision = 0;
float fractionX = 0.0f;
float fractionY = 0.0f;
float curve(float speed) {
std::size_t segment = 1;
while (segment + 1 < CURVE_SPEEDS.size() && speed > CURVE_SPEEDS[segment]) ++segment;
const float lowSpeed = CURVE_SPEEDS[segment - 1];
const float lowMove = CURVE_MOVES[segment - 1];
return lowMove + (speed - lowSpeed) * (CURVE_MOVES[segment] - lowMove) / (CURVE_SPEEDS[segment] - lowSpeed);
}
std::int64_t step(float& fraction, float delta, float scale) {
const float total = delta * scale + fraction;
const float whole = std::trunc(total);
fraction = total - whole;
return static_cast<std::int64_t>(whole);
}
void moveAbsolute(std::int32_t dx, std::int32_t dy) {
std::int64_t moveX = dx;
std::int64_t moveY = dy;
if (highPrecision != 0) {
moveX = 0;
moveY = 0;
const float length = std::sqrt(static_cast<float>(dx) * static_cast<float>(dx) + static_cast<float>(dy) * static_cast<float>(dy));
if (length != 0.0f) {
const float scale = curve(length * 120.0f / 420.0f) * 96.0f / 120.0f / length;
moveX = step(fractionX, static_cast<float>(dx), scale);
moveY = step(fractionY, static_cast<float>(dy), scale);
}
}
absoluteX = std::clamp<std::int64_t>(absoluteX + moveX, 0, SCREEN_WIDTHS[screenSize] - 1);
absoluteY = std::clamp<std::int64_t>(absoluteY + moveY, 0, SCREEN_HEIGHTS[screenSize] - 1);
}
void enqueueAbsolute(const AbsoluteSample& sample) {
if (absoluteCount == MOUSE_MAX_DATA_NUM) {
absoluteHead = (absoluteHead + 1) % MOUSE_MAX_DATA_NUM;
--absoluteCount;
}
absoluteHistory[(absoluteHead + absoluteCount) % MOUSE_MAX_DATA_NUM] = sample;
++absoluteCount;
}
bool sharesRecord(const AbsoluteSample& first, const AbsoluteSample& second) {
return first.connected && second.connected && first.buttons == second.buttons;
}
void enqueue(const MouseData& data) {
if (count == MOUSE_MAX_DATA_NUM) {
@@ -23,6 +93,24 @@ void enqueue(const MouseData& data) {
history[(head + count) % MOUSE_MAX_DATA_NUM] = data;
++count;
}
bool motionOnly(const MouseData& data) {
return data.wheel == 0 && data.tilt == 0 && (data.x_axis != 0 || data.y_axis != 0);
}
std::int32_t saturatedSum(std::int32_t a, std::int32_t b) {
return static_cast<std::int32_t>(std::clamp<std::int64_t>(static_cast<std::int64_t>(a) + b, INT32_MIN, INT32_MAX));
}
bool mergeMotion(const MouseData& data) {
if (count == 0 || !motionOnly(data)) return false;
MouseData& tail = history[(head + count - 1) % MOUSE_MAX_DATA_NUM];
if (!motionOnly(tail) || tail.buttons != data.buttons || tail.connected != data.connected) return false;
tail.timestamp = data.timestamp;
tail.x_axis = saturatedSum(tail.x_axis, data.x_axis);
tail.y_axis = saturatedSum(tail.y_axis, data.y_axis);
return true;
}
}
namespace Mouse {
@@ -50,10 +138,19 @@ int Open(int userId, std::int32_t type, std::int32_t index, const MouseOpenParam
buttons = 0;
head = 0;
count = 0;
screenSize = 1;
absoluteX = 0;
absoluteY = 0;
highPrecision = 0;
fractionX = 0.0f;
fractionY = 0.0f;
MouseData initial{};
initial.timestamp = sceKernelGetProcessTime();
initial.connected = connected;
enqueue(initial);
absoluteHead = 0;
absoluteCount = 0;
enqueueAbsolute(AbsoluteSample{initial.timestamp, 0, 0, 0, 0, 0, connected});
return MOUSE_HANDLE;
}
@@ -65,6 +162,8 @@ int Close(std::int32_t handle) {
buttons = 0;
head = 0;
count = 0;
absoluteHead = 0;
absoluteCount = 0;
return MOUSE_OK;
}
@@ -82,11 +181,87 @@ int Read(std::int32_t handle, MouseData* data, std::int32_t num) {
return available;
}
int SetAbsoluteVirtualScreenSize(std::int32_t handle, std::int32_t size) {
std::lock_guard lock(mouseMutex);
if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED;
const auto next = static_cast<std::uint32_t>(size);
if (next >= SCREEN_WIDTHS.size()) return MOUSE_ERROR_INVALID_ARG;
if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE;
absoluteX = absoluteX * SCREEN_WIDTHS[next] / SCREEN_WIDTHS[screenSize];
absoluteY = absoluteY * SCREEN_HEIGHTS[next] / SCREEN_HEIGHTS[screenSize];
screenSize = next;
return MOUSE_OK;
}
int SetAbsoluteHighPrecisionMode(std::int32_t handle, std::int32_t mode) {
std::lock_guard lock(mouseMutex);
if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED;
if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE;
highPrecision = static_cast<std::uint8_t>(mode);
return MOUSE_OK;
}
int ReadAbsolutePosition(std::int32_t handle, MouseAbsoluteData* data, std::int32_t num) {
std::lock_guard lock(mouseMutex);
if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED;
if (data == nullptr || num <= 0 || num > MOUSE_MAX_DATA_NUM) return MOUSE_ERROR_INVALID_ARG;
if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE;
if (!connected) {
data[0] = MouseAbsoluteData{};
return 1;
}
std::array<AbsoluteSample, MOUSE_MAX_DATA_NUM> samples{};
std::array<bool, MOUSE_MAX_DATA_NUM> endsRecord{};
const int pending = absoluteCount;
for (int i = 0; i < pending; ++i) {
samples[i] = absoluteHistory[(absoluteHead + i) % MOUSE_MAX_DATA_NUM];
endsRecord[i] = true;
}
absoluteHead = 0;
absoluteCount = 0;
int records = pending;
for (int i = 0; i + 1 < pending && records > num; ++i) {
if (!sharesRecord(samples[i], samples[i + 1])) continue;
endsRecord[i] = false;
--records;
}
int first = pending;
for (int kept = 0; first > 0 && kept < num;) {
--first;
if (endsRecord[first]) ++kept;
while (first > 0 && !endsRecord[first - 1]) --first;
}
int written = 0;
std::int32_t wheel = 0;
std::int32_t tilt = 0;
for (int i = first; i < pending; ++i) {
const AbsoluteSample& sample = samples[i];
if (!sample.connected) continue;
moveAbsolute(sample.x, sample.y);
wheel += sample.wheel;
tilt += sample.tilt;
if (!endsRecord[i]) continue;
MouseAbsoluteData& record = data[written++];
record.timestamp = sample.timestamp;
record.connected = 1;
record.buttons = sample.buttons;
record.x = static_cast<std::uint16_t>(absoluteX);
record.y = static_cast<std::uint16_t>(absoluteY);
record.wheel = wheel;
record.tilt = tilt;
wheel = 0;
tilt = 0;
}
return written;
}
int ResetAbsolutePosition(std::int32_t handle, std::uint16_t x, std::uint16_t y) {
std::lock_guard lock(mouseMutex);
if (!initialized) return MOUSE_ERROR_NOT_INITIALIZED;
if (handle != MOUSE_HANDLE || !opened) return MOUSE_ERROR_INVALID_HANDLE;
if (x >= 1920 || y >= 1080) return MOUSE_ERROR_INVALID_ARG;
if (x >= SCREEN_WIDTHS[screenSize] || y >= SCREEN_HEIGHTS[screenSize]) return MOUSE_ERROR_INVALID_ARG;
absoluteX = x;
absoluteY = y;
return MOUSE_OK;
}
@@ -100,19 +275,26 @@ void Publish(const MouseInputEvent& event) {
return;
}
if (!opened) return;
const bool countsGiven = event.countsX != 0 || event.countsY != 0;
const bool noReport = event.button == 0 && !event.resetButtons && !event.connectionChange && event.wheel == 0 && event.tilt == 0;
if (noReport && event.x == 0 && event.y == 0 && !countsGiven) return;
if (event.resetButtons) buttons = 0;
if (event.button != 0) {
if (event.pressed) buttons |= event.button;
else buttons &= ~event.button;
}
const std::uint64_t timestamp = sceKernelGetProcessTime();
enqueueAbsolute(AbsoluteSample{timestamp, buttons, countsGiven ? event.countsX : event.x, countsGiven ? event.countsY : event.y, event.wheel, event.tilt, connected});
if (noReport && event.x == 0 && event.y == 0) return;
MouseData data{};
data.timestamp = sceKernelGetProcessTime();
data.timestamp = timestamp;
data.connected = connected;
data.buttons = buttons;
data.x_axis = event.x;
data.y_axis = event.y;
data.wheel = event.wheel;
data.tilt = event.tilt;
if (event.button == 0 && !event.resetButtons && mergeMotion(data)) return;
enqueue(data);
}
+109 -3
View File
@@ -5,7 +5,9 @@
#include "SDL_events.h"
#include "SDL_mouse.h"
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
extern "C" {
bool MouseIsOpen_nid_no_patch();
@@ -14,6 +16,18 @@ bool MouseIsOpen_nid_no_patch();
static void Require(bool value) { if (!value) std::abort(); }
struct AbsoluteRecord {
std::uint64_t timestamp;
std::uint8_t connected;
std::uint8_t padding[3];
std::uint32_t buttons;
std::uint16_t x;
std::uint16_t y;
std::int32_t wheel;
std::int32_t tilt;
std::uint8_t reserved[12];
};
int main() {
MouseData data[64]{};
Require(!MouseIsOpen_nid_no_patch());
@@ -97,13 +111,87 @@ int main() {
input.HandleEvent(event, 7);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].connected);
for (int i = 0; i < 70; ++i) {
for (int i = 0; i < 300; ++i) {
MouseInputEvent motion{};
motion.x = i;
motion.x = -1;
motion.y = i % 2;
MousePublishInput_nid_no_patch(motion);
}
Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1);
Require(data[0].x_axis == -300 && data[0].y_axis == 150 && data[0].buttons == 0);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0);
MouseInputEvent still{};
MousePublishInput_nid_no_patch(still);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0);
for (int i = 0; i < 300; ++i) {
MouseInputEvent alternating{};
alternating.x = i % 2;
MousePublishInput_nid_no_patch(alternating);
}
Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1);
Require(data[0].x_axis == 150 && data[0].y_axis == 0);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0);
MouseInputEvent zero{};
MouseInputEvent two{};
two.x = 2;
MouseInputEvent three{};
three.x = 3;
MouseInputEvent down{};
down.button = 1;
down.pressed = true;
MouseInputEvent up{};
up.button = 1;
for (const MouseInputEvent& report : {two, zero, down, zero, three, zero, up, zero}) MousePublishInput_nid_no_patch(report);
Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 4);
Require(data[0].x_axis == 2 && data[0].buttons == 0);
Require(data[1].x_axis == 0 && data[1].buttons == 1);
Require(data[2].x_axis == 3 && data[2].buttons == 1);
Require(data[3].x_axis == 0 && data[3].buttons == 0);
MouseInputEvent step{};
step.x = 3;
MousePublishInput_nid_no_patch(step);
MousePublishInput_nid_no_patch(step);
MouseInputEvent press{};
press.button = 1;
press.pressed = true;
MousePublishInput_nid_no_patch(press);
MousePublishInput_nid_no_patch(step);
MousePublishInput_nid_no_patch(step);
MouseInputEvent wheel{};
wheel.wheel = 1;
MousePublishInput_nid_no_patch(wheel);
MousePublishInput_nid_no_patch(step);
MouseInputEvent release{};
release.button = 1;
MousePublishInput_nid_no_patch(release);
MousePublishInput_nid_no_patch(step);
Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 7);
Require(data[0].x_axis == 6 && data[0].buttons == 0);
Require(data[1].x_axis == 0 && data[1].buttons == 1);
Require(data[2].x_axis == 6 && data[2].buttons == 1);
Require(data[3].wheel == 1 && data[3].x_axis == 0 && data[3].buttons == 1);
Require(data[4].x_axis == 3 && data[4].wheel == 0 && data[4].buttons == 1);
Require(data[5].x_axis == 0 && data[5].buttons == 0);
Require(data[6].x_axis == 3 && data[6].buttons == 0);
Require(data[2].timestamp >= data[1].timestamp && data[6].timestamp >= data[5].timestamp);
MouseInputEvent large{};
large.x = INT32_MAX;
MousePublishInput_nid_no_patch(large);
MousePublishInput_nid_no_patch(large);
Require(sceMouseRead(MOUSE_HANDLE, data, 8) == 1 && data[0].x_axis == INT32_MAX);
for (int i = 0; i < 70; ++i) {
MouseInputEvent click{};
click.button = 1;
click.pressed = (i % 2) == 0;
MousePublishInput_nid_no_patch(click);
}
Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 64);
Require(data[0].x_axis == 6 && data[63].x_axis == 69);
Require(data[0].buttons == 1 && data[63].buttons == 0 && data[62].buttons == 1);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 0);
Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK);
Require(!MouseIsOpen_nid_no_patch());
@@ -119,4 +207,22 @@ int main() {
Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK);
Require(!MouseIsOpen_nid_no_patch());
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 959, 539) == MOUSE_ERROR_INVALID_HANDLE);
using ReadAbsolute = int (APS5_VABI*)(std::int32_t, AbsoluteRecord*, std::int32_t);
const auto readAbsolute = reinterpret_cast<ReadAbsolute>(reinterpret_cast<void (*)()>(&sceMouseReadAbsolutePosition));
AbsoluteRecord absolute[MOUSE_MAX_DATA_NUM]{};
Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE);
Require(sceMouseRead(MOUSE_HANDLE, data, 64) == 1);
Require(readAbsolute(MOUSE_HANDLE, absolute, MOUSE_MAX_DATA_NUM) == 1);
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK);
input.SetPresentation(3840, 2160, 1920, 1080);
event = {};
event.type = SDL_MOUSEMOTION;
event.motion.windowID = 7;
event.motion.xrel = 10;
event.motion.yrel = 5;
input.HandleEvent(event, 7);
Require(sceMouseRead(MOUSE_HANDLE, data, 1) == 1 && data[0].x_axis == 20 && data[0].y_axis == 10);
Require(readAbsolute(MOUSE_HANDLE, absolute, 1) == 1 && absolute[0].x == 110 && absolute[0].y == 105);
Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK);
}
@@ -0,0 +1,193 @@
#include "prx/libSceMouse/include/Api.hpp"
#include "prx/libSceMouse/include/MouseState.hpp"
#include "prx/libSceMouse/include/mouse_structs.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
namespace {
struct AbsoluteRecord {
std::uint64_t timestamp;
std::uint8_t connected;
std::uint8_t padding[3];
std::uint32_t buttons;
std::uint16_t x;
std::uint16_t y;
std::int32_t wheel;
std::int32_t tilt;
std::uint8_t reserved[12];
};
static_assert(sizeof(AbsoluteRecord) == 40);
using SetValueFn = int (APS5_VABI*)(std::int32_t, std::int32_t);
using ReadAbsoluteFn = int (APS5_VABI*)(std::int32_t, AbsoluteRecord*, std::int32_t);
template <typename Function>
Function Resolve(const char* name) {
#ifdef _WIN32
void* symbol = reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("libSceMouse.prx"), name));
#else
void* symbol = dlsym(RTLD_DEFAULT, name);
#endif
if (!symbol) {
std::fprintf(stderr, "%s is not exported by libSceMouse\n", name);
std::abort();
}
return reinterpret_cast<Function>(symbol);
}
void Require(bool value, const char* what) {
if (value) return;
std::fprintf(stderr, "mouse absolute position: %s\n", what);
std::abort();
}
void Motion(std::int32_t x, std::int32_t y) {
MouseInputEvent event{};
event.x = x;
event.y = y;
MousePublishInput_nid_no_patch(event);
}
void Button(std::uint32_t button, bool pressed) {
MouseInputEvent event{};
event.button = button;
event.pressed = pressed;
MousePublishInput_nid_no_patch(event);
}
}
int main() {
const auto setSize = Resolve<SetValueFn>("sceMouseSetAbsoluteVirtualScreenSize");
const auto setMode = Resolve<SetValueFn>("sceMouseSetAbsoluteHighPrecisionMode");
const auto readAbsolute = Resolve<ReadAbsoluteFn>("sceMouseReadAbsolutePosition");
AbsoluteRecord records[64];
Require(setSize(MOUSE_HANDLE, 9) == MOUSE_ERROR_NOT_INITIALIZED, "the screen size before initialization did not return NOT_INITIALIZED first");
Require(setMode(MOUSE_HANDLE, 1) == MOUSE_ERROR_NOT_INITIALIZED, "the precision mode before initialization did not return NOT_INITIALIZED");
Require(readAbsolute(MOUSE_HANDLE, nullptr, 0) == MOUSE_ERROR_NOT_INITIALIZED, "a read before initialization did not return NOT_INITIALIZED first");
Require(sceMouseInit() == MOUSE_OK, "initialization failed");
Require(setSize(MOUSE_HANDLE, 4) == MOUSE_ERROR_INVALID_ARG && setSize(MOUSE_HANDLE, -1) == MOUSE_ERROR_INVALID_ARG, "a size past 3 was not refused before the handle");
Require(setSize(MOUSE_HANDLE, 1) == MOUSE_ERROR_INVALID_HANDLE, "the screen size of a closed handle was set");
Require(setMode(MOUSE_HANDLE, 1) == MOUSE_ERROR_INVALID_HANDLE, "the precision mode of a closed handle was set");
Require(readAbsolute(MOUSE_HANDLE, nullptr, 1) == MOUSE_ERROR_INVALID_ARG && readAbsolute(MOUSE_HANDLE, records, 0) == MOUSE_ERROR_INVALID_ARG && readAbsolute(MOUSE_HANDLE, records, 65) == MOUSE_ERROR_INVALID_ARG, "a bad read was not refused before the handle");
Require(readAbsolute(MOUSE_HANDLE, records, 1) == MOUSE_ERROR_INVALID_HANDLE, "a closed handle was read");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 5000, 0) == MOUSE_ERROR_INVALID_HANDLE, "a reset with a bad handle and a bad position did not return INVALID_HANDLE");
Require(sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE, "open failed");
std::memset(records, 0xA5, sizeof(records));
Require(readAbsolute(MOUSE_HANDLE, records, 64) == 1, "the sample queued at open was not read");
Require(records[0].connected == 1 && records[0].x == 0 && records[0].y == 0, "the position does not start at the top-left corner");
Require(records[0].padding[0] == 0xA5 && records[0].padding[2] == 0xA5 && records[0].reserved[0] == 0xA5 && records[0].reserved[11] == 0xA5, "bytes the console leaves alone were written");
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 0, "a read with no samples returned a record");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1920, 0) == MOUSE_ERROR_INVALID_ARG, "x 1920 was taken at 1920x1080");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1000, 500) == MOUSE_OK, "the reset failed");
Require(setSize(MOUSE_HANDLE, 3) == MOUSE_OK, "the size 3840x2160 was refused");
Button(1, true);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 2000 && records[0].y == 1000 && records[0].buttons == 1, "the position was not scaled to 3840x2160");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 3839, 2159) == MOUSE_OK, "the corner of 3840x2160 was refused");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 2000, 1000) == MOUSE_OK, "the reset at 3840x2160 failed");
Require(setSize(MOUSE_HANDLE, 0) == MOUSE_OK, "the size 1280x720 was refused");
Button(1, false);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 666 && records[0].y == 333 && records[0].buttons == 0, "the position was not scaled to 1280x720 with truncation");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 1280, 0) == MOUSE_ERROR_INVALID_ARG, "x 1280 was taken at 1280x720");
Require(setSize(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "returning to 1920x1080 failed");
Motion(5, -3);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 105 && records[0].y == 97, "motion did not move the position one for one");
Motion(-5000, 5000);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 0 && records[0].y == 1079, "the position was not clamped to the screen");
MouseInputEvent wheel{};
wheel.wheel = 2;
wheel.tilt = -1;
MousePublishInput_nid_no_patch(wheel);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].wheel == 2 && records[0].tilt == -1, "the wheel and tilt were not copied");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the reset before the merge checks failed");
Motion(1, 0);
Motion(2, 0);
Motion(4, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 3) == 3 && records[0].x == 101 && records[1].x == 103 && records[2].x == 107, "three samples read with num 3 did not give one record each");
Motion(1, 0);
Motion(2, 0);
Motion(4, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 114, "three samples read with num 1 did not share one record with all their motion");
Require(setMode(MOUSE_HANDLE, 0x100) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the precision mode was refused");
Motion(1, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 101, "mode 0x100 did not keep its low byte 0, which is off");
Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed");
Motion(1, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 100, "a delta of 1 moved a whole pixel in high precision");
Motion(1, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 101, "the kept fraction was not added to the next delta");
Motion(10, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 111, "a delta of 10 did not follow the curve");
Motion(100, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 359 && records[0].y == 100, "a delta of 100 did not follow the curve");
MouseData relative[64];
const auto reopen = [&](const char* what) {
Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK && sceMouseOpen(1, 0, 0, nullptr) == MOUSE_HANDLE, what);
Require(readAbsolute(MOUSE_HANDLE, records, 64) == 1 && sceMouseRead(MOUSE_HANDLE, relative, 64) == 1, what);
};
reopen("reopening for the queued precision check failed");
Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed");
for (int i = 0; i < 10; ++i) Motion(1, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 105, "ten queued deltas of 1 did not each go through the curve");
reopen("reopening for the incremental precision check failed");
Require(setMode(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "turning high precision on failed");
for (int i = 0; i < 10; ++i) {
Motion(1, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1, "a delta read on its own gave no record");
}
Require(records[0].x == 105, "ten deltas of 1 read one at a time did not end where the queued ones did");
Require(setMode(MOUSE_HANDLE, 0) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 0, 100) == MOUSE_OK, "the edge reset failed");
Motion(-5, 0);
Motion(5, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 5, "queued motion at the left edge was not clamped per sample");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 0, 100) == MOUSE_OK, "the edge reset failed");
Motion(-5, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 0, "motion past the left edge was not clamped");
Motion(5, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 5, "motion read one at a time at the edge did not end where the queued motion did");
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "the reset before the size check failed");
Motion(10, 0);
Require(setSize(MOUSE_HANDLE, 3) == MOUSE_OK && readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 210 && records[0].y == 200, "a queued sample was not applied at the size in force when it was read");
Require(setSize(MOUSE_HANDLE, 1) == MOUSE_OK && sceMouseResetAbsolutePosition(MOUSE_HANDLE, 100, 100) == MOUSE_OK, "returning to 1920x1080 failed");
Motion(10, 0);
Require(sceMouseResetAbsolutePosition(MOUSE_HANDLE, 50, 50) == MOUSE_OK && readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 60 && records[0].y == 50, "a queued sample did not move the position a later reset set");
Require(sceMouseRead(MOUSE_HANDLE, relative, 64) >= 0, "draining sceMouseRead failed");
Motion(7, 0);
Require(sceMouseRead(MOUSE_HANDLE, relative, 1) == 1 && relative[0].x_axis == 7, "sceMouseRead did not get the motion");
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 67, "a relative read took the sample from the absolute reads");
Motion(3, 0);
Require(readAbsolute(MOUSE_HANDLE, records, 1) == 1 && records[0].x == 70, "the absolute read missed the motion");
Require(sceMouseRead(MOUSE_HANDLE, relative, 1) == 1 && relative[0].x_axis == 3, "an absolute read took the sample from sceMouseRead");
MouseInputEvent disconnect{};
disconnect.connectionChange = true;
disconnect.connected = false;
MousePublishInput_nid_no_patch(disconnect);
std::memset(records, 0xA5, sizeof(records));
Require(readAbsolute(MOUSE_HANDLE, records, 4) == 1, "a disconnected mouse did not return one record");
const AbsoluteRecord zero{};
Require(std::memcmp(&records[0], &zero, sizeof(zero)) == 0 && records[1].connected == 0xA5, "a disconnected mouse did not return one zeroed record");
Require(sceMouseClose(MOUSE_HANDLE) == MOUSE_OK, "close failed");
Require(readAbsolute(MOUSE_HANDLE, records, 1) == MOUSE_ERROR_INVALID_HANDLE, "a closed handle was read");
std::puts("mouse absolute position tests passed");
return 0;
}
@@ -14,6 +14,8 @@ void MouseInput::HandleEvent(const SDL_Event& event, unsigned windowId) {
if (event.motion.windowID != windowId || !focused || ModalDialogCapturesInput_nid_postfix()) return;
input.x = scaled(event.motion.xrel, scaleX, remainderX);
input.y = scaled(event.motion.yrel, scaleY, remainderY);
input.countsX = event.motion.xrel;
input.countsY = event.motion.yrel;
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
+3 -2
View File
@@ -21,8 +21,9 @@
- [sceMouseDisconnectAll, sceMouseVirtualDeviceAddDevice, sceMouseVirtualDeviceDeleteDevice and sceMouseVirtualDeviceGetCursor](../../../core/libs/prx/libSceMouse/InventoryExports.cpp) - the names and NIDs are recorded in the [13.60 export inventory](https://github.com/xdCloudy/PS5-13.60-Function-Stub-Library/blob/e495d3cf24aea17cefda59decdfda9e3e4e3e5ff/data/firmware-13.60/libscemouse-acdd5b5e23.jsonl). Signatures and behavior remain unknown; the parameterless `int` declarations only resolve imports and every call throws. Not measured on a console.
- [sceMouseConnectPort, sceMouseDebugGetDeviceId, sceMouseDeviceOpen, sceMouseDisconnectDevice, sceMouseDisconnectPort, sceMouseGetDeviceInfo, sceMouseMbusInit, sceMouseSetHandType, sceMouseSetPointerSpeed and sceMouseSetProcessPrivilege](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - names and NIDs are present in the [PS5 3.20 export inventory](https://github.com/DNNDHH/PS5-3.20_Libs/blob/b2a77c09cd71c01f34c790cfbe98c751a13262ad/libSceMouse.c); the no-argument prototypes follow [shadPS4's PS4 stubs](https://github.com/shadps4-emu/shadPS4/blob/f68923fe807f657348e6719ce658bcbd0a2b1c72/src/core/libraries/mouse/mouse.cpp#L34-L146), not measured on a console. These declarations have no parameters solely to resolve known imports and make no ABI claim.
- [sceMouseReadAbsolutePosition, sceMouseSetAbsoluteHighPrecisionMode and sceMouseSetAbsoluteVirtualScreenSize](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - the names match unresolved NIDs reported by a title, but their signatures and behavior are not verified. These no-argument declarations only resolve imports; the exports throw if called.
### Functional
- [sceMouseResetAbsolutePosition](../../../core/libs/prx/libSceMouse/Export.cpp) (libSceMouse) - the position is not stored because the host reports relative motion only. Native input widths and validation are confirmed, including the default 1920 by 1080 bounds; other native coordinate modes and absolute reports remain unimplemented
- [Publish](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - consecutive motion-only reports with the same buttons are summed into the newest unread entry, so a title that reads a few entries per frame gets current motion instead of a full queue of old reports. Reports with no motion and no button, wheel, tilt or connection change are dropped. Per-report granularity and the console's own report rate are not reproduced
- [sceMouseResetAbsolutePosition, sceMouseSetAbsoluteVirtualScreenSize, sceMouseSetAbsoluteHighPrecisionMode, sceMouseReadAbsolutePosition](../../../core/libs/prx/libSceMouse/src/mouse_impl.cpp) (libSceMouse) - follow firmware 13.42 [run natively](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6502712241) with the driver stubbed: checks and order, screen sizes and rescale, the mode byte, the curve with its fraction, the 40-byte record. Host choices, after [XatiX's title-tested version](https://github.com/boykopovar/AnyPS5/issues/11477#issuecomment-6503782633): each host event is one sample with unscaled counts, in its own queue, so `sceMouseRead`'s motion merging does not feed the curve. A read takes all pending samples; past `num`, same-button neighbours share a record, each still moving the pointer, and the last `num` records are kept. Speed is taken as 4, the hand swap and flagged bit never apply, and a disconnected mouse stands in for a zero driver status