mirror of
https://github.com/MrLowPr/overdrive.git
synced 2026-10-17 12:01:07 +02:00
fix: remove usb keyboard support
This commit is contained in:
1 parent
b58946c506
commit
13fb1dbdb1
12 files changed
+8
-383
No files matched your search
@@ -257,7 +257,7 @@ that creates its tag, and installed apps offer exactly the tested files on their
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Changes to upstream ps5-react</b></summary>
|
||||
<summary><b>Fixes over upstream ps5-react</b></summary>
|
||||
<br>
|
||||
|
||||
Two fixes in the vendored framework, both in the build tooling and both reported by a Linux build:
|
||||
@@ -269,13 +269,6 @@ Two fixes in the vendored framework, both in the build tooling and both reported
|
||||
which reads zstd only from Python 3.14 while the documented minimum is 3.12. It now falls back
|
||||
to the `zstd` command.
|
||||
|
||||
One addition, for Overdrive's text fields:
|
||||
|
||||
- **USB keyboard.** `ps5-react/native/ps5/usb_keyboard.cpp` reads a keyboard plugged into the console
|
||||
through libSceKeyboard and hands typed characters (US layout) and Backspace, Enter, Escape and Tab to
|
||||
the new `useKeyboardInput` hook (input protocol v6, see `ps5-react/docs/NAVIGATION.md`). Without a
|
||||
keyboard nothing changes. It has no hardware evidence yet.
|
||||
|
||||
Nothing else in the framework is changed.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -273,13 +273,6 @@ Linux:
|
||||
`tarfile`, а тот читает zstd только начиная с Python 3.14, тогда как документированный минимум —
|
||||
3.12. Теперь есть откат на команду `zstd`.
|
||||
|
||||
Одно дополнение для текстовых полей Overdrive:
|
||||
|
||||
- **USB-клавиатура.** `ps5-react/native/ps5/usb_keyboard.cpp` читает клавиатуру, подключённую к консоли,
|
||||
через libSceKeyboard и передаёт набранные символы (раскладка US), а также Backspace, Enter, Escape и Tab
|
||||
новому хуку `useKeyboardInput` (протокол ввода v6, см. `ps5-react/docs/NAVIGATION.md`). Без клавиатуры
|
||||
ничего не меняется. На консоли пока не проверено.
|
||||
|
||||
Больше во фреймворке ничего не изменено.
|
||||
|
||||
</details>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// ../pub-game-store/native/search/Keyboard.jsx
|
||||
// reconstructed from the esbuild bundle in eboot.bin
|
||||
// MODIFIED: a USB keyboard types into the field too.
|
||||
|
||||
import '../assets/Inter-Regular.ttf';
|
||||
import { Fragment, jsx, jsxs } from 'react/jsx-runtime';
|
||||
@@ -11,7 +10,7 @@ import { ButtonGlyph } from '../ui/ButtonHints.jsx';
|
||||
import { Icon } from '../ui/Icon.jsx';
|
||||
import { HINT, useHints } from '../ui/Legend.jsx';
|
||||
import { colors } from '../ui/colors.js';
|
||||
import { FocusScope, Text, View, motion, useIsFocused, useKeyboardInput } from '@ps5-react/core';
|
||||
import { FocusScope, Text, View, motion, useIsFocused } from '@ps5-react/core';
|
||||
|
||||
var COLUMNS2 = 6;
|
||||
var LTR2 = { direction: "ltr" };
|
||||
@@ -57,13 +56,6 @@ import { FocusScope, Text, View, motion, useIsFocused, useKeyboardInput } from '
|
||||
(value) => value === "case" ? setUpper((value2) => !value2) : value === "done" ? onSubmit() : onChange((query) => edit(query, value, url ? 8192 : MAX_QUERY)),
|
||||
[onChange, onSubmit, url]
|
||||
);
|
||||
// A USB keyboard types straight into the field: characters as if their keys were pressed here,
|
||||
// Backspace deletes and Enter submits. The on-screen keys and the controller work as before.
|
||||
useKeyboardInput((input) => {
|
||||
if (input.type === "text") for (const character of input.value) onKey(character);
|
||||
else if (input.value === "Backspace") onKey("delete");
|
||||
else if (input.value === "Enter") onKey("done");
|
||||
});
|
||||
const onAction = (action) => {
|
||||
if (action === "square") onKey("delete");
|
||||
else if (action === "triangle" && !url) onKey(" ");
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased — USB keyboard
|
||||
|
||||
- The PS5 host reads a USB keyboard through libSceKeyboard (input protocol v6): typed characters, on a
|
||||
US layout, and the editing keys Backspace, Enter, Escape and Tab, with hold-to-repeat, reach the new
|
||||
`useKeyboardInput(handler)` as `{type: 'text' | 'key', value}`. They never move focus. Without a
|
||||
keyboard, or if the console refuses one, nothing is sent and the title runs as before; the log says
|
||||
which. The desktop host keeps its keys for the controller bindings. Not hardware-tested yet.
|
||||
|
||||
## Unreleased — comparing upscalers
|
||||
|
||||
- `<Video compare={{upscale, split}}>` (native ABI v15, `video.setCompare`): right of `split` (0 to 1
|
||||
|
||||
@@ -60,7 +60,7 @@ AppRegistry.registerComponent('menu', () => Menu);
|
||||
`npm run create` scaffolds this menu; `apps/starter`, `apps/tailwind-gallery`,
|
||||
`apps/system-explorer`, and `apps/motion-lab` use the patterns below.
|
||||
|
||||
## Host input (input protocol v6)
|
||||
## Host input (input protocol v5)
|
||||
|
||||
The input protocol is versioned on its own; it is not the native API's ABI
|
||||
version ([NATIVE-API.md](NATIVE-API.md#contract)).
|
||||
@@ -81,17 +81,6 @@ with its position 0..1 across and down the pad each frame it touches, and `__ps5
|
||||
once when it lifts (the PS5 reads the DualSense; the desktop, a controller's touchpad through SDL).
|
||||
See [Touchpad](#touchpad).
|
||||
|
||||
Since input protocol v6, the PS5 host also reads a USB keyboard: `__ps5ReactKeyboard('text', characters)`
|
||||
for what was typed and `__ps5ReactKeyboard('key', name)` for `Backspace`, `Enter`, `Escape` and `Tab`, with
|
||||
the platform's hold-to-repeat. Characters follow the US layout, since the console reports key positions,
|
||||
not the layout's characters. These reach only `useKeyboardInput` handlers, never the focus manager, so
|
||||
typing fills a field without moving focus. The desktop host keeps its keys for the controller bindings
|
||||
and reports no keyboard input. The title does not load libSceKeyboard, and a fake-signed title may not
|
||||
load it by path, so `main` has the system load it by id (`sceSysmoduleLoadModule(0x0106)`, before filesystem
|
||||
elevation) and its calls come from `sceKernelDlsym`; where any step fails the keyboard stays off and the
|
||||
log says which (`keyboard: module 0x0106 load=… handle=…`). A keyboard that isn't there at launch is tried
|
||||
again every three seconds. The keyboard has no hardware evidence yet ([HARDWARE.md](HARDWARE.md)).
|
||||
|
||||
The focus manager does not move on these buttons: they go to the enclosing
|
||||
`FocusScope.onAction` handlers (tabs on L1/R1, a filter on Triangle, settings on Options)
|
||||
and to `useController`.
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#include "video_api.hpp"
|
||||
#include "layouts.h"
|
||||
#include "video_output.hpp"
|
||||
#include "usb_keyboard.hpp"
|
||||
#include "video_frame.hpp"
|
||||
#include "filesystem_access.hpp"
|
||||
#include <unistd.h>
|
||||
@@ -336,13 +335,6 @@ bool touch(bool down, float x, float y) {
|
||||
return call_input("__ps5ReactTouch", 2, args);
|
||||
}
|
||||
|
||||
// A USB keyboard's typed characters ("text") or editing key ("key"), input protocol v6.
|
||||
bool keyboard(const char* type, const char* value) {
|
||||
JSContext* ctx = er_runtime_context();
|
||||
JSValue args[2] = {JS_NewString(ctx, type), JS_NewString(ctx, value)};
|
||||
return call_input("__ps5ReactKeyboard", 2, args);
|
||||
}
|
||||
|
||||
// Display.setRefreshRate: the rate can change only while EGL is terminated (ps5_opengl_display_modes.h),
|
||||
// so the presenter's GL objects go with the context and are made again. The engine framebuffer is CPU
|
||||
// memory and survives; the presenter's first draw uploads all of it.
|
||||
@@ -363,7 +355,6 @@ bool switch_refresh_rate(hui::ps5::Display& display, GlPresenter& presenter, con
|
||||
bool run_proof() {
|
||||
hui::ps5::Display display;
|
||||
hui::ps5::Pad pad;
|
||||
usb_keyboard::Session usb_keys;
|
||||
GlPresenter presenter;
|
||||
hui::InputTracker tracker;
|
||||
hui::PadSample samples[64];
|
||||
@@ -427,7 +418,6 @@ bool run_proof() {
|
||||
if (ok) {
|
||||
async_log::write("[PS5-REACT] pad=%d at %lldms", pad.open(), since_launch_ms());
|
||||
host_platform_set_pad(&pad);
|
||||
async_log::write("[PS5-REACT] usb keyboard=%d", usb_keys.open());
|
||||
}
|
||||
if (ok) {
|
||||
images::set_cache_bytes(image_cache_bytes());
|
||||
@@ -558,7 +548,6 @@ bool run_proof() {
|
||||
const bool touch_down = host_platform_read_touch(&touch_x, &touch_y);
|
||||
if (ok && (touch_down || touched)) ok = touch(touch_down, touch_x, touch_y);
|
||||
touched = touch_down;
|
||||
if (ok) ok = usb_keys.poll(now, keyboard);
|
||||
commands.poll(now, script);
|
||||
if (const char* action = ok ? script.next(static_cast<std::uint64_t>(now / 1000)) : nullptr) {
|
||||
async_log::write("[PS5-REACT] script: %s", action);
|
||||
@@ -733,8 +722,6 @@ int main() {
|
||||
#if PS5_REACT_VIDEO
|
||||
video::preload_platform_decoder();
|
||||
#endif
|
||||
// Like the decoder, before elevation: the system refuses module loads afterwards.
|
||||
usb_keyboard::preload();
|
||||
initialize_filesystem_access();
|
||||
pthread_attr_t attributes;
|
||||
int result = pthread_attr_init(&attributes);
|
||||
|
||||
@@ -1,233 +0,0 @@
|
||||
// Copyright (C) 2026 half144 and PS5 React contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
||||
#include "usb_keyboard.hpp"
|
||||
|
||||
#include "async_log.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
|
||||
namespace {
|
||||
|
||||
// The state sceKeyboardReadState fills, laid out as the ps5-payload-dev SDL port reads it
|
||||
// (src/video/ps5/SDL_ps5keyboard.c): an availability flag, the modifier bits, and up to sixteen held
|
||||
// keys as USB HID usage IDs. The SDK ships the library's stubs without headers. The spare tail only
|
||||
// absorbs a firmware that writes more than that layout, so it never lands on the stack around it.
|
||||
struct KeyboardState {
|
||||
std::uint64_t reserved0[2];
|
||||
std::uint8_t available;
|
||||
std::uint32_t reserved1[2];
|
||||
std::uint32_t modifiers;
|
||||
std::uint16_t keys[16];
|
||||
std::uint64_t reserved2[4];
|
||||
std::uint8_t spare[256];
|
||||
};
|
||||
|
||||
constexpr std::uint32_t kShift = 0x02 | 0x20; // left and right Shift
|
||||
constexpr std::uint32_t kCtrlAltGui = 0x01 | 0x04 | 0x08 | 0x10 | 0x40 | 0x80;
|
||||
constexpr std::int64_t kRepeatDelayUs = 500000, kRepeatIntervalUs = 40000;
|
||||
constexpr int kInvalidUserId = -1;
|
||||
|
||||
// USB HID usages 0x04..0x38 (letters, digits, Enter..Space and punctuation) on a US layout, plain and
|
||||
// shifted. A zero is a key handled elsewhere or not a character.
|
||||
constexpr char kPlain[] =
|
||||
"abcdefghijklmnopqrstuvwxyz1234567890\0\0\0\0 -=[]\\\0;'`,./";
|
||||
constexpr char kShifted[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()\0\0\0\0 _+{}|\0:\"~<>?";
|
||||
static_assert(sizeof kPlain == 0x38 - 0x04 + 2 && sizeof kShifted == sizeof kPlain);
|
||||
|
||||
constexpr std::uint16_t kEnter = 0x28, kEscape = 0x29, kBackspace = 0x2A, kTab = 0x2B, kCapsLock = 0x39,
|
||||
kDelete = 0x4C, kKeypadEnter = 0x58;
|
||||
|
||||
// The editing key a usage names, or nullptr.
|
||||
const char* key_name(std::uint16_t usage) {
|
||||
switch (usage) {
|
||||
case kEnter: case kKeypadEnter: return "Enter";
|
||||
case kEscape: return "Escape";
|
||||
case kBackspace: case kDelete: return "Backspace";
|
||||
case kTab: return "Tab";
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// The character a usage types, or 0. Caps Lock shifts letters only, as on a computer.
|
||||
char character(std::uint16_t usage, bool shift, bool caps_lock) {
|
||||
if (usage >= 0x04 && usage <= 0x38) {
|
||||
const bool letter = usage <= 0x1D;
|
||||
const char c = (shift != (letter && caps_lock) ? kShifted : kPlain)[usage - 0x04];
|
||||
return c;
|
||||
}
|
||||
// Keypad: / * - + Enter(handled) 1..9 0 .
|
||||
static constexpr char kKeypad[] = "/*-+\0" "1234567890.";
|
||||
if (usage >= 0x54 && usage <= 0x63) return kKeypad[usage - 0x54];
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool held(const std::uint16_t (&keys)[16], std::uint16_t usage) {
|
||||
return usage && std::find(std::begin(keys), std::end(keys), usage) != std::end(keys);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
int sceUserServiceGetForegroundUser(int* user);
|
||||
int sceSysmoduleLoadModule(std::uint16_t id);
|
||||
int sceSysmoduleGetModuleHandleInternal(std::uint32_t id, std::int32_t* handle);
|
||||
int sceKernelDlsym(std::int32_t handle, const char* symbol, void** address);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// libSceKeyboard is not loaded for the title. Calling it directly jumps to nothing (CE-108255-1 at
|
||||
// launch, rip 0x780); the SDK's dlopen works only in payloads; and a fake-signed title may not load
|
||||
// the module by path (0x80020063). The system loads it as one of its modules by id instead, as
|
||||
// prospero-win (native/pw_hid_ps5.c) measured on the console: sceSysmoduleLoadModule before
|
||||
// filesystem elevation, the module's handle from sceSysmoduleGetModuleHandleInternal (by its id or
|
||||
// its internal id, 0x80000000 | id), and each call from sceKernelDlsym. A step that fails leaves the
|
||||
// keyboard off and the title running.
|
||||
constexpr std::uint16_t kKeyboardModule = 0x0106;
|
||||
constexpr std::int64_t kOpenRetryUs = 3000000;
|
||||
|
||||
struct KeyboardCalls {
|
||||
int (*init)(void) = nullptr;
|
||||
int (*open)(int user, int type, int index, void* param) = nullptr;
|
||||
int (*read_state)(int handle, KeyboardState* state) = nullptr;
|
||||
int (*close)(int handle) = nullptr;
|
||||
};
|
||||
KeyboardCalls calls;
|
||||
// What preload found, for Session::open to log.
|
||||
int load_status = 0;
|
||||
std::int32_t module_handle = -1;
|
||||
const char* missing_call = nullptr;
|
||||
bool preloaded = false;
|
||||
|
||||
std::int32_t system_module_handle(std::uint16_t id) {
|
||||
std::int32_t handle = -1;
|
||||
if (sceSysmoduleGetModuleHandleInternal(id, &handle) == 0 && handle >= 0) return handle;
|
||||
handle = -1;
|
||||
if (sceSysmoduleGetModuleHandleInternal(0x80000000u | id, &handle) == 0 && handle >= 0) return handle;
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
bool find_call(Function& function, const char* name) {
|
||||
void* address = nullptr;
|
||||
if (sceKernelDlsym(module_handle, name, &address) != 0 || !address) {
|
||||
if (!missing_call) missing_call = name;
|
||||
return false;
|
||||
}
|
||||
function = reinterpret_cast<Function>(address);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace usb_keyboard {
|
||||
|
||||
void preload() {
|
||||
preloaded = true;
|
||||
load_status = sceSysmoduleLoadModule(kKeyboardModule);
|
||||
module_handle = system_module_handle(kKeyboardModule);
|
||||
if (module_handle < 0) return;
|
||||
// Every lookup runs, so the log names the first one missing.
|
||||
const bool has_init = find_call(calls.init, "sceKeyboardInit");
|
||||
const bool has_open = find_call(calls.open, "sceKeyboardOpen");
|
||||
const bool has_read = find_call(calls.read_state, "sceKeyboardReadState");
|
||||
const bool has_close = find_call(calls.close, "sceKeyboardClose");
|
||||
if (!(has_init && has_open && has_read && has_close)) calls = {};
|
||||
}
|
||||
|
||||
Session::~Session() {
|
||||
if (handle_ >= 0 && calls.close) calls.close(handle_);
|
||||
}
|
||||
|
||||
bool Session::open() {
|
||||
async_log::write("[PS5-REACT] keyboard: module 0x%04x load=0x%08x handle=%d%s%s", static_cast<unsigned>(kKeyboardModule),
|
||||
static_cast<unsigned>(load_status), static_cast<int>(module_handle),
|
||||
missing_call ? " missing " : "", missing_call ? missing_call : "");
|
||||
if (!preloaded || !calls.init) return false;
|
||||
if (int error = sceUserServiceGetForegroundUser(&user_); error != 0 || user_ == kInvalidUserId) {
|
||||
async_log::write("[PS5-REACT] keyboard: no foreground user 0x%08x", static_cast<unsigned>(error));
|
||||
return false;
|
||||
}
|
||||
if (int error = calls.init(); error != 0) {
|
||||
async_log::write("[PS5-REACT] keyboard: sceKeyboardInit=0x%08x", static_cast<unsigned>(error));
|
||||
return false;
|
||||
}
|
||||
ready_ = true;
|
||||
try_open(0);
|
||||
return handle_ >= 0;
|
||||
}
|
||||
|
||||
void Session::try_open(std::int64_t now_us) {
|
||||
std::uint8_t param[8] = {};
|
||||
const int handle = calls.open(user_, 0, 0, param);
|
||||
retry_at_us_ = now_us + kOpenRetryUs;
|
||||
if (handle >= 0) {
|
||||
handle_ = handle;
|
||||
async_log::write("[PS5-REACT] keyboard: open handle=%d", handle);
|
||||
return;
|
||||
}
|
||||
// Without a keyboard plugged in this fails every few seconds: logged the first time only.
|
||||
if (!open_failed_) async_log::write("[PS5-REACT] keyboard: sceKeyboardOpen=0x%08x, retrying", static_cast<unsigned>(handle));
|
||||
open_failed_ = true;
|
||||
}
|
||||
|
||||
bool Session::poll(std::int64_t now_us, Emit emit) {
|
||||
if (handle_ < 0) {
|
||||
if (!ready_ || now_us < retry_at_us_) return true;
|
||||
try_open(now_us);
|
||||
if (handle_ < 0) return true;
|
||||
}
|
||||
KeyboardState state;
|
||||
std::memset(&state, 0, sizeof state);
|
||||
if (int error = calls.read_state(handle_, &state); error != 0) {
|
||||
// Logged once per run of failures: an unplugged keyboard must not fill the log every frame.
|
||||
if (!read_failed_) async_log::write("[PS5-REACT] keyboard: sceKeyboardReadState=0x%08x", static_cast<unsigned>(error));
|
||||
read_failed_ = true;
|
||||
return true;
|
||||
}
|
||||
read_failed_ = false;
|
||||
std::uint16_t keys[16] = {};
|
||||
if (state.available) std::copy(std::begin(state.keys), std::end(state.keys), std::begin(keys));
|
||||
// Ctrl, Alt and the system key make shortcuts, not text.
|
||||
const bool shortcut = state.available && (state.modifiers & kCtrlAltGui);
|
||||
const bool shift = state.available && (state.modifiers & kShift);
|
||||
|
||||
// Sends one press of `usage`; false when the app's input call failed.
|
||||
auto press = [&](std::uint16_t usage) {
|
||||
if (const char* name = key_name(usage)) return emit("key", name);
|
||||
if (shortcut) return true;
|
||||
const char c = character(usage, shift, caps_lock_);
|
||||
if (!c) return true;
|
||||
const char text[2] = {c, 0};
|
||||
return emit("text", text);
|
||||
};
|
||||
|
||||
bool ok = true;
|
||||
for (std::uint16_t usage : keys) {
|
||||
if (!usage || held(held_, usage)) continue; // only newly pressed keys
|
||||
if (usage == kCapsLock) {
|
||||
caps_lock_ = !caps_lock_;
|
||||
continue;
|
||||
}
|
||||
if (ok) ok = press(usage);
|
||||
repeat_key_ = usage;
|
||||
repeat_shift_ = shift;
|
||||
repeat_at_us_ = now_us + kRepeatDelayUs;
|
||||
}
|
||||
// The last key pressed repeats while it stays down; a released key or a Shift change stops it.
|
||||
if (repeat_key_ && (!held(keys, repeat_key_) || repeat_shift_ != shift)) repeat_key_ = 0;
|
||||
if (ok && repeat_key_ && repeat_key_ != kCapsLock && now_us >= repeat_at_us_) {
|
||||
ok = press(repeat_key_);
|
||||
// Keeps the cadence across frames that don't fall on it; after a stall it resumes, never bursts.
|
||||
repeat_at_us_ += kRepeatIntervalUs;
|
||||
if (repeat_at_us_ <= now_us) repeat_at_us_ = now_us + kRepeatIntervalUs;
|
||||
}
|
||||
std::copy(std::begin(keys), std::end(keys), std::begin(held_));
|
||||
return ok;
|
||||
}
|
||||
|
||||
} // namespace usb_keyboard
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright (C) 2026 half144 and PS5 React contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
// A USB keyboard read through libSceKeyboard, turned into input protocol v6 keyboard input: typed
|
||||
// characters (US layout) and the editing keys Backspace, Enter, Escape and Tab, with hold-to-repeat.
|
||||
// Without a keyboard, or if the console refuses one, the title runs as before and nothing is sent.
|
||||
namespace usb_keyboard {
|
||||
|
||||
// Has the system load libSceKeyboard and finds its calls. Call it from main(), before filesystem
|
||||
// elevation: afterwards the system refuses module loads (0x80020063, as video_decoder.cpp found).
|
||||
// It logs nothing itself; Session::open reports what it found once the log is running.
|
||||
void preload();
|
||||
|
||||
// Receives ("text", characters) or ("key", name); false stops the frame, like the other input calls.
|
||||
using Emit = bool (*)(const char* type, const char* value);
|
||||
|
||||
class Session {
|
||||
public:
|
||||
Session() = default;
|
||||
Session(const Session&) = delete;
|
||||
Session& operator=(const Session&) = delete;
|
||||
~Session();
|
||||
|
||||
// After the pad opens: it initializes the user service the keyboard is opened for. True when a
|
||||
// keyboard is open now; without one, poll keeps trying, so a keyboard plugged in later works.
|
||||
bool open();
|
||||
// Once per frame on the render thread.
|
||||
bool poll(std::int64_t now_us, Emit emit);
|
||||
|
||||
private:
|
||||
void try_open(std::int64_t now_us);
|
||||
|
||||
int user_ = -1;
|
||||
int handle_ = -1;
|
||||
bool ready_ = false;
|
||||
bool open_failed_ = false;
|
||||
std::int64_t retry_at_us_ = 0;
|
||||
bool caps_lock_ = false;
|
||||
bool read_failed_ = false;
|
||||
std::uint16_t held_[16] = {};
|
||||
std::uint16_t repeat_key_ = 0;
|
||||
bool repeat_shift_ = false;
|
||||
std::int64_t repeat_at_us_ = 0;
|
||||
};
|
||||
|
||||
} // namespace usb_keyboard
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
// Input actions (input protocol v6, see runtime/js/input.js): what hosts pass to
|
||||
// Input actions (input protocol v5, see runtime/js/input.js): what hosts pass to
|
||||
// globalThis.__ps5ReactDispatch and input scripts accept. runtime/js/input.js names the same actions
|
||||
// for JavaScript. Options reaches the app as `options`; the PS button leaves.
|
||||
enum class HostAction : unsigned char { up, down, left, right, confirm, back, l1, r1, l2, r2, triangle, square, options };
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
||||
import {useLayoutEffect, useRef} from 'react';
|
||||
import {subscribeInput, subscribeKeyboard} from './input.js';
|
||||
import {subscribeInput} from './input.js';
|
||||
import {Text as EngineText, unstable_batchedUpdates} from 'embedded-react';
|
||||
import {setFrameBatcher} from './frame.js';
|
||||
import {inspectableText} from './focus/inspect-text.js';
|
||||
@@ -35,19 +35,6 @@ export function useController(handler) {
|
||||
}), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a USB keyboard while mounted (PS5 host only; see docs/NAVIGATION.md). The handler
|
||||
* gets `{type: 'text', value}` for typed characters and `{type: 'key', value}` for `Backspace`,
|
||||
* `Enter`, `Escape` and `Tab`. Pass a falsy handler to stop listening.
|
||||
* @param {((input: import('./input.js').KeyboardInput) => void) | null | undefined} handler
|
||||
*/
|
||||
export function useKeyboardInput(handler) {
|
||||
const current = useRef(handler);
|
||||
useLayoutEffect(() => { current.current = handler; });
|
||||
const listening = Boolean(handler);
|
||||
useLayoutEffect(() => (listening ? subscribeKeyboard(input => current.current?.(input)) : undefined), [listening]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tailwind-style classes → a style object, compiled by the build (see docs/TAILWIND.md).
|
||||
* @param {TemplateStringsArray} classes
|
||||
|
||||
@@ -3,17 +3,14 @@
|
||||
// Additional attribution term: see LICENSE-ATTRIBUTION.
|
||||
const listeners = new Set();
|
||||
const touchListeners = new Set();
|
||||
const keyboardListeners = new Set();
|
||||
|
||||
// Actions as strings, touch samples as [x, y] (x null when the finger lifts), keyboard input as
|
||||
// {type, value}, in arrival order.
|
||||
// Actions as strings, touch samples as [x, y] (x null when the finger lifts), in arrival order.
|
||||
const pending = [];
|
||||
|
||||
function deliver() {
|
||||
for (const item of pending.splice(0)) {
|
||||
if (typeof item === 'string') for (const listener of listeners) listener(item);
|
||||
else if (Array.isArray(item)) for (const listener of touchListeners) listener(item[0], item[1]);
|
||||
else for (const listener of keyboardListeners) listener(item);
|
||||
else for (const listener of touchListeners) listener(item[0], item[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +30,6 @@ globalThis.__ps5ReactTouch = (x, y) => {
|
||||
if (pending.push([x ?? null, y ?? null]) === 1) Promise.resolve().then(deliver);
|
||||
};
|
||||
|
||||
// Input protocol v6: a USB keyboard on the PS5 host, as ('text', characters) for what was typed and
|
||||
// ('key', name) for Backspace, Enter, Escape and Tab. The keys reach only keyboard listeners, never
|
||||
// the focus runtime, so a keyboard types into fields without moving focus.
|
||||
globalThis.__ps5ReactKeyboard = (type, value) => {
|
||||
if (type !== 'text' && type !== 'key') return;
|
||||
if (pending.push({type, value: String(value)}) === 1) Promise.resolve().then(deliver);
|
||||
};
|
||||
|
||||
/**
|
||||
* The host actions, as native/shared/actions.hpp names them.
|
||||
* @typedef {'l1' | 'r1' | 'l2' | 'r2' | 'triangle' | 'square' | 'options'} ButtonAction shoulders, triggers, Triangle, Square, Options
|
||||
@@ -64,17 +53,3 @@ export function subscribeTouch(listener) {
|
||||
touchListeners.add(listener);
|
||||
return () => { touchListeners.delete(listener); };
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyboard input: `text` carries typed characters, `key` names an editing key.
|
||||
* @typedef {{type: 'text', value: string} | {type: 'key', value: 'Backspace' | 'Enter' | 'Escape' | 'Tab'}} KeyboardInput
|
||||
*/
|
||||
|
||||
/**
|
||||
* Internal: receive keyboard input until the returned function is called.
|
||||
* @param {(input: KeyboardInput) => void} listener
|
||||
*/
|
||||
export function subscribeKeyboard(listener) {
|
||||
keyboardListeners.add(listener);
|
||||
return () => { keyboardListeners.delete(listener); };
|
||||
}
|
||||
@@ -153,7 +153,7 @@ def main():
|
||||
definitions = next(line.split("=", 1)[1] for line in
|
||||
(cross / "bridge/engine/CMakeFiles/embedded-react.dir/flags.make").read_text().splitlines()
|
||||
if line.startswith("C_DEFINES ="))
|
||||
sources = [ROOT / "native/ps5/native_host.cpp", ROOT / "native/ps5/usb_keyboard.cpp", ROOT / "native/ps5/async_log.cpp", ROOT / "native/ps5/host_platform.cpp", ROOT / "native/ps5/video_output.cpp", ROOT / "native/ps5/time_compat.c", ROOT / "native/ps5/archive_compat.c",
|
||||
sources = [ROOT / "native/ps5/native_host.cpp", ROOT / "native/ps5/async_log.cpp", ROOT / "native/ps5/host_platform.cpp", ROOT / "native/ps5/video_output.cpp", ROOT / "native/ps5/time_compat.c", ROOT / "native/ps5/archive_compat.c",
|
||||
ROOT / "native/ps5/filesystem_access.cpp",
|
||||
ROOT / "native/ps5/elevation_transport.cpp",
|
||||
ROOT / "native/shared/host_api.cpp", ROOT / "native/shared/removals.cpp", ROOT / "native/shared/network.cpp",
|
||||
|
||||
Reference in new issue
Block a user