// Copyright 2010 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "InputCommon/ControllerInterface/XInput/XInput.h" #ifndef XINPUT_GAMEPAD_GUIDE #define XINPUT_GAMEPAD_GUIDE 0x0400 #endif namespace ciface::XInput { static const struct { const char* const name; const WORD bitmask; } named_buttons[] = {{"Button A", XINPUT_GAMEPAD_A}, {"Button B", XINPUT_GAMEPAD_B}, {"Button X", XINPUT_GAMEPAD_X}, {"Button Y", XINPUT_GAMEPAD_Y}, {"Pad N", XINPUT_GAMEPAD_DPAD_UP}, {"Pad S", XINPUT_GAMEPAD_DPAD_DOWN}, {"Pad W", XINPUT_GAMEPAD_DPAD_LEFT}, {"Pad E", XINPUT_GAMEPAD_DPAD_RIGHT}, {"Start", XINPUT_GAMEPAD_START}, {"Back", XINPUT_GAMEPAD_BACK}, {"Shoulder L", XINPUT_GAMEPAD_LEFT_SHOULDER}, {"Shoulder R", XINPUT_GAMEPAD_RIGHT_SHOULDER}, {"Guide", XINPUT_GAMEPAD_GUIDE}, {"Thumb L", XINPUT_GAMEPAD_LEFT_THUMB}, {"Thumb R", XINPUT_GAMEPAD_RIGHT_THUMB}}; static const char* const named_triggers[] = {"Trigger L", "Trigger R"}; static const char* const named_axes[] = {"Left X", "Left Y", "Right X", "Right Y"}; static const char* const named_motors[] = {"Motor L", "Motor R"}; static HMODULE hXInput = nullptr; typedef decltype(&XInputGetCapabilities) XInputGetCapabilities_t; typedef decltype(&XInputSetState) XInputSetState_t; typedef decltype(&XInputGetState) XInputGetState_t; static XInputGetCapabilities_t PXInputGetCapabilities = nullptr; static XInputSetState_t PXInputSetState = nullptr; static XInputGetState_t PXInputGetState = nullptr; static bool haveGuideButton = false; void Init() { if (!hXInput) { // Try for the most recent version we were compiled against (will only work if running on Win8+) hXInput = ::LoadLibrary(XINPUT_DLL); if (!hXInput) { // Drop back to DXSDK June 2010 version. Requires DX June 2010 redist. hXInput = ::LoadLibrary(TEXT("xinput1_3.dll")); if (!hXInput) { return; } } PXInputGetCapabilities = (XInputGetCapabilities_t)::GetProcAddress(hXInput, "XInputGetCapabilities"); PXInputSetState = (XInputSetState_t)::GetProcAddress(hXInput, "XInputSetState"); // Ordinal 100 is the same as XInputGetState, except it doesn't dummy out the guide // button info. Try loading it and fall back if needed. PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, (LPCSTR)100); if (PXInputGetState) haveGuideButton = true; else PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, "XInputGetState"); if (!PXInputGetCapabilities || !PXInputSetState || !PXInputGetState) { ::FreeLibrary(hXInput); hXInput = nullptr; return; } } } void PopulateDevices() { if (!hXInput) return; g_controller_interface.RemoveDevice([](const auto* dev) { return dev->GetSource() == "XInput"; }); XINPUT_CAPABILITIES caps; for (int i = 0; i != 4; ++i) if (ERROR_SUCCESS == PXInputGetCapabilities(i, 0, &caps)) g_controller_interface.AddDevice(std::make_shared(caps, i)); } void DeInit() { if (hXInput) { ::FreeLibrary(hXInput); hXInput = nullptr; } } Device::Device(const XINPUT_CAPABILITIES& caps, u8 index) : m_subtype(caps.SubType), m_index(index) { // XInputGetCaps can be broken on some devices, so we'll just ignore it // and assume all gamepad + vibration capabilities are supported // get supported buttons for (int i = 0; i != sizeof(named_buttons) / sizeof(*named_buttons); ++i) { // Only add guide button if we have the 100 ordinal XInputGetState if (!(named_buttons[i].bitmask & XINPUT_GAMEPAD_GUIDE) || haveGuideButton) AddInput(new Button(i, m_state_in.Gamepad.wButtons)); } // get supported triggers for (int i = 0; i != sizeof(named_triggers) / sizeof(*named_triggers); ++i) { AddInput(new Trigger(i, (&m_state_in.Gamepad.bLeftTrigger)[i], 255)); } // get supported axes for (int i = 0; i != sizeof(named_axes) / sizeof(*named_axes); ++i) { const SHORT& ax = (&m_state_in.Gamepad.sThumbLX)[i]; // each axis gets a negative and a positive input instance associated with it AddInput(new Axis(i, ax, -32768)); AddInput(new Axis(i, ax, 32767)); } // get supported motors for (int i = 0; i != sizeof(named_motors) / sizeof(*named_motors); ++i) { AddOutput(new Motor(i, this, (&m_state_out.wLeftMotorSpeed)[i], 65535)); } ZeroMemory(&m_state_in, sizeof(m_state_in)); } std::string Device::GetName() const { switch (m_subtype) { case XINPUT_DEVSUBTYPE_GAMEPAD: return "Gamepad"; case XINPUT_DEVSUBTYPE_WHEEL: return "Wheel"; case XINPUT_DEVSUBTYPE_ARCADE_STICK: return "Arcade Stick"; case XINPUT_DEVSUBTYPE_FLIGHT_STICK: return "Flight Stick"; case XINPUT_DEVSUBTYPE_DANCE_PAD: return "Dance Pad"; case XINPUT_DEVSUBTYPE_GUITAR: return "Guitar"; case XINPUT_DEVSUBTYPE_DRUM_KIT: return "Drum Kit"; default: return "Device"; } } std::string Device::GetSource() const { return "XInput"; } // Update I/O void Device::UpdateInput() { PXInputGetState(m_index, &m_state_in); } void Device::UpdateMotors() { // this if statement is to make rumble work better when multiple ControllerInterfaces are using // the device // only calls XInputSetState if the state changed if (memcmp(&m_state_out, &m_current_state_out, sizeof(m_state_out))) { m_current_state_out = m_state_out; PXInputSetState(m_index, &m_state_out); } } std::optional Device::GetPreferredId() const { return m_index; } // GET name/source/id std::string Device::Button::GetName() const { return named_buttons[m_index].name; } std::string Device::Axis::GetName() const { return std::string(named_axes[m_index]) + (m_range < 0 ? '-' : '+'); } std::string Device::Trigger::GetName() const { return named_triggers[m_index]; } std::string Device::Motor::GetName() const { return named_motors[m_index]; } // GET / SET STATES ControlState Device::Button::GetState() const { return (m_buttons & named_buttons[m_index].bitmask) > 0; } ControlState Device::Trigger::GetState() const { return ControlState(m_trigger) / m_range; } ControlState Device::Axis::GetState() const { return ControlState(m_axis) / m_range; } void Device::Motor::SetState(ControlState state) { m_motor = (WORD)(state * m_range); m_parent->UpdateMotors(); } } // namespace ciface::XInput