diff --git a/core/libs/prx/libScePad/include/InputCommandFile.hpp b/core/libs/prx/libScePad/include/InputCommandFile.hpp index a4a1c4d405..5ee3b1fb73 100644 --- a/core/libs/prx/libScePad/include/InputCommandFile.hpp +++ b/core/libs/prx/libScePad/include/InputCommandFile.hpp @@ -16,6 +16,8 @@ public: std::uint32_t buttons = 0; bool hasSticks = false; std::array sticks{}; + bool touchLeft = false; + bool touchRight = false; bool operator==(const Input&) const = default; }; @@ -65,10 +67,12 @@ private: const auto parsed = std::from_chars(first, last, values[i]); if (first == last || parsed.ec != std::errc{} || parsed.ptr != last) return {}; } - if (values[0] == 0 || (values[1] & ~0x10fffeull) != 0 || values[2] == 0 || values[2] > 5000) return {}; + if (values[0] == 0 || (values[1] & ~(0x10fffeull | TouchLeftBit | TouchRightBit)) != 0 || values[2] == 0 || values[2] > 5000) return {}; Command command; command.sequence = values[0]; - command.input.buttons = static_cast(values[1]); + command.input.buttons = static_cast(values[1] & 0x10fffeull); + command.input.touchLeft = (values[1] & TouchLeftBit) != 0; + command.input.touchRight = (values[1] & TouchRightBit) != 0; command.durationMs = static_cast(values[2]); for (int i = 3; i < count; ++i) { if (values[i] > 255) return {}; @@ -78,6 +82,9 @@ private: return command; } + static constexpr std::uint64_t TouchLeftBit = 0x200000; + static constexpr std::uint64_t TouchRightBit = 0x400000; + std::filesystem::path path; std::uint64_t lastSequence = 0; std::uint64_t nextPoll = 0; diff --git a/core/libs/prx/libScePad/src/PadState.cpp b/core/libs/prx/libScePad/src/PadState.cpp index e8446a411c..21f63c8232 100644 --- a/core/libs/prx/libScePad/src/PadState.cpp +++ b/core/libs/prx/libScePad/src/PadState.cpp @@ -179,10 +179,10 @@ PadData BuildData(const PadInputState& state, std::uint64_t changeTime, std::uin } ++touchNum; } - if (touchNum == 0 && (state.touchLeft || state.touchRight)) { + if (touchNum == 0 && (state.touchLeft || state.touchRight || injected.touchLeft || injected.touchRight)) { data.buttons |= 0x100000; touchNum = 1; - data.touch_data_touch0_x = state.touchRight ? 1440 : 480; + data.touch_data_touch0_x = state.touchRight || injected.touchRight ? 1440 : 480; data.touch_data_touch0_y = 471; data.touch_data_touch0_id = 0; } diff --git a/core/libs/tests/GuestPad.cpp b/core/libs/tests/GuestPad.cpp index 2392d45159..84b0ad50ab 100644 --- a/core/libs/tests/GuestPad.cpp +++ b/core/libs/tests/GuestPad.cpp @@ -10,6 +10,7 @@ #include #include #include +#include extern "C" { int APS5_VABI scePadOpen_nid_postfix(int, int, int, const void*); @@ -96,8 +97,16 @@ static void CheckInputCommands() { write("7 8192 300 1 2 3 4"); const auto accepted = commands.Poll(2601000); Require(accepted.buttons == 0x2000 && accepted.hasSticks && accepted.sticks[3] == 4); + write("8 2097152 300"); + const auto left = commands.Poll(3001000); + Require(left.buttons == 0 && left.touchLeft && !left.touchRight && !left.hasSticks); + write("9 4210688 300"); + const auto right = commands.Poll(3401000); + Require(right.buttons == 0x4000 && !right.touchLeft && right.touchRight); + write("10 8388608 300"); + Require(commands.Poll(3801000) == none); std::filesystem::remove(path); - Require(commands.Buttons(3001000) == 0); + Require(commands.Buttons(4201000) == 0); Pad::InputCommandFile disabled(std::filesystem::path{}); Require(disabled.Buttons(0) == 0); } @@ -248,6 +257,21 @@ static void CheckInjectedState(const std::filesystem::path& path) { const auto centred = Pad::ReadState(); Require(centred.left_stick_x == 42 && centred.right_stick_y == 17); Require(centred.timestamp > moved.timestamp); + for (const auto& [command, x] : {std::pair{"3 2097152 300\n", 480}, std::pair{"4 4194304 300\n", 1440}}) { + std::ofstream(path) << command; + PadData touched{}; + const auto touchDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + do { + touched = Pad::ReadState(); + if (touched.touch_data_touch_num != 0) break; + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } while (std::chrono::steady_clock::now() < touchDeadline); + Require(touched.touch_data_touch_num == 1 && touched.touch_data_touch0_x == x && touched.touch_data_touch0_y == 471); + Require(touched.buttons == (0x2000 | 0x100000)); + std::this_thread::sleep_for(std::chrono::milliseconds(310)); + const auto lifted = Pad::ReadState(); + Require(lifted.touch_data_touch_num == 0 && lifted.buttons == 0x2000); + } PadPublishInput_nid_postfix(PadInputState{}); } diff --git a/docs/user/USAGE.md b/docs/user/USAGE.md index 18996014a7..cecd8d1d3c 100644 --- a/docs/user/USAGE.md +++ b/docs/user/USAGE.md @@ -144,9 +144,10 @@ python3 tools/pad_input.py /tmp/anyps5-pad down --hold-ms 500 python3 tools/pad_input.py /tmp/anyps5-pad release python3 tools/pad_input.py /tmp/anyps5-pad release --left-stick 128 0 --hold-ms 2000 python3 tools/pad_input.py /tmp/anyps5-pad cross --left-stick 255 128 --right-stick 0 128 +python3 tools/pad_input.py /tmp/anyps5-pad touch-right ``` -Run the helper in a second terminal. Commands are polled at most every 100 ms during controller reads, and their buttons add to physical controller input. A press lasts 300 ms by default, starting when the application reads it, and releases automatically; `--hold-ms` accepts 1–5000 ms. `--left-stick X Y` and `--right-stick X Y` hold the sticks for the same time: each axis is 0-255, 128 is the centre, X grows to the right and Y grows downward. While such a command is active both sticks come from it instead of the physical controller, and a stick that was not named stays centred. Wait for the application to observe a release before sending another press. The file holds only the latest command; rapid commands can replace one another. On POSIX, replacing an existing command file retains its permission mode; a privileged helper also retains its owner and group so the game user can still read it. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled. +Run the helper in a second terminal. Commands are polled at most every 100 ms during controller reads, and their buttons add to physical controller input. A press lasts 300 ms by default, starting when the application reads it, and releases automatically; `--hold-ms` accepts 1–5000 ms. `--left-stick X Y` and `--right-stick X Y` hold the sticks for the same time: each axis is 0-255, 128 is the centre, X grows to the right and Y grows downward. While such a command is active both sticks come from it instead of the physical controller, and a stick that was not named stays centred. `touch-left` and `touch-right` press the touchpad with a finger on its left or right half, as the `TouchLeft` and `TouchRight` keys do; in the command file they are bits `0x200000` and `0x400000` of the button field. Wait for the application to observe a release before sending another press. The file holds only the latest command; rapid commands can replace one another. On POSIX, replacing an existing command file retains its permission mode; a privileged helper also retains its owner and group so the game user can still read it. Commands left over when the application first reads the controller are ignored. Without the environment variable, file input is disabled. ### System fonts diff --git a/tools/pad_input.py b/tools/pad_input.py index 38522df145..a83c7a1116 100644 --- a/tools/pad_input.py +++ b/tools/pad_input.py @@ -12,7 +12,8 @@ BUTTONS = { "up": 0x10, "right": 0x20, "down": 0x40, "left": 0x80, "l2": 0x100, "r2": 0x200, "l1": 0x400, "r1": 0x800, "triangle": 0x1000, "circle": 0x2000, "cross": 0x4000, - "square": 0x8000, "touchpad": 0x100000, "release": 0, + "square": 0x8000, "touchpad": 0x100000, + "touch-left": 0x200000, "touch-right": 0x400000, "release": 0, } @@ -32,7 +33,7 @@ def previous_sequence(path): values = [int(token) for token in tokens] if any(value > 2**64 - 1 for value in values): return 0 - if values[0] == 0 or values[1] & ~0x10fffe or not 1 <= values[2] <= 5000: + if values[0] == 0 or values[1] & ~0x70fffe or not 1 <= values[2] <= 5000: return 0 if any(axis > 255 for axis in values[3:]): return 0 diff --git a/tools/tests/test_pad_input.py b/tools/tests/test_pad_input.py index 38c9514571..01b48a9e68 100644 --- a/tools/tests/test_pad_input.py +++ b/tools/tests/test_pad_input.py @@ -77,6 +77,10 @@ class PadInputTests(unittest.TestCase): self.assertEqual(self.queue(2000, "release", "--right-stick", "0", "255"), [2000, 0, 300, 128, 128, 0, 255]) + def test_touchpad_halves_queue_their_bits_and_preserve_sequence(self): + self.assertEqual(self.queue(1000, "touch-left"), [1000, 0x200000, 300]) + self.assertEqual(self.queue(1000, "touch-right"), [1001, 0x400000, 300]) + def test_valid_stick_command_and_unsigned_tokens_preserve_sequence(self): self.path.write_text("+2000 +16384 +300 +0 +255 +128 +128\n", encoding="utf-8") self.assertEqual(self.queue(1000, "release"), [2001, 0, 300])