#include "android_input_system.h" #include #include #include AndroidInputSystem::AndroidInputSystem() : CInputSystem("android-sdl"), m_keys(SDL_NUM_SCANCODES, 0) { 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; 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; } 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)); } void AndroidInputSystem::SetVirtualWheelEnabled(bool enabled) { if (m_virtualWheelEnabled == enabled) return; m_virtualWheelEnabled = enabled; m_wheelFingerActive = false; m_wheelFinger = 0; m_virtualJoyX = 0; } void AndroidInputSystem::SetVirtualShifterMode(bool shift4, bool shiftUpDown) { m_virtualShifterShift4 = shift4; m_virtualShifterUpDown = shiftUpDown; m_lastVirtualGear = -1; } 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); } void AndroidInputSystem::ApplyConfig(const Util::Config::Node& config) { auto get = [&](const char* key, const char* fallback) -> std::string { return config[key].ValueAsDefault(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); m_touchBrake.a = keySc(get("InputBrake", "KEY_X"), SDL_SCANCODE_X); m_touchShiftUp.a = keySc(get("InputGearShiftUp", "KEY_I"), SDL_SCANCODE_I); m_touchShiftDown.a = keySc(get("InputGearShiftDown", "KEY_K"), SDL_SCANCODE_K); m_touchShift1.a = keySc(get("InputGearShift1", "KEY_7"), SDL_SCANCODE_7); m_touchShift2.a = keySc(get("InputGearShift2", "KEY_8"), SDL_SCANCODE_8); m_touchShift3.a = keySc(get("InputGearShift3", "KEY_9"), SDL_SCANCODE_9); m_touchShift4.a = keySc(get("InputGearShift4", "KEY_0"), SDL_SCANCODE_0); m_touchShiftN.a = keySc(get("InputGearShiftN", "KEY_6"), SDL_SCANCODE_6); } bool AndroidInputSystem::InitializeSystem() { std::fill(m_keys.begin(), m_keys.end(), 0); m_fingerHeldDir.clear(); m_fingerHeldKey.clear(); m_pulseUntilMs.clear(); 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; m_wheelFingerActive = false; m_wheelFinger = 0; m_virtualJoyX = 0; m_lastVirtualGear = -1; SDL_GameControllerEventState(SDL_ENABLE); RefreshControllers(); return true; } void AndroidInputSystem::SetKey(SDL_Scancode sc, bool down) { if (sc <= SDL_SCANCODE_UNKNOWN || sc >= SDL_NUM_SCANCODES) return; m_keys[static_cast(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; } 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); } bool AndroidInputSystem::Poll() { SDL_GameControllerUpdate(); const uint32_t now = SDL_GetTicks(); for (auto it = m_pulseUntilMs.begin(); it != m_pulseUntilMs.end();) { if (now >= it->second) { SetKey(static_cast(it->first), false); it = m_pulseUntilMs.erase(it); } else { ++it; } } for (int i = 0; i < kMouseButtons; i++) { if (m_mouseButtonPulseUntilMs[i] != 0 && now >= m_mouseButtonPulseUntilMs[i]) { m_mouseButtonPulseUntilMs[i] = 0; m_mouseButtons[i] = 0; } } return true; } void AndroidInputSystem::HandleEvent(const SDL_Event& ev) { switch (ev.type) { case SDL_CONTROLLERDEVICEADDED: case SDL_CONTROLLERDEVICEREMOVED: case SDL_CONTROLLERDEVICEREMAPPED: RefreshControllers(); break; 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) { // 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. DualScancode sc; switch (btn.button) { case SDL_CONTROLLER_BUTTON_START: sc = m_touchStart; break; case SDL_CONTROLLER_BUTTON_BACK: sc = m_touchCoin; break; case SDL_CONTROLLER_BUTTON_GUIDE: sc = m_touchService; break; 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; default: break; } if (sc.a == SDL_SCANCODE_UNKNOWN && sc.b == SDL_SCANCODE_UNKNOWN) return; // For coin/start/test, prefer a pulse to avoid repeating while held. 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); else SetKeys(sc, down); } void AndroidInputSystem::HandleTouch(const SDL_TouchFingerEvent& tf, bool down) { const float x = tf.x; const float y = tf.y; // Virtual manual shifter (racing games): encoded in tf.x/tf.y from Java and keyed by a fixed fingerId. if ((m_virtualShifterShift4 || m_virtualShifterUpDown) && tf.fingerId == 1108) { if (!down) return; if (m_virtualShifterShift4) { const float dx = x - 0.5f; const float dy = y - 0.5f; const bool inNeutral = (std::abs(dx) < 0.18f && std::abs(dy) < 0.18f); int gear = m_lastVirtualGear; if (inNeutral) { gear = 0; } else { const bool left = dx < 0.0f; const bool upper = dy < 0.0f; if (left && upper) gear = 1; else if (left && !upper) gear = 2; else if (!left && upper) gear = 3; else gear = 4; } if (gear != m_lastVirtualGear) { switch (gear) { case 0: PulseKeys(m_touchShiftN, 120); break; case 1: PulseKeys(m_touchShift1, 120); break; case 2: PulseKeys(m_touchShift2, 120); break; case 3: PulseKeys(m_touchShift3, 120); break; case 4: PulseKeys(m_touchShift4, 120); break; default: break; } m_lastVirtualGear = gear; } return; } // Up/down shifter: tap upper/lower half. if (m_virtualShifterUpDown) { if (y < 0.5f) PulseKeys(m_touchShiftUp, 120); else PulseKeys(m_touchShiftDown, 120); return; } } // 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; } } } // Tap zones (momentary): // - Bottom-left: Coin (KEY_5) // - Bottom-middle: Start (KEY_1) // - Top-left: Service (KEY_F1) // - Top-right: Test (KEY_F2) if (down) { if (x < 0.25f && y > 0.75f) { PulseKeys(m_touchCoin, 120); return; } if (x > 0.40f && x < 0.60f && y > 0.75f) { PulseKeys(m_touchStart, 120); return; } if (x < 0.25f && y < 0.25f) { PulseKeys(m_touchService, 120); return; } if (x > 0.75f && y < 0.25f) { PulseKeys(m_touchTest, 120); return; } } // 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; } // 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()) { SetKeys(it->second, false); m_fingerHeldKey.erase(it); } return; } DualScancode pedal = (y < 0.575f) ? m_touchThrottle : m_touchBrake; m_fingerHeldKey[tf.fingerId] = pedal; SetKeys(pedal, true); 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()) { SetKeys(it->second, false); 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; DualScancode dir; if (std::abs(dx) > std::abs(dy)) dir = (dx < 0.0f) ? m_touchJoyLeft : m_touchJoyRight; else dir = (dy < 0.0f) ? m_touchJoyUp : m_touchJoyDown; if (dir.a != SDL_SCANCODE_UNKNOWN || dir.b != SDL_SCANCODE_UNKNOWN) { m_fingerHeldDir[tf.fingerId] = dir; SetKeys(dir, true); } } void AndroidInputSystem::HandleTouchMotion(const SDL_TouchFingerEvent& tf) { if (m_virtualShifterShift4 && tf.fingerId == 1108) { // Avoid accidental neutral when passing through center during motion. const float dx = tf.x - 0.5f; const float dy = tf.y - 0.5f; if (std::abs(dx) < 0.18f && std::abs(dy) < 0.18f) return; const bool left = dx < 0.0f; const bool upper = dy < 0.0f; int gear; if (left && upper) gear = 1; else if (left && !upper) gear = 2; else if (!left && upper) gear = 3; else gear = 4; if (gear != m_lastVirtualGear) { switch (gear) { case 1: PulseKeys(m_touchShift1, 120); break; case 2: PulseKeys(m_touchShift2, 120); break; case 3: PulseKeys(m_touchShift3, 120); break; case 4: PulseKeys(m_touchShift4, 120); break; default: break; } m_lastVirtualGear = gear; } return; } if (UseVirtualWheel()) { if (m_wheelFingerActive && tf.fingerId == m_wheelFinger) { SetVirtualSteerFromEncoded(tf.x); return; } } if (m_gunTouchEnabled) { if (m_gunFingerActive && tf.fingerId == m_gunFinger) { SetMousePosFromNormalized(tf.x, tf.y); } return; } // 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. SetKeys(itKey->second, false); m_fingerHeldKey.erase(itKey); HandleTouch(tf, true); return; } // Release previous direction and compute a new one. SetKeys(it->second, false); m_fingerHeldDir.erase(it); HandleTouch(tf, true); } 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() { if (UseVirtualWheel()) return 1; return static_cast(m_controllers.size()); } const JoyDetails* AndroidInputSystem::GetJoyDetails(int joyNum) { if (UseVirtualWheel()) { if (joyNum == ANY_JOYSTICK || joyNum == 0) return &m_virtualJoyDetails; return nullptr; } if (joyNum < 0 || joyNum >= static_cast(m_controllers.size())) return nullptr; return &m_controllers[static_cast(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) { if (UseVirtualWheel()) { if (axisNum == AXIS_X) return m_virtualJoyX; return 0; } 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(m_controllers.size())) return 0; return AxisValueFor(m_controllers[static_cast(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(m_controllers.size())) return false; return PovPressedFor(m_controllers[static_cast(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(m_controllers.size())) return false; return ButtonPressedFor(m_controllers[static_cast(joyNum)], butNum); } 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); } 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; } 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(keyIndex)); } bool AndroidInputSystem::IsKeyPressed(int /*kbdNum*/, int keyIndex) { if (keyIndex <= SDL_SCANCODE_UNKNOWN || keyIndex >= SDL_NUM_SCANCODES) return false; return m_keys[static_cast(keyIndex)] != 0; }