2025-12-16 21:51:41 -05:00
|
|
|
#include "android_input_system.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
2025-12-18 15:05:51 -05:00
|
|
|
#include <cmath>
|
2025-12-16 21:51:41 -05:00
|
|
|
#include <cstring>
|
|
|
|
|
|
|
|
|
|
AndroidInputSystem::AndroidInputSystem()
|
|
|
|
|
: CInputSystem("android-sdl"),
|
|
|
|
|
m_keys(SDL_NUM_SCANCODES, 0)
|
|
|
|
|
{
|
2025-12-18 15:05:51 -05:00
|
|
|
std::memset(&m_mouseDetails, 0, sizeof(m_mouseDetails));
|
|
|
|
|
std::strncpy(m_mouseDetails.name, "Touchscreen", MAX_NAME_LENGTH);
|
|
|
|
|
m_mouseDetails.name[MAX_NAME_LENGTH] = '\0';
|
|
|
|
|
m_mouseDetails.isAbsolute = true;
|
2025-12-19 02:08:24 -05:00
|
|
|
|
|
|
|
|
std::memset(&m_virtualJoyDetails, 0, sizeof(m_virtualJoyDetails));
|
|
|
|
|
std::strncpy(m_virtualJoyDetails.name, "Touch Wheel", MAX_NAME_LENGTH);
|
|
|
|
|
m_virtualJoyDetails.name[MAX_NAME_LENGTH] = '\0';
|
|
|
|
|
m_virtualJoyDetails.numAxes = 6;
|
|
|
|
|
m_virtualJoyDetails.numPOVs = 0;
|
|
|
|
|
m_virtualJoyDetails.numButtons = 0;
|
|
|
|
|
m_virtualJoyDetails.hasFFeedback = false;
|
|
|
|
|
for (int a = 0; a < NUM_JOY_AXES; a++)
|
|
|
|
|
{
|
|
|
|
|
m_virtualJoyDetails.hasAxis[a] = false;
|
|
|
|
|
m_virtualJoyDetails.axisHasFF[a] = false;
|
|
|
|
|
std::strncpy(m_virtualJoyDetails.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH);
|
|
|
|
|
m_virtualJoyDetails.axisName[a][MAX_NAME_LENGTH] = '\0';
|
|
|
|
|
}
|
|
|
|
|
m_virtualJoyDetails.hasAxis[AXIS_X] = true;
|
2025-12-18 15:05:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AndroidInputSystem::~AndroidInputSystem()
|
|
|
|
|
{
|
|
|
|
|
CloseControllers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::SetGunTouchEnabled(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
if (m_gunTouchEnabled == enabled)
|
|
|
|
|
return;
|
|
|
|
|
m_gunTouchEnabled = enabled;
|
|
|
|
|
m_gunFingerActive = false;
|
|
|
|
|
m_gunFinger = 0;
|
|
|
|
|
m_mouseX = 0;
|
|
|
|
|
m_mouseY = 0;
|
|
|
|
|
m_mouseWheelDir = 0;
|
|
|
|
|
std::memset(m_mouseButtons, 0, sizeof(m_mouseButtons));
|
|
|
|
|
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
|
2025-12-16 21:51:41 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-19 02:08:24 -05:00
|
|
|
void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
if (m_virtualWheelEnabled == enabled)
|
|
|
|
|
return;
|
|
|
|
|
m_virtualWheelEnabled = enabled;
|
|
|
|
|
m_wheelFingerActive = false;
|
|
|
|
|
m_wheelFinger = 0;
|
|
|
|
|
m_virtualJoyX = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::UseVirtualWheel() const
|
|
|
|
|
{
|
|
|
|
|
return m_virtualWheelEnabled && m_controllers.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::SetVirtualSteerFromEncoded(float encodedX)
|
|
|
|
|
{
|
|
|
|
|
// encodedX comes from Java as [(steer+1)/2], where steer is in [-1,1].
|
|
|
|
|
const float steer = std::clamp((encodedX - 0.5f) * 2.0f, -1.0f, 1.0f);
|
|
|
|
|
constexpr float deadzone = 0.08f;
|
|
|
|
|
if (std::abs(steer) < deadzone)
|
|
|
|
|
{
|
|
|
|
|
m_virtualJoyX = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const float scaled = (std::abs(steer) - deadzone) / (1.0f - deadzone);
|
|
|
|
|
const float signedScaled = (steer < 0.0f) ? -scaled : scaled;
|
|
|
|
|
m_virtualJoyX = (int)std::lround(std::clamp(signedScaled, -1.0f, 1.0f) * 32767.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config)
|
|
|
|
|
{
|
|
|
|
|
auto get = [&](const char* key, const char* fallback) -> std::string {
|
|
|
|
|
return config[key].ValueAsDefault<std::string>(fallback);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto keySc = [&](const std::string& mapping, SDL_Scancode fallback) -> SDL_Scancode {
|
|
|
|
|
if (mapping.empty()) return fallback;
|
|
|
|
|
SDL_Scancode sc = ParseFirstKeyboardScancode(mapping, *this);
|
|
|
|
|
return (sc == SDL_SCANCODE_UNKNOWN) ? fallback : sc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
m_touchCoin.a = keySc(get("InputCoin1", "KEY_5"), SDL_SCANCODE_5);
|
|
|
|
|
m_touchStart.a = keySc(get("InputStart1", "KEY_1"), SDL_SCANCODE_1);
|
|
|
|
|
m_touchService.a = keySc(get("InputServiceA", "KEY_F1"), SDL_SCANCODE_F1);
|
|
|
|
|
m_touchTest.a = keySc(get("InputTestA", "KEY_F2"), SDL_SCANCODE_F2);
|
|
|
|
|
|
|
|
|
|
// For left/right, we press both joy and steering if they differ, to keep menus and driving working.
|
|
|
|
|
const SDL_Scancode joyLeft = keySc(get("InputJoyLeft", "KEY_LEFT"), SDL_SCANCODE_LEFT);
|
|
|
|
|
const SDL_Scancode joyRight = keySc(get("InputJoyRight", "KEY_RIGHT"), SDL_SCANCODE_RIGHT);
|
|
|
|
|
const SDL_Scancode steerLeft = keySc(get("InputSteeringLeft", "KEY_LEFT"), SDL_SCANCODE_LEFT);
|
|
|
|
|
const SDL_Scancode steerRight = keySc(get("InputSteeringRight", "KEY_RIGHT"), SDL_SCANCODE_RIGHT);
|
|
|
|
|
|
|
|
|
|
m_touchJoyUp.a = keySc(get("InputJoyUp", "KEY_UP"), SDL_SCANCODE_UP);
|
|
|
|
|
m_touchJoyDown.a = keySc(get("InputJoyDown", "KEY_DOWN"), SDL_SCANCODE_DOWN);
|
|
|
|
|
|
|
|
|
|
m_touchJoyLeft = {joyLeft, (joyLeft != steerLeft) ? steerLeft : SDL_SCANCODE_UNKNOWN};
|
|
|
|
|
m_touchJoyRight = {joyRight, (joyRight != steerRight) ? steerRight : SDL_SCANCODE_UNKNOWN};
|
|
|
|
|
m_touchSteerLeft = {steerLeft, (steerLeft != joyLeft) ? joyLeft : SDL_SCANCODE_UNKNOWN};
|
|
|
|
|
m_touchSteerRight = {steerRight, (steerRight != joyRight) ? joyRight : SDL_SCANCODE_UNKNOWN};
|
|
|
|
|
|
|
|
|
|
m_touchThrottle.a = keySc(get("InputAccelerator", "KEY_W"), SDL_SCANCODE_W);
|
2025-12-18 22:18:28 -05:00
|
|
|
m_touchBrake.a = keySc(get("InputBrake", "KEY_X"), SDL_SCANCODE_X);
|
2025-12-17 04:44:30 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
bool AndroidInputSystem::InitializeSystem()
|
|
|
|
|
{
|
|
|
|
|
std::fill(m_keys.begin(), m_keys.end(), 0);
|
|
|
|
|
m_fingerHeldDir.clear();
|
|
|
|
|
m_fingerHeldKey.clear();
|
|
|
|
|
m_pulseUntilMs.clear();
|
2025-12-18 15:05:51 -05:00
|
|
|
m_mouseX = 0;
|
|
|
|
|
m_mouseY = 0;
|
|
|
|
|
m_mouseWheelDir = 0;
|
|
|
|
|
std::memset(m_mouseButtons, 0, sizeof(m_mouseButtons));
|
|
|
|
|
std::memset(m_mouseButtonPulseUntilMs, 0, sizeof(m_mouseButtonPulseUntilMs));
|
|
|
|
|
m_gunFingerActive = false;
|
|
|
|
|
m_gunFinger = 0;
|
2025-12-19 02:08:24 -05:00
|
|
|
m_wheelFingerActive = false;
|
|
|
|
|
m_wheelFinger = 0;
|
|
|
|
|
m_virtualJoyX = 0;
|
2025-12-18 15:05:51 -05:00
|
|
|
|
|
|
|
|
SDL_GameControllerEventState(SDL_ENABLE);
|
|
|
|
|
RefreshControllers();
|
2025-12-16 21:51:41 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::SetKey(SDL_Scancode sc, bool down)
|
|
|
|
|
{
|
|
|
|
|
if (sc <= SDL_SCANCODE_UNKNOWN || sc >= SDL_NUM_SCANCODES)
|
|
|
|
|
return;
|
|
|
|
|
m_keys[static_cast<size_t>(sc)] = down ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::PulseKey(SDL_Scancode sc, uint32_t durationMs)
|
|
|
|
|
{
|
|
|
|
|
if (sc <= SDL_SCANCODE_UNKNOWN || sc >= SDL_NUM_SCANCODES)
|
|
|
|
|
return;
|
|
|
|
|
SetKey(sc, true);
|
|
|
|
|
m_pulseUntilMs[sc] = SDL_GetTicks() + durationMs;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
void AndroidInputSystem::SetKeys(const DualScancode& sc, bool down)
|
|
|
|
|
{
|
|
|
|
|
if (sc.a != SDL_SCANCODE_UNKNOWN) SetKey(sc.a, down);
|
|
|
|
|
if (sc.b != SDL_SCANCODE_UNKNOWN) SetKey(sc.b, down);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::PulseKeys(const DualScancode& sc, uint32_t durationMs)
|
|
|
|
|
{
|
|
|
|
|
if (sc.a != SDL_SCANCODE_UNKNOWN) PulseKey(sc.a, durationMs);
|
|
|
|
|
if (sc.b != SDL_SCANCODE_UNKNOWN) PulseKey(sc.b, durationMs);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
bool AndroidInputSystem::Poll()
|
|
|
|
|
{
|
2025-12-18 15:05:51 -05:00
|
|
|
SDL_GameControllerUpdate();
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
const uint32_t now = SDL_GetTicks();
|
|
|
|
|
for (auto it = m_pulseUntilMs.begin(); it != m_pulseUntilMs.end();)
|
|
|
|
|
{
|
|
|
|
|
if (now >= it->second)
|
|
|
|
|
{
|
|
|
|
|
SetKey(static_cast<SDL_Scancode>(it->first), false);
|
|
|
|
|
it = m_pulseUntilMs.erase(it);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
++it;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-18 15:05:51 -05:00
|
|
|
|
|
|
|
|
for (int i = 0; i < kMouseButtons; i++)
|
|
|
|
|
{
|
|
|
|
|
if (m_mouseButtonPulseUntilMs[i] != 0 && now >= m_mouseButtonPulseUntilMs[i])
|
|
|
|
|
{
|
|
|
|
|
m_mouseButtonPulseUntilMs[i] = 0;
|
|
|
|
|
m_mouseButtons[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-16 21:51:41 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::HandleEvent(const SDL_Event& ev)
|
|
|
|
|
{
|
|
|
|
|
switch (ev.type)
|
|
|
|
|
{
|
2025-12-18 15:05:51 -05:00
|
|
|
case SDL_CONTROLLERDEVICEADDED:
|
|
|
|
|
case SDL_CONTROLLERDEVICEREMOVED:
|
|
|
|
|
case SDL_CONTROLLERDEVICEREMAPPED:
|
|
|
|
|
RefreshControllers();
|
|
|
|
|
break;
|
2025-12-16 21:51:41 -05:00
|
|
|
case SDL_KEYDOWN:
|
|
|
|
|
HandleKeyEvent(ev.key, true);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_KEYUP:
|
|
|
|
|
HandleKeyEvent(ev.key, false);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_CONTROLLERBUTTONDOWN:
|
|
|
|
|
HandleControllerButtonEvent(ev.cbutton, true);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_CONTROLLERBUTTONUP:
|
|
|
|
|
HandleControllerButtonEvent(ev.cbutton, false);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_FINGERDOWN:
|
|
|
|
|
HandleTouch(ev.tfinger, true);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_FINGERUP:
|
|
|
|
|
HandleTouch(ev.tfinger, false);
|
|
|
|
|
break;
|
|
|
|
|
case SDL_FINGERMOTION:
|
|
|
|
|
HandleTouchMotion(ev.tfinger);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::HandleKeyEvent(const SDL_KeyboardEvent& key, bool down)
|
|
|
|
|
{
|
|
|
|
|
SetKey(key.keysym.scancode, down);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::HandleControllerButtonEvent(const SDL_ControllerButtonEvent& btn, bool down)
|
|
|
|
|
{
|
2025-12-18 15:05:51 -05:00
|
|
|
// Controller buttons can be mapped in Supermodel.ini via JOY mappings, but we also
|
|
|
|
|
// synthesize a few "touch-style" keys for convenience in the UI / test menu.
|
2025-12-17 04:44:30 -05:00
|
|
|
DualScancode sc;
|
2025-12-16 21:51:41 -05:00
|
|
|
switch (btn.button)
|
|
|
|
|
{
|
2025-12-17 04:44:30 -05:00
|
|
|
case SDL_CONTROLLER_BUTTON_START: sc = m_touchStart; break;
|
2025-12-18 15:05:51 -05:00
|
|
|
case SDL_CONTROLLER_BUTTON_BACK: sc = m_touchCoin; break;
|
|
|
|
|
case SDL_CONTROLLER_BUTTON_GUIDE: sc = m_touchService; break;
|
2025-12-17 04:44:30 -05:00
|
|
|
case SDL_CONTROLLER_BUTTON_DPAD_UP: sc = m_touchJoyUp; break;
|
|
|
|
|
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: sc = m_touchJoyDown; break;
|
|
|
|
|
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: sc = m_touchJoyLeft; break;
|
|
|
|
|
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: sc = m_touchJoyRight; break;
|
2025-12-16 21:51:41 -05:00
|
|
|
default: break;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
if (sc.a == SDL_SCANCODE_UNKNOWN && sc.b == SDL_SCANCODE_UNKNOWN)
|
2025-12-16 21:51:41 -05:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// For coin/start/test, prefer a pulse to avoid repeating while held.
|
2025-12-17 04:44:30 -05:00
|
|
|
if (down && (sc.a == m_touchStart.a || sc.a == m_touchCoin.a || sc.a == m_touchService.a || sc.a == m_touchTest.a))
|
|
|
|
|
PulseKeys(sc, 120);
|
2025-12-16 21:51:41 -05:00
|
|
|
else
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(sc, down);
|
2025-12-16 21:51:41 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down)
|
|
|
|
|
{
|
|
|
|
|
const float x = tf.x;
|
|
|
|
|
const float y = tf.y;
|
|
|
|
|
|
2025-12-19 02:08:24 -05:00
|
|
|
// Virtual steering wheel (racing games): encoded in tf.x from Java and keyed by a fixed fingerId.
|
|
|
|
|
if (UseVirtualWheel())
|
|
|
|
|
{
|
|
|
|
|
constexpr SDL_FingerID kWheelFingerId = 1107;
|
|
|
|
|
if (down)
|
|
|
|
|
{
|
|
|
|
|
if (!m_wheelFingerActive && tf.fingerId == kWheelFingerId)
|
|
|
|
|
{
|
|
|
|
|
m_wheelFingerActive = true;
|
|
|
|
|
m_wheelFinger = tf.fingerId;
|
|
|
|
|
SetVirtualSteerFromEncoded(x);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (m_wheelFingerActive && tf.fingerId == m_wheelFinger)
|
|
|
|
|
{
|
|
|
|
|
SetVirtualSteerFromEncoded(0.5f);
|
|
|
|
|
m_wheelFingerActive = false;
|
|
|
|
|
m_wheelFinger = 0;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
// Tap zones (momentary):
|
|
|
|
|
// - Bottom-left: Coin (KEY_5)
|
2025-12-18 22:18:28 -05:00
|
|
|
// - Bottom-middle: Start (KEY_1)
|
2025-12-16 21:51:41 -05:00
|
|
|
// - Top-left: Service (KEY_F1)
|
|
|
|
|
// - Top-right: Test (KEY_F2)
|
|
|
|
|
if (down)
|
|
|
|
|
{
|
2025-12-17 04:44:30 -05:00
|
|
|
if (x < 0.25f && y > 0.75f) { PulseKeys(m_touchCoin, 120); return; }
|
2025-12-18 22:18:28 -05:00
|
|
|
if (x > 0.40f && x < 0.60f && y > 0.75f) { PulseKeys(m_touchStart, 120); return; }
|
2025-12-17 04:44:30 -05:00
|
|
|
if (x < 0.25f && y < 0.25f) { PulseKeys(m_touchService, 120); return; }
|
|
|
|
|
if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; }
|
2025-12-16 21:51:41 -05:00
|
|
|
}
|
|
|
|
|
|
2025-12-18 15:05:51 -05:00
|
|
|
// Lightgun/analog-gun games: use the touchscreen as an absolute "mouse" so
|
|
|
|
|
// existing MOUSE_XAXIS/MOUSE_YAXIS + MOUSE_LEFT_BUTTON mappings work without
|
|
|
|
|
// a physical mouse.
|
|
|
|
|
if (m_gunTouchEnabled)
|
|
|
|
|
{
|
|
|
|
|
if (down)
|
|
|
|
|
{
|
|
|
|
|
if (!m_gunFingerActive)
|
|
|
|
|
{
|
|
|
|
|
m_gunFingerActive = true;
|
|
|
|
|
m_gunFinger = tf.fingerId;
|
|
|
|
|
SetMousePosFromNormalized(x, y);
|
|
|
|
|
SetMouseButton(0, true); // left = trigger (held)
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Second touch while aiming: treat as offscreen/reload (right button pulse).
|
|
|
|
|
PulseMouseButton(2, 120);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (m_gunFingerActive && tf.fingerId == m_gunFinger)
|
|
|
|
|
{
|
|
|
|
|
SetMouseButton(0, false);
|
|
|
|
|
m_gunFingerActive = false;
|
|
|
|
|
m_gunFinger = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
// Held throttle/brake zone (right-middle): hold to accelerate/brake.
|
|
|
|
|
// - Upper half: throttle (KEY_W)
|
|
|
|
|
// - Lower half: brake (KEY_S)
|
|
|
|
|
const bool inPedalZone = (x > 0.55f && y >= 0.25f && y <= 0.90f);
|
|
|
|
|
if (inPedalZone)
|
|
|
|
|
{
|
|
|
|
|
if (!down)
|
|
|
|
|
{
|
|
|
|
|
auto it = m_fingerHeldKey.find(tf.fingerId);
|
|
|
|
|
if (it != m_fingerHeldKey.end())
|
|
|
|
|
{
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(it->second, false);
|
2025-12-16 21:51:41 -05:00
|
|
|
m_fingerHeldKey.erase(it);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
DualScancode pedal = (y < 0.575f) ? m_touchThrottle : m_touchBrake;
|
2025-12-16 21:51:41 -05:00
|
|
|
m_fingerHeldKey[tf.fingerId] = pedal;
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(pedal, true);
|
2025-12-16 21:51:41 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Held D-pad zone (left-middle): press one of the arrow keys based on direction.
|
|
|
|
|
const bool inDpadZone = (x < 0.45f && y >= 0.35f && y <= 0.75f);
|
|
|
|
|
if (!inDpadZone)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (!down)
|
|
|
|
|
{
|
|
|
|
|
auto it = m_fingerHeldDir.find(tf.fingerId);
|
|
|
|
|
if (it != m_fingerHeldDir.end())
|
|
|
|
|
{
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(it->second, false);
|
2025-12-16 21:51:41 -05:00
|
|
|
m_fingerHeldDir.erase(it);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Determine direction relative to center of the d-pad zone.
|
|
|
|
|
const float cx = 0.225f;
|
|
|
|
|
const float cy = 0.55f;
|
|
|
|
|
const float dx = x - cx;
|
|
|
|
|
const float dy = y - cy;
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
DualScancode dir;
|
2025-12-16 21:51:41 -05:00
|
|
|
if (std::abs(dx) > std::abs(dy))
|
2025-12-17 04:44:30 -05:00
|
|
|
dir = (dx < 0.0f) ? m_touchJoyLeft : m_touchJoyRight;
|
2025-12-16 21:51:41 -05:00
|
|
|
else
|
2025-12-17 04:44:30 -05:00
|
|
|
dir = (dy < 0.0f) ? m_touchJoyUp : m_touchJoyDown;
|
2025-12-16 21:51:41 -05:00
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
if (dir.a != SDL_SCANCODE_UNKNOWN || dir.b != SDL_SCANCODE_UNKNOWN)
|
2025-12-16 21:51:41 -05:00
|
|
|
{
|
|
|
|
|
m_fingerHeldDir[tf.fingerId] = dir;
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(dir, true);
|
2025-12-16 21:51:41 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf)
|
|
|
|
|
{
|
2025-12-19 02:08:24 -05:00
|
|
|
if (UseVirtualWheel())
|
|
|
|
|
{
|
|
|
|
|
if (m_wheelFingerActive && tf.fingerId == m_wheelFinger)
|
|
|
|
|
{
|
|
|
|
|
SetVirtualSteerFromEncoded(tf.x);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 15:05:51 -05:00
|
|
|
if (m_gunTouchEnabled)
|
|
|
|
|
{
|
|
|
|
|
if (m_gunFingerActive && tf.fingerId == m_gunFinger)
|
|
|
|
|
{
|
|
|
|
|
SetMousePosFromNormalized(tf.x, tf.y);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
// Update held d-pad direction.
|
|
|
|
|
auto it = m_fingerHeldDir.find(tf.fingerId);
|
|
|
|
|
if (it == m_fingerHeldDir.end())
|
|
|
|
|
{
|
|
|
|
|
// Update held pedal if this finger is a pedal touch.
|
|
|
|
|
auto itKey = m_fingerHeldKey.find(tf.fingerId);
|
|
|
|
|
if (itKey == m_fingerHeldKey.end())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Release previous pedal and re-evaluate based on new Y position.
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(itKey->second, false);
|
2025-12-16 21:51:41 -05:00
|
|
|
m_fingerHeldKey.erase(itKey);
|
|
|
|
|
HandleTouch(tf, true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release previous direction and compute a new one.
|
2025-12-17 04:44:30 -05:00
|
|
|
SetKeys(it->second, false);
|
2025-12-16 21:51:41 -05:00
|
|
|
m_fingerHeldDir.erase(it);
|
|
|
|
|
HandleTouch(tf, true);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 15:05:51 -05:00
|
|
|
int AndroidInputSystem::GetNumMice()
|
|
|
|
|
{
|
|
|
|
|
return m_gunTouchEnabled ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const MouseDetails* AndroidInputSystem::GetMouseDetails(int mseNum)
|
|
|
|
|
{
|
|
|
|
|
if (!m_gunTouchEnabled)
|
|
|
|
|
return nullptr;
|
|
|
|
|
if (mseNum == ANY_MOUSE || mseNum == 0)
|
|
|
|
|
return &m_mouseDetails;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int AndroidInputSystem::GetMouseAxisValue(int mseNum, int axisNum)
|
|
|
|
|
{
|
|
|
|
|
if (!m_gunTouchEnabled)
|
|
|
|
|
return 0;
|
|
|
|
|
if (mseNum != ANY_MOUSE && mseNum != 0)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
switch (axisNum)
|
|
|
|
|
{
|
|
|
|
|
case AXIS_X: return m_mouseX;
|
|
|
|
|
case AXIS_Y: return m_mouseY;
|
|
|
|
|
default: return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int AndroidInputSystem::GetMouseWheelDir(int mseNum)
|
|
|
|
|
{
|
|
|
|
|
if (!m_gunTouchEnabled)
|
|
|
|
|
return 0;
|
|
|
|
|
if (mseNum != ANY_MOUSE && mseNum != 0)
|
|
|
|
|
return 0;
|
|
|
|
|
return m_mouseWheelDir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::IsMouseButPressed(int mseNum, int butNum)
|
|
|
|
|
{
|
|
|
|
|
if (!m_gunTouchEnabled)
|
|
|
|
|
return false;
|
|
|
|
|
if (mseNum != ANY_MOUSE && mseNum != 0)
|
|
|
|
|
return false;
|
|
|
|
|
if (butNum < 0 || butNum >= kMouseButtons)
|
|
|
|
|
return false;
|
|
|
|
|
return m_mouseButtons[butNum] != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::SetMouseButton(int butNum, bool down)
|
|
|
|
|
{
|
|
|
|
|
if (butNum < 0 || butNum >= kMouseButtons)
|
|
|
|
|
return;
|
|
|
|
|
m_mouseButtons[butNum] = down ? 1 : 0;
|
|
|
|
|
if (!down)
|
|
|
|
|
m_mouseButtonPulseUntilMs[butNum] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::PulseMouseButton(int butNum, uint32_t durationMs)
|
|
|
|
|
{
|
|
|
|
|
if (butNum < 0 || butNum >= kMouseButtons)
|
|
|
|
|
return;
|
|
|
|
|
m_mouseButtons[butNum] = 1;
|
|
|
|
|
m_mouseButtonPulseUntilMs[butNum] = SDL_GetTicks() + durationMs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::SetMousePosFromNormalized(float x, float y)
|
|
|
|
|
{
|
|
|
|
|
// The emulator polls inputs with a fixed display geometry (currently 496x384).
|
|
|
|
|
// Use the same coordinate system so the core's mouse/lightgun normalization works.
|
|
|
|
|
constexpr int w = 496;
|
|
|
|
|
constexpr int h = 384;
|
|
|
|
|
const int px = (int)std::lround(std::clamp(x, 0.0f, 1.0f) * (float)(w - 1));
|
|
|
|
|
const int py = (int)std::lround(std::clamp(y, 0.0f, 1.0f) * (float)(h - 1));
|
|
|
|
|
m_mouseX = std::clamp(px, 0, w - 1);
|
|
|
|
|
m_mouseY = std::clamp(py, 0, h - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::CloseControllers()
|
|
|
|
|
{
|
|
|
|
|
for (auto& c : m_controllers)
|
|
|
|
|
{
|
|
|
|
|
if (c.controller)
|
|
|
|
|
{
|
|
|
|
|
SDL_GameControllerClose(c.controller);
|
|
|
|
|
c.controller = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
m_controllers.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void AndroidInputSystem::RefreshControllers()
|
|
|
|
|
{
|
|
|
|
|
CloseControllers();
|
|
|
|
|
|
|
|
|
|
const int n = SDL_NumJoysticks();
|
|
|
|
|
for (int i = 0; i < n; i++)
|
|
|
|
|
{
|
|
|
|
|
if (!SDL_IsGameController(i))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
SDL_GameController* controller = SDL_GameControllerOpen(i);
|
|
|
|
|
if (!controller)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
SDL_Joystick* js = SDL_GameControllerGetJoystick(controller);
|
|
|
|
|
if (!js)
|
|
|
|
|
{
|
|
|
|
|
SDL_GameControllerClose(controller);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ControllerState state;
|
|
|
|
|
state.controller = controller;
|
|
|
|
|
state.instanceId = SDL_JoystickInstanceID(js);
|
|
|
|
|
|
|
|
|
|
std::memset(&state.details, 0, sizeof(state.details));
|
|
|
|
|
const char* name = SDL_GameControllerName(controller);
|
|
|
|
|
if (!name) name = "GameController";
|
|
|
|
|
std::strncpy(state.details.name, name, MAX_NAME_LENGTH);
|
|
|
|
|
state.details.name[MAX_NAME_LENGTH] = '\0';
|
|
|
|
|
|
|
|
|
|
// Align with the desktop SDL "gamecontroller" path.
|
|
|
|
|
state.details.numAxes = 6;
|
|
|
|
|
state.details.numPOVs = 4;
|
|
|
|
|
state.details.numButtons = 17;
|
|
|
|
|
state.details.hasFFeedback = false;
|
|
|
|
|
|
|
|
|
|
for (int a = 0; a < NUM_JOY_AXES; a++)
|
|
|
|
|
{
|
|
|
|
|
state.details.hasAxis[a] = false;
|
|
|
|
|
state.details.axisHasFF[a] = false;
|
|
|
|
|
std::strncpy(state.details.axisName[a], GetDefaultAxisName(a), MAX_NAME_LENGTH);
|
|
|
|
|
state.details.axisName[a][MAX_NAME_LENGTH] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state.details.hasAxis[AXIS_X] = true;
|
|
|
|
|
state.details.hasAxis[AXIS_Y] = true;
|
|
|
|
|
state.details.hasAxis[AXIS_Z] = true;
|
|
|
|
|
state.details.hasAxis[AXIS_RX] = true;
|
|
|
|
|
state.details.hasAxis[AXIS_RY] = true;
|
|
|
|
|
state.details.hasAxis[AXIS_RZ] = true;
|
|
|
|
|
|
|
|
|
|
m_controllers.push_back(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int AndroidInputSystem::GetNumJoysticks()
|
|
|
|
|
{
|
2025-12-19 02:08:24 -05:00
|
|
|
if (UseVirtualWheel())
|
|
|
|
|
return 1;
|
2025-12-18 15:05:51 -05:00
|
|
|
return static_cast<int>(m_controllers.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum)
|
|
|
|
|
{
|
2025-12-19 02:08:24 -05:00
|
|
|
if (UseVirtualWheel())
|
|
|
|
|
{
|
|
|
|
|
if (joyNum == ANY_JOYSTICK || joyNum == 0)
|
|
|
|
|
return &m_virtualJoyDetails;
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 15:05:51 -05:00
|
|
|
if (joyNum < 0 || joyNum >= static_cast<int>(m_controllers.size()))
|
|
|
|
|
return nullptr;
|
|
|
|
|
return &m_controllers[static_cast<size_t>(joyNum)].details;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int AndroidInputSystem::AxisValueFor(const ControllerState& c, int axisNum) const
|
|
|
|
|
{
|
|
|
|
|
if (!c.controller)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
switch (axisNum)
|
|
|
|
|
{
|
|
|
|
|
case AXIS_X: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTX);
|
|
|
|
|
case AXIS_Y: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_LEFTY);
|
|
|
|
|
case AXIS_Z: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
|
|
|
|
|
case AXIS_RX: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTX);
|
|
|
|
|
case AXIS_RY: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_RIGHTY);
|
|
|
|
|
case AXIS_RZ: return (int)SDL_GameControllerGetAxis(c.controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
|
|
|
|
|
default: return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::PovPressedFor(const ControllerState& c, int povDir) const
|
|
|
|
|
{
|
|
|
|
|
if (!c.controller)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
switch (povDir)
|
|
|
|
|
{
|
|
|
|
|
case POV_UP: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_UP) != 0;
|
|
|
|
|
case POV_DOWN: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0;
|
|
|
|
|
case POV_LEFT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0;
|
|
|
|
|
case POV_RIGHT: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0;
|
|
|
|
|
default: return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::ButtonPressedFor(const ControllerState& c, int butNum) const
|
|
|
|
|
{
|
|
|
|
|
if (!c.controller)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Match the desktop SDLInputSystem "useGameController" mapping.
|
|
|
|
|
switch (butNum)
|
|
|
|
|
{
|
|
|
|
|
case 0: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_A) != 0;
|
|
|
|
|
case 1: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_B) != 0;
|
|
|
|
|
case 2: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_X) != 0;
|
|
|
|
|
case 3: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_Y) != 0;
|
|
|
|
|
case 4: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0;
|
|
|
|
|
case 5: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0;
|
|
|
|
|
case 6: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_BACK) != 0;
|
|
|
|
|
case 7: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_START) != 0;
|
|
|
|
|
case 8: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0;
|
|
|
|
|
case 9: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0;
|
|
|
|
|
case 10: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE1) != 0;
|
|
|
|
|
case 11: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE2) != 0;
|
|
|
|
|
case 12: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_GUIDE) != 0;
|
|
|
|
|
case 13: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE3) != 0;
|
|
|
|
|
case 14: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_PADDLE4) != 0;
|
|
|
|
|
case 15: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_MISC1) != 0;
|
|
|
|
|
case 16: return SDL_GameControllerGetButton(c.controller, SDL_CONTROLLER_BUTTON_TOUCHPAD) != 0;
|
|
|
|
|
default: return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int AndroidInputSystem::GetJoyAxisValue(int joyNum, int axisNum)
|
|
|
|
|
{
|
2025-12-19 02:08:24 -05:00
|
|
|
if (UseVirtualWheel())
|
|
|
|
|
{
|
|
|
|
|
if (axisNum == AXIS_X)
|
|
|
|
|
return m_virtualJoyX;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-18 15:05:51 -05:00
|
|
|
if (m_controllers.empty())
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (joyNum == ANY_JOYSTICK)
|
|
|
|
|
{
|
|
|
|
|
int best = 0;
|
|
|
|
|
for (const auto& c : m_controllers)
|
|
|
|
|
{
|
|
|
|
|
const int v = AxisValueFor(c, axisNum);
|
|
|
|
|
if (std::abs(v) > std::abs(best))
|
|
|
|
|
best = v;
|
|
|
|
|
}
|
|
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (joyNum < 0 || joyNum >= static_cast<int>(m_controllers.size()))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
return AxisValueFor(m_controllers[static_cast<size_t>(joyNum)], axisNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::IsJoyPOVInDir(int joyNum, int /*povNum*/, int povDir)
|
|
|
|
|
{
|
|
|
|
|
if (m_controllers.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (joyNum == ANY_JOYSTICK)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& c : m_controllers)
|
|
|
|
|
{
|
|
|
|
|
if (PovPressedFor(c, povDir))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (joyNum < 0 || joyNum >= static_cast<int>(m_controllers.size()))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return PovPressedFor(m_controllers[static_cast<size_t>(joyNum)], povDir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::IsJoyButPressed(int joyNum, int butNum)
|
|
|
|
|
{
|
|
|
|
|
if (m_controllers.empty())
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (joyNum == ANY_JOYSTICK)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& c : m_controllers)
|
|
|
|
|
{
|
|
|
|
|
if (ButtonPressedFor(c, butNum))
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (joyNum < 0 || joyNum >= static_cast<int>(m_controllers.size()))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return ButtonPressedFor(m_controllers[static_cast<size_t>(joyNum)], butNum);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
SDL_Scancode AndroidInputSystem::ScancodeFromSupermodelKeyName(const char* keyName) const
|
|
|
|
|
{
|
|
|
|
|
if (!keyName || !keyName[0])
|
|
|
|
|
return SDL_SCANCODE_UNKNOWN;
|
|
|
|
|
|
|
|
|
|
// Fast-path common names used by our defaults.
|
|
|
|
|
if (strcmp(keyName, "UP") == 0) return SDL_SCANCODE_UP;
|
|
|
|
|
if (strcmp(keyName, "DOWN") == 0) return SDL_SCANCODE_DOWN;
|
|
|
|
|
if (strcmp(keyName, "LEFT") == 0) return SDL_SCANCODE_LEFT;
|
|
|
|
|
if (strcmp(keyName, "RIGHT") == 0) return SDL_SCANCODE_RIGHT;
|
|
|
|
|
if (strcmp(keyName, "RETURN") == 0) return SDL_SCANCODE_RETURN;
|
|
|
|
|
if (strcmp(keyName, "ESCAPE") == 0) return SDL_SCANCODE_ESCAPE;
|
|
|
|
|
if (strcmp(keyName, "SPACE") == 0) return SDL_SCANCODE_SPACE;
|
|
|
|
|
|
|
|
|
|
// Digits and letters match SDL key names.
|
|
|
|
|
if ((keyName[0] >= '0' && keyName[0] <= '9') && keyName[1] == '\0')
|
|
|
|
|
return SDL_GetScancodeFromName(keyName);
|
|
|
|
|
if ((keyName[0] >= 'A' && keyName[0] <= 'Z') && keyName[1] == '\0')
|
|
|
|
|
return SDL_GetScancodeFromName(keyName);
|
|
|
|
|
|
|
|
|
|
// Function keys.
|
|
|
|
|
if (keyName[0] == 'F' && keyName[1] >= '1' && keyName[1] <= '9')
|
|
|
|
|
return SDL_GetScancodeFromName(keyName);
|
|
|
|
|
|
|
|
|
|
// Try SDL's name lookup as a fallback (works for many names, albeit with different casing).
|
|
|
|
|
return SDL_GetScancodeFromName(keyName);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-17 04:44:30 -05:00
|
|
|
SDL_Scancode AndroidInputSystem::ParseFirstKeyboardScancode(const std::string& mapping, const AndroidInputSystem& self)
|
|
|
|
|
{
|
|
|
|
|
// mapping examples:
|
|
|
|
|
// - "KEY_F2"
|
|
|
|
|
// - "KEY_RIGHT,JOY1_XAXIS_POS"
|
|
|
|
|
// - "KEY_ALT+KEY_R"
|
|
|
|
|
// - "!KEY_ALT+KEY_P"
|
|
|
|
|
std::string s = mapping;
|
|
|
|
|
size_t start = 0;
|
|
|
|
|
while (start < s.size())
|
|
|
|
|
{
|
|
|
|
|
// Find next token boundary.
|
|
|
|
|
while (start < s.size() && (s[start] == ' ' || s[start] == '\t' || s[start] == ',' || s[start] == '+'))
|
|
|
|
|
start++;
|
|
|
|
|
if (start >= s.size()) break;
|
|
|
|
|
|
|
|
|
|
size_t end = start;
|
|
|
|
|
while (end < s.size() && s[end] != ' ' && s[end] != '\t' && s[end] != ',' && s[end] != '+')
|
|
|
|
|
end++;
|
|
|
|
|
|
|
|
|
|
std::string tok = s.substr(start, end - start);
|
|
|
|
|
if (!tok.empty() && tok[0] == '!')
|
|
|
|
|
tok.erase(tok.begin());
|
|
|
|
|
|
|
|
|
|
if (tok.rfind("KEY_", 0) == 0 && tok.size() > 4)
|
|
|
|
|
{
|
|
|
|
|
const std::string keyName = tok.substr(4);
|
|
|
|
|
return self.ScancodeFromSupermodelKeyName(keyName.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start = end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return SDL_SCANCODE_UNKNOWN;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:51:41 -05:00
|
|
|
int AndroidInputSystem::GetKeyIndex(const char* keyName)
|
|
|
|
|
{
|
|
|
|
|
const SDL_Scancode sc = ScancodeFromSupermodelKeyName(keyName);
|
|
|
|
|
return (sc == SDL_SCANCODE_UNKNOWN) ? -1 : (int)sc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char* AndroidInputSystem::GetKeyName(int keyIndex)
|
|
|
|
|
{
|
|
|
|
|
if (keyIndex <= SDL_SCANCODE_UNKNOWN || keyIndex >= SDL_NUM_SCANCODES)
|
|
|
|
|
return "";
|
|
|
|
|
return SDL_GetScancodeName(static_cast<SDL_Scancode>(keyIndex));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool AndroidInputSystem::IsKeyPressed(int /*kbdNum*/, int keyIndex)
|
|
|
|
|
{
|
|
|
|
|
if (keyIndex <= SDL_SCANCODE_UNKNOWN || keyIndex >= SDL_NUM_SCANCODES)
|
|
|
|
|
return false;
|
|
|
|
|
return m_keys[static_cast<size_t>(keyIndex)] != 0;
|
|
|
|
|
}
|