304 lines
8.1 KiB
C++
304 lines
8.1 KiB
C++
// Copyright 2010 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "InputCommon/ControllerInterface/XInput/XInput.h"
|
|
|
|
#ifndef XINPUT_GAMEPAD_GUIDE
|
|
#define XINPUT_GAMEPAD_GUIDE 0x0400
|
|
#endif
|
|
|
|
namespace ciface::XInput
|
|
{
|
|
struct ButtonDef
|
|
{
|
|
const char* name;
|
|
WORD bitmask;
|
|
};
|
|
static constexpr std::array<ButtonDef, 15> 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 constexpr std::array named_triggers{"Trigger L", "Trigger R"};
|
|
|
|
static constexpr std::array named_axes{"Left X", "Left Y", "Right X", "Right Y"};
|
|
|
|
static constexpr std::array named_motors{"Motor L", "Motor R"};
|
|
|
|
class Button final : public Core::Device::Input
|
|
{
|
|
public:
|
|
Button(u8 index, const WORD& buttons) : m_buttons(buttons), m_index(index) {}
|
|
std::string GetName() const override { return named_buttons[m_index].name; }
|
|
ControlState GetState() const override
|
|
{
|
|
return (m_buttons & named_buttons[m_index].bitmask) > 0;
|
|
}
|
|
|
|
private:
|
|
const WORD& m_buttons;
|
|
const u8 m_index;
|
|
};
|
|
|
|
class Axis final : public Core::Device::Input
|
|
{
|
|
public:
|
|
Axis(u8 index, const SHORT& axis, SHORT range) : m_axis(axis), m_range(range), m_index(index) {}
|
|
std::string GetName() const override
|
|
{
|
|
return std::string(named_axes[m_index]) + (m_range < 0 ? '-' : '+');
|
|
}
|
|
ControlState GetState() const override { return ControlState(m_axis) / m_range; }
|
|
|
|
private:
|
|
const SHORT& m_axis;
|
|
const SHORT m_range;
|
|
const u8 m_index;
|
|
};
|
|
|
|
class Trigger final : public Core::Device::Input
|
|
{
|
|
public:
|
|
Trigger(u8 index, const BYTE& trigger, BYTE range)
|
|
: m_trigger(trigger), m_range(range), m_index(index)
|
|
{
|
|
}
|
|
std::string GetName() const override { return named_triggers[m_index]; }
|
|
ControlState GetState() const override { return ControlState(m_trigger) / m_range; }
|
|
|
|
private:
|
|
const BYTE& m_trigger;
|
|
const BYTE m_range;
|
|
const u8 m_index;
|
|
};
|
|
|
|
class Motor final : public Core::Device::Output
|
|
{
|
|
public:
|
|
Motor(u8 index, Device* parent, WORD& motor, WORD range)
|
|
: m_motor(motor), m_range(range), m_index(index), m_parent(parent)
|
|
{
|
|
}
|
|
|
|
std::string GetName() const override { return named_motors[m_index]; }
|
|
void SetState(ControlState state) override
|
|
{
|
|
const auto old_value = m_motor;
|
|
m_motor = (WORD)(state * m_range);
|
|
|
|
// Only update if the state changed.
|
|
if (m_motor != old_value)
|
|
m_parent->UpdateMotors();
|
|
}
|
|
|
|
private:
|
|
WORD& m_motor;
|
|
const WORD m_range;
|
|
const u8 m_index;
|
|
Device* m_parent;
|
|
};
|
|
|
|
class Battery final : public Core::Device::Input
|
|
{
|
|
public:
|
|
Battery(const ControlState* level) : m_level(*level) {}
|
|
std::string GetName() const override { return "Battery"; }
|
|
ControlState GetState() const override { return m_level; }
|
|
bool IsDetectable() const override { return false; }
|
|
|
|
private:
|
|
const ControlState& m_level;
|
|
};
|
|
|
|
static HMODULE hXInput = nullptr;
|
|
|
|
typedef decltype(&XInputGetCapabilities) XInputGetCapabilities_t;
|
|
typedef decltype(&XInputSetState) XInputSetState_t;
|
|
typedef decltype(&XInputGetState) XInputGetState_t;
|
|
typedef decltype(&XInputGetBatteryInformation) XInputGetBatteryInformation_t;
|
|
|
|
static XInputGetCapabilities_t PXInputGetCapabilities = nullptr;
|
|
static XInputSetState_t PXInputSetState = nullptr;
|
|
static XInputGetState_t PXInputGetState = nullptr;
|
|
static XInputGetBatteryInformation_t PXInputGetBatteryInformation = nullptr;
|
|
|
|
static bool s_have_guide_button = 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");
|
|
PXInputGetBatteryInformation =
|
|
(XInputGetBatteryInformation_t)::GetProcAddress(hXInput, "XInputGetBatteryInformation");
|
|
|
|
// 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);
|
|
|
|
s_have_guide_button = PXInputGetState != nullptr;
|
|
|
|
if (!PXInputGetState)
|
|
PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, "XInputGetState");
|
|
|
|
if (!PXInputGetCapabilities || !PXInputSetState || !PXInputGetState ||
|
|
!PXInputGetBatteryInformation)
|
|
{
|
|
::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<Device>(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
|
|
|
|
// Buttons.
|
|
for (size_t i = 0; i != size(named_buttons); ++i)
|
|
{
|
|
// Only add guide button if we have the 100 ordinal XInputGetState.
|
|
if (named_buttons[i].bitmask == XINPUT_GAMEPAD_GUIDE && !s_have_guide_button)
|
|
continue;
|
|
|
|
AddInput(new Button(u8(i), m_state_in.Gamepad.wButtons));
|
|
}
|
|
|
|
// Triggers.
|
|
for (size_t i = 0; i != size(named_triggers); ++i)
|
|
AddInput(new Trigger(u8(i), (&m_state_in.Gamepad.bLeftTrigger)[i], 255));
|
|
|
|
// Axes.
|
|
for (size_t i = 0; i != size(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(u8(i), ax, -32768));
|
|
AddInput(new Axis(u8(i), ax, 32767));
|
|
}
|
|
|
|
// Rumble motors.
|
|
for (size_t i = 0; i != size(named_motors); ++i)
|
|
AddOutput(new Motor(u8(i), this, (&m_state_out.wLeftMotorSpeed)[i], 65535));
|
|
|
|
AddInput(new Battery(&m_battery_level));
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
Core::DeviceRemoval Device::UpdateInput()
|
|
{
|
|
PXInputGetState(m_index, &m_state_in);
|
|
|
|
XINPUT_BATTERY_INFORMATION battery_info = {};
|
|
if (SUCCEEDED(PXInputGetBatteryInformation(m_index, BATTERY_DEVTYPE_GAMEPAD, &battery_info)))
|
|
{
|
|
switch (battery_info.BatteryType)
|
|
{
|
|
case BATTERY_TYPE_DISCONNECTED:
|
|
case BATTERY_TYPE_UNKNOWN:
|
|
m_battery_level = 0;
|
|
break;
|
|
case BATTERY_TYPE_WIRED:
|
|
m_battery_level = BATTERY_INPUT_MAX_VALUE;
|
|
break;
|
|
default:
|
|
m_battery_level =
|
|
battery_info.BatteryLevel / ControlState(BATTERY_LEVEL_FULL) * BATTERY_INPUT_MAX_VALUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Core::DeviceRemoval::Keep;
|
|
}
|
|
|
|
void Device::UpdateMotors()
|
|
{
|
|
PXInputSetState(m_index, &m_state_out);
|
|
}
|
|
|
|
std::optional<int> Device::GetPreferredId() const
|
|
{
|
|
return m_index;
|
|
}
|
|
|
|
} // namespace ciface::XInput
|