Files
2026-07-28 06:34:41 +02:00

280 lines
9.2 KiB
C++

// teclado usb, copiando el orden del ejemplo samples/keyboard de openorbis:
// sceSysmoduleLoadModule(0x0106)
// sceKeyboardInit()
// sceKeyboardOpen(usuario, 0, 0, nullptr)
// sceKeyboardReadState(handle, &data) <- una vez por frame
//
// cuidado con esto: ReadState no devuelve una cola de eventos, devuelve las
// teclas que estan pulsadas AHORA. asi que me guardo como estaban antes y
// comparo, y de ahi salen los "pulsada" y "soltada"
#include "ps4_keyboard.h"
#include <cstdio>
#include <cstring>
#include <orbis/Keyboard.h>
// lo declaro a mano en vez de incluir Sysmodule.h porque el nombre del enum
// cambia entre versiones de openorbis y se me rompia la compilacion
extern "C" int sceSysmoduleLoadModule(int moduleId);
static constexpr int PS4_SYSMODULE_KEYBOARD = 0x0106;
namespace {
// los usages caben en un byte. si llega algo mas grande es cosa rara del
// teclado y lo ignoro, no me fio de usarlo como indice
constexpr unsigned USAGE_TABLE_SIZE = 256;
bool s_sysmoduleLoaded = false;
bool s_libraryInitialized = false;
int s_handle = -1;
int s_userId = -1;
int s_sysmoduleResult = -1;
int s_initResult = -1;
int s_openResult = -1;
int s_lastReadResult = 0;
unsigned s_openAttempts = 0;
bool s_pressed[USAGE_TABLE_SIZE] {};
std::uint16_t s_modifiers = 0;
std::uint16_t s_locks = 0;
void resetPressedState()
{
std::memset(s_pressed, 0, sizeof(s_pressed));
s_modifiers = 0;
s_locks = 0;
}
// los modificadores vienen en data.mods, pero algunos teclados ademas los
// meten en la lista de teclas. junto las dos cosas para no contarlos dos veces
struct ModifierUsage {
std::uint16_t bit;
std::uint16_t usage;
};
constexpr ModifierUsage MODIFIER_USAGES[] {
{PS4_KBD_MOD_LEFT_CTRL, 0xE0},
{PS4_KBD_MOD_LEFT_SHIFT, 0xE1},
{PS4_KBD_MOD_LEFT_ALT, 0xE2},
{PS4_KBD_MOD_LEFT_META, 0xE3},
{PS4_KBD_MOD_RIGHT_CTRL, 0xE4},
{PS4_KBD_MOD_RIGHT_SHIFT, 0xE5},
{PS4_KBD_MOD_RIGHT_ALT, 0xE6},
{PS4_KBD_MOD_RIGHT_META, 0xE7}
};
// esto es el plan b por si sceKeyboardGetKey2Char no contesta. solo me importa
// el ascii: al pc-98 le llega el usage, el caracter es de adorno para libretro.
// la distribucion que asumo aqui es la de un teclado US, con otra saldran mal
// algunos simbolos
char asciiFallback(std::uint16_t usage, bool shift, bool caps)
{
if (usage >= 0x04 && usage <= 0x1D) {
const bool upper = shift != caps;
return static_cast<char>((upper ? 'A' : 'a') + (usage - 0x04));
}
if (usage >= 0x1E && usage <= 0x26) {
static const char shifted[] = "!@#$%^&*(";
return shift ? shifted[usage - 0x1E]
: static_cast<char>('1' + (usage - 0x1E));
}
switch (usage) {
case 0x27: return shift ? ')' : '0';
case 0x28: return '\n';
case 0x2A: return '\b';
case 0x2B: return '\t';
case 0x2C: return ' ';
case 0x2D: return shift ? '_' : '-';
case 0x2E: return shift ? '+' : '=';
case 0x2F: return shift ? '{' : '[';
case 0x30: return shift ? '}' : ']';
case 0x31: return shift ? '|' : '\\';
case 0x33: return shift ? ':' : ';';
case 0x34: return shift ? '"' : '\'';
case 0x35: return shift ? '~' : '`';
case 0x36: return shift ? '<' : ',';
case 0x37: return shift ? '>' : '.';
case 0x38: return shift ? '?' : '/';
case 0x54: return '/';
case 0x55: return '*';
case 0x56: return '-';
case 0x57: return '+';
case 0x58: return '\n';
case 0x63: return '.';
default: break;
}
if (usage >= 0x59 && usage <= 0x61) {
return static_cast<char>('1' + (usage - 0x59));
}
if (usage == 0x62) return '0';
return 0;
}
bool ensureSysmodule()
{
if (s_sysmoduleLoaded) return true;
s_sysmoduleResult = sceSysmoduleLoadModule(PS4_SYSMODULE_KEYBOARD);
// si ya estaba cargado devuelve algo distinto de cero que no es un error.
// no me fio de este valor, el que decide es el init de abajo
s_sysmoduleLoaded = true;
std::printf("[NP2kai/keyboard] sysmodule keyboard result=0x%x\n",
s_sysmoduleResult);
return true;
}
bool ensureLibrary()
{
if (s_libraryInitialized) return true;
s_initResult = sceKeyboardInit();
s_libraryInitialized = s_initResult == 0;
std::printf("[NP2kai/keyboard] sceKeyboardInit result=0x%x\n", s_initResult);
return s_libraryInitialized;
}
} // namespace
extern "C" {
bool ps4KeyboardOpenDevice(int userId)
{
if (s_handle >= 0) return true;
if (userId < 0) return false;
++s_openAttempts;
s_userId = userId;
if (!ensureSysmodule()) return false;
if (!ensureLibrary()) return false;
s_openResult = sceKeyboardOpen(s_userId, 0, 0, nullptr);
if (s_openResult < 0) {
std::printf("[NP2kai/keyboard] sceKeyboardOpen failed result=0x%x "
"user=%d attempts=%u\n",
s_openResult, s_userId, s_openAttempts);
return false;
}
s_handle = s_openResult;
resetPressedState();
std::printf("[NP2kai/keyboard] device open handle=%d user=%d attempts=%u\n",
s_handle, s_userId, s_openAttempts);
return true;
}
void ps4KeyboardCloseDevice()
{
if (s_handle >= 0) {
sceKeyboardClose(s_handle);
std::printf("[NP2kai/keyboard] device closed handle=%d\n", s_handle);
}
s_handle = -1;
resetPressedState();
}
bool ps4KeyboardIsOpen()
{
return s_handle >= 0;
}
unsigned ps4KeyboardPoll(Ps4KeyboardEvent* events, unsigned maxEvents)
{
if (events == nullptr || maxEvents == 0 || s_handle < 0) return 0;
OrbisKeyboardData data {};
s_lastReadResult = sceKeyboardReadState(s_handle, &data);
if (s_lastReadResult < 0) {
// o lo han desenchufado o el handle ya no vale. suelto todas las teclas
// que estaban pulsadas o el pc-98 se queda con una tecla fantasma
// apretada para siempre (me paso, y es de lo mas molesto)
unsigned produced = 0;
for (unsigned usage = 0; usage < USAGE_TABLE_SIZE && produced < maxEvents;
++usage) {
if (!s_pressed[usage]) continue;
events[produced].usage = static_cast<std::uint16_t>(usage);
events[produced].modifiers = s_modifiers;
events[produced].locks = s_locks;
events[produced].down = false;
++produced;
}
ps4KeyboardCloseDevice();
std::printf("[NP2kai/keyboard] read failed result=0x%x, handle released\n",
s_lastReadResult);
return produced;
}
bool current[USAGE_TABLE_SIZE] {};
int reportedKeys = data.nkeys;
if (reportedKeys < 0) reportedKeys = 0;
const int maxKeys =
static_cast<int>(sizeof(data.keycodes) / sizeof(data.keycodes[0]));
if (reportedKeys > maxKeys) reportedKeys = maxKeys;
for (int index = 0; index < reportedKeys; ++index) {
const std::uint16_t usage = data.keycodes[index];
if (usage == 0 || usage >= USAGE_TABLE_SIZE) continue;
current[usage] = true;
}
const std::uint16_t modifiers = static_cast<std::uint16_t>(data.mods);
const std::uint16_t locks = static_cast<std::uint16_t>(data.locks);
for (const ModifierUsage& entry : MODIFIER_USAGES) {
if ((modifiers & entry.bit) != 0) current[entry.usage] = true;
}
unsigned produced = 0;
for (unsigned usage = 0; usage < USAGE_TABLE_SIZE; ++usage) {
if (current[usage] == s_pressed[usage]) continue;
if (produced < maxEvents) {
events[produced].usage = static_cast<std::uint16_t>(usage);
events[produced].modifiers = modifiers;
events[produced].locks = locks;
events[produced].down = current[usage];
++produced;
s_pressed[usage] = current[usage];
}
// si se llena el buffer, lo que sobra queda pendiente para la siguiente
// vuelta: solo doy por hecha la tecla que he llegado a entregar
}
s_modifiers = modifiers;
s_locks = locks;
return produced;
}
char ps4KeyboardUsageToCharacter(std::uint16_t usage,
std::uint16_t modifiers,
std::uint16_t locks)
{
if (s_handle >= 0) {
OrbisKeyboardKey2Char translated {};
const int result = sceKeyboardGetKey2Char(
s_handle, false, static_cast<int32_t>(locks),
static_cast<int32_t>(modifiers), static_cast<int32_t>(usage),
&translated);
if (result >= 0 && translated.ok != 0) {
const int character = translated.keycode;
if (character > 0 && character < 0x80) {
return static_cast<char>(character);
}
}
}
const bool shift =
(modifiers & (PS4_KBD_MOD_LEFT_SHIFT | PS4_KBD_MOD_RIGHT_SHIFT)) != 0;
const bool caps = (locks & PS4_KBD_LOCK_CAPS) != 0;
return asciiFallback(usage, shift, caps);
}
int ps4KeyboardSysmoduleResult() { return s_sysmoduleResult; }
int ps4KeyboardInitResult() { return s_initResult; }
int ps4KeyboardOpenResult() { return s_openResult; }
int ps4KeyboardLastReadResult() { return s_lastReadResult; }
unsigned ps4KeyboardOpenAttempts() { return s_openAttempts; }
int ps4KeyboardUserId() { return s_userId; }
} // extern "C"