332 lines
10 KiB
C++
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();
|
|
}
|
|
}
|