Cxbx-Reloaded/src/common/input/XInputPad.cpp

332 lines
10 KiB
C++

// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
// ******************************************************************
// *
// * This file is part of the Cxbx project.
// *
// * Cxbx and Cxbe are free software; you can redistribute them
// * and/or modify them under the terms of the GNU General Public
// * License as published by the Free Software Foundation; either
// * version 2 of the license, or (at your option) any later version.
// *
// * This program is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// * GNU General Public License for more details.
// *
// * You should have recieved a copy of the GNU General Public License
// * along with this program; see the file COPYING.
// * If not, write to the Free Software Foundation, Inc.,
// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA.
// *
// * (c) 2019 ergo720
// *
// * All rights reserved
// *
// ******************************************************************
// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file of Dolphin at https://github.com/dolphin-emu/dolphin/blob/master/license.txt.
// Derived from XInput.cpp of Dolphin emulator
// https://github.com/dolphin-emu/dolphin
#define LOG_PREFIX CXBXR_MODULE::XINP
#include "XInputPad.h"
#include "core\kernel\support\Emu.h"
#include "InputManager.h"
#ifndef XINPUT_GAMEPAD_GUIDE
#define XINPUT_GAMEPAD_GUIDE 0x0400
#endif
// These are defined in the Xinput.h header supplied by Windows, but because we use _WIN32_WINNT=0x0601, they are greyed out.
// If the init function loads succesfully the 1.3 library version, then XInputGetCapabilities can return these subtypes too.
#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK
#define XINPUT_DEVSUBTYPE_UNKNOWN 0x00
#define XINPUT_DEVSUBTYPE_WHEEL 0x02
#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03
#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04
#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05
#define XINPUT_DEVSUBTYPE_GUITAR 0x06
#define XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE 0x07
#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08
#define XINPUT_DEVSUBTYPE_GUITAR_BASS 0x0B
#define XINPUT_DEVSUBTYPE_ARCADE_PAD 0x13
#endif
namespace XInput
{
typedef decltype(&XInputGetCapabilities) XInputGetCapabilities_t;
typedef decltype(&XInputSetState) XInputSetState_t;
typedef decltype(&XInputGetState) XInputGetState_t;
static HMODULE hXInput = nullptr;
static XInputGetCapabilities_t PXInputGetCapabilities = nullptr;
static XInputSetState_t PXInputSetState = nullptr;
static XInputGetState_t PXInputGetState = nullptr;
static bool haveGuideButton = false;
int InitStatus = NOT_INIT;
uint8_t DevicesConnected = 0;
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" };
void Init(std::mutex& Mtx)
{
std::unique_lock<std::mutex> lck(Mtx);
// Load the most appropriate version of the xinput library depending on which Windows OS version we are running on.
// We will try the 9.1.0 version as a last resort since that's the only one that doesn't provide support for the guide button.
// For more info, see MS documentation at https://docs.microsoft.com/en-us/windows/win32/xinput/xinput-versions.
// will only work on Win8/10; provides guide button info
std::string xinput_dll_name = "xinput1_4.dll";
hXInput = ::LoadLibrary(TEXT(xinput_dll_name.c_str()));
if (!hXInput) {
// will only work if the user has installed the June 2010 release of the DirectX SDK; provides guide button info
xinput_dll_name = "xinput1_3.dll";
hXInput = ::LoadLibrary(TEXT(xinput_dll_name.c_str()));
if (!hXInput) {
// will work on Win7; does NOT provide guide button info
xinput_dll_name = "xinput9_1_0.dll";
hXInput = ::LoadLibrary(TEXT(xinput_dll_name.c_str()));
if (!hXInput) {
EmuLog(LOG_LEVEL::ERROR2, "Failed to initialize XInput subsystem!");
InitStatus = INIT_ERROR;
return;
}
}
}
EmuLog(LOG_LEVEL::INFO, "Loaded %s library", xinput_dll_name.c_str());
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) {
EmuLog(LOG_LEVEL::INFO, "Guide button information available");
haveGuideButton = true;
}
else {
EmuLog(LOG_LEVEL::INFO, "Guide button information not available");
PXInputGetState = (XInputGetState_t)::GetProcAddress(hXInput, "XInputGetState");
}
if (!PXInputGetCapabilities || !PXInputSetState || !PXInputGetState)
{
::FreeLibrary(hXInput);
hXInput = nullptr;
EmuLog(LOG_LEVEL::ERROR2, "Failed to find XInput functions!");
InitStatus = INIT_ERROR;
return;
}
InitStatus = INIT_SUCCESS;
}
void DeInit()
{
InitStatus = NOT_INIT;
if (hXInput)
{
::FreeLibrary(hXInput);
hXInput = nullptr;
}
}
void GetDeviceChanges()
{
XINPUT_CAPABILITIES caps;
DWORD ret;
for (uint8_t i = 0, mask = 1; i != 4; ++i, mask <<= 1) {
ret = PXInputGetCapabilities(i, 0, &caps);
if ((ERROR_SUCCESS == ret) && !(DevicesConnected & mask)) {
auto Device = std::make_shared<XDevice>(caps, i);
// only add the device if it has some I/O controls
if (!Device->GetInputs().empty() || !Device->GetOutputs().empty()) {
g_InputDeviceManager.AddDevice(std::move(Device));
}
else {
EmuLog(LOG_LEVEL::INFO, "Rejected XInput device %i. No controls detected", i);
}
DevicesConnected |= mask;
}
else if ((ERROR_DEVICE_NOT_CONNECTED == ret) && (DevicesConnected & mask)) {
g_InputDeviceManager.RemoveDevice([&i](const auto& Device) {
const XDevice* dev = dynamic_cast<const XDevice*>(Device);
return dev && (dev->GetXInputDevice() == i);
});
DevicesConnected &= ~mask;
}
}
}
void PopulateDevices()
{
DevicesConnected = 0;
GetDeviceChanges();
}
XDevice::XDevice(const XINPUT_CAPABILITIES& Capabilities, uint8_t Index) : m_Subtype(Capabilities.SubType), m_Index(Index),
m_dwPacketNumber(0)
{
// 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
AddOutput(new Motor(this, m_state_out.wLeftMotorSpeed, m_state_out.wRightMotorSpeed, 65535));
memset(&m_state_in, 0, sizeof(m_state_in));
}
std::string XDevice::GetDeviceName() 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 XDevice::GetAPI() const
{
return "XInput";
}
uint8_t XDevice::GetXInputDevice() const
{
return m_Index;
}
// Update I/O
bool XDevice::UpdateInput()
{
if (PXInputGetState(m_Index, &m_state_in) != ERROR_SUCCESS) {
return false;
}
DWORD packet_number = m_state_in.dwPacketNumber;
if (packet_number != m_dwPacketNumber) {
m_dwPacketNumber = packet_number;
return true;
}
return false;
}
void XDevice::UpdateMotors()
{
PXInputSetState(m_Index, &m_state_out);
}
// GET name/source/id
std::string XDevice::Button::GetName() const
{
return named_buttons[m_Index].name;
}
std::string XDevice::Axis::GetName() const
{
return std::string(named_axes[m_Index]) + (m_Range < 0 ? '-' : '+');
}
std::string XDevice::Trigger::GetName() const
{
return named_triggers[m_Index];
}
std::string XDevice::Motor::GetName() const
{
return "LeftRight";
}
// GET / SET STATES
ControlState XDevice::Button::GetState() const
{
return (m_Buttons & named_buttons[m_Index].bitmask) > 0;
}
ControlState XDevice::Trigger::GetState() const
{
return ControlState(m_Trigger) / m_Range;
}
ControlState XDevice::Axis::GetState() const
{
return std::max(0.0, ControlState(m_Axis) / m_Range);
}
void XDevice::Motor::SetState(ControlState StateLeft, ControlState StateRight)
{
m_MotorLeft = (WORD)(StateLeft * m_Range);
m_MotorRight = (WORD)(StateRight * m_Range);
m_Parent->UpdateMotors();
}
}