547 lines
15 KiB
C++
547 lines
15 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 SDL.cpp of Dolphin emulator
|
|
// https://github.com/dolphin-emu/dolphin
|
|
|
|
#define LOG_PREFIX CXBXR_MODULE::SDL
|
|
|
|
#include <assert.h>
|
|
#include <thread>
|
|
#include "core\kernel\support\Emu.h"
|
|
#include "core\kernel\init\CxbxKrnl.h"
|
|
#include "SdlJoystick.h"
|
|
#include "XInputPad.h"
|
|
#include "DInputKeyboardMouse.h"
|
|
#include "InputManager.h"
|
|
|
|
// These values are those used by Dolphin!
|
|
static const uint16_t RUMBLE_PERIOD = 10;
|
|
static const uint16_t RUMBLE_LENGTH_MAX = 500;
|
|
|
|
|
|
namespace Sdl
|
|
{
|
|
uint32_t ExitEvent_t;
|
|
uint32_t PopulateEvent_t;
|
|
uint32_t UpdateInputEvent_t;
|
|
uint32_t DeviceRemoveAck_t;
|
|
int SdlInitStatus = SDL_NOT_INIT;
|
|
bool SdlPopulateOK = false;
|
|
|
|
void Init(std::mutex& Mtx, std::condition_variable& Cv, bool is_gui)
|
|
{
|
|
SDL_Event Event;
|
|
uint32_t CustomEvent_t;
|
|
std::unique_lock<std::mutex> lck(Mtx);
|
|
|
|
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC) < 0) {
|
|
EmuLog(LOG_LEVEL::ERROR2, "Failed to initialize SDL subsystem! The error was: %s", SDL_GetError());
|
|
SdlInitStatus = SDL_INIT_ERROR;
|
|
lck.unlock();
|
|
Cv.notify_one();
|
|
return;
|
|
}
|
|
CustomEvent_t = SDL_RegisterEvents(4);
|
|
if (CustomEvent_t == (uint32_t)-1) {
|
|
SDL_Quit();
|
|
EmuLog(LOG_LEVEL::ERROR2, "Failed to create SDL custom events!");
|
|
SdlInitStatus = SDL_INIT_ERROR;
|
|
lck.unlock();
|
|
Cv.notify_one();
|
|
return;
|
|
}
|
|
ExitEvent_t = CustomEvent_t;
|
|
PopulateEvent_t = CustomEvent_t + 1;
|
|
UpdateInputEvent_t = CustomEvent_t + 2;
|
|
DeviceRemoveAck_t = CustomEvent_t + 3;
|
|
|
|
// Drain all joystick add/remove events to avoid creating duplicated
|
|
// devices when we call PopulateDevices
|
|
while (SDL_PollEvent(&Event))
|
|
{
|
|
switch (Event.type)
|
|
{
|
|
case SDL_JOYDEVICEADDED: { break; }
|
|
case SDL_JOYDEVICEREMOVED: { break; }
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
SdlInitStatus = SDL_INIT_SUCCESS;
|
|
lck.unlock();
|
|
Cv.notify_one();
|
|
|
|
while (SDL_WaitEvent(&Event))
|
|
{
|
|
if (Event.type == SDL_JOYDEVICEADDED) {
|
|
OpenSdlDevice(Event.jdevice.which);
|
|
g_InputDeviceManager.HotplugHandler(true);
|
|
}
|
|
else if (Event.type == SDL_JOYDEVICEREMOVED) {
|
|
CloseSdlDevice(Event.jdevice.which);
|
|
}
|
|
else if (Event.type == SDL_JOYAXISMOTION ||
|
|
Event.type == SDL_JOYHATMOTION ||
|
|
Event.type == SDL_JOYBALLMOTION ||
|
|
Event.type == SDL_JOYBUTTONDOWN ||
|
|
Event.type == SDL_JOYBUTTONUP) {
|
|
SDL_JoystickID id;
|
|
switch (Event.type)
|
|
{
|
|
case SDL_JOYAXISMOTION:
|
|
id = Event.jaxis.which;
|
|
break;
|
|
|
|
case SDL_JOYHATMOTION:
|
|
id = Event.jhat.which;
|
|
break;
|
|
|
|
case SDL_JOYBALLMOTION:
|
|
id = Event.jball.which;
|
|
break;
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
case SDL_JOYBUTTONUP:
|
|
id = Event.jbutton.which;
|
|
break;
|
|
|
|
default: {
|
|
// unreachable
|
|
}
|
|
}
|
|
auto dev = g_InputDeviceManager.FindDevice(id);
|
|
if (dev != nullptr) {
|
|
dynamic_cast<SdlJoystick*>(dev.get())->SetDirty();
|
|
}
|
|
}
|
|
else if (Event.type == PopulateEvent_t) {
|
|
for (int i = 0; i < SDL_NumJoysticks(); i++) {
|
|
OpenSdlDevice(i);
|
|
}
|
|
SdlPopulateOK = true;
|
|
Cv.notify_one();
|
|
}
|
|
else if (Event.type == ExitEvent_t) {
|
|
break;
|
|
}
|
|
else if (Event.type == UpdateInputEvent_t) {
|
|
if ((*static_cast<int *>(Event.user.data1)) == PORT_INVALID) {
|
|
g_InputDeviceManager.UpdateOpt(is_gui);
|
|
}
|
|
else {
|
|
XInput::GetDeviceChanges();
|
|
DInput::GetDeviceChanges();
|
|
std::string port = std::to_string(*static_cast<int *>(Event.user.data1));
|
|
int port1, slot;
|
|
PortStr2Int(port, &port1, &slot);
|
|
if (g_devs[port1].type == XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE || g_devs[port1].type == XBOX_INPUT_DEVICE::MS_CONTROLLER_S) {
|
|
// Force an update of the entire slot connectivity of this port
|
|
g_InputDeviceManager.UpdateDevices(port + ".0", false);
|
|
g_InputDeviceManager.UpdateDevices(port + ".1", false);
|
|
}
|
|
g_InputDeviceManager.UpdateDevices(port, false);
|
|
}
|
|
|
|
delete Event.user.data1;
|
|
Event.user.data1 = nullptr;
|
|
}
|
|
else if (Event.type == DeviceRemoveAck_t) {
|
|
g_InputDeviceManager.UpdateDevices(*static_cast<int*>(Event.user.data1), true);
|
|
delete Event.user.data1;
|
|
Event.user.data1 = nullptr;
|
|
}
|
|
}
|
|
|
|
SDL_Quit();
|
|
}
|
|
|
|
void DeInit(std::thread& Thr)
|
|
{
|
|
SdlInitStatus = SDL_NOT_INIT;
|
|
if (!Thr.joinable()) {
|
|
return;
|
|
}
|
|
|
|
SDL_Event ExitEvent;
|
|
SDL_memset(&ExitEvent, 0, sizeof(SDL_Event));
|
|
ExitEvent.type = ExitEvent_t;
|
|
SDL_PushEvent(&ExitEvent);
|
|
|
|
Thr.join();
|
|
}
|
|
|
|
void OpenSdlDevice(const int Index)
|
|
{
|
|
SDL_Joystick* pJoystick = SDL_JoystickOpen(Index);
|
|
if (pJoystick) {
|
|
auto Device = std::make_shared<Sdl::SdlJoystick>(pJoystick, Index);
|
|
if (Device->IsXInput()) {
|
|
EmuLog(LOG_LEVEL::INFO, "Rejected joystick %i. It will be handled by XInput", Index);
|
|
return;
|
|
}
|
|
// 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 joystick %i. No controls detected", Index);
|
|
}
|
|
}
|
|
else {
|
|
EmuLog(LOG_LEVEL::ERROR2, "Failed to open joystick %i. The error was %s", Index, SDL_GetError());
|
|
}
|
|
}
|
|
|
|
void CloseSdlDevice(const int Index)
|
|
{
|
|
g_InputDeviceManager.RemoveDevice([&Index](const auto& Device) {
|
|
const SdlJoystick* joystick = dynamic_cast<const SdlJoystick*>(Device);
|
|
return joystick && SDL_JoystickInstanceID(joystick->GetSDLJoystick()) == Index;
|
|
});
|
|
}
|
|
|
|
void PopulateDevices()
|
|
{
|
|
SDL_Event PopulateEvent;
|
|
SDL_memset(&PopulateEvent, 0, sizeof(SDL_Event));
|
|
PopulateEvent.type = PopulateEvent_t;
|
|
SDL_PushEvent(&PopulateEvent);
|
|
}
|
|
|
|
SdlJoystick::SdlJoystick(SDL_Joystick* const Joystick, const int Index)
|
|
: m_Joystick(Joystick), m_Sdl_ID(SDL_JoystickInstanceID(Joystick)),
|
|
m_DeviceName(StripSpaces(SDL_JoystickNameForIndex(Index))), m_bIsXInput(false)
|
|
{
|
|
uint8_t i;
|
|
int NumButtons, NumAxes, NumHats, NumBalls;
|
|
std::string lcasename;
|
|
|
|
// From Dolphin:
|
|
// "really bad HACKS: do not use SDL for an XInput device,
|
|
// too many people on the forums pick the SDL device and ask:
|
|
// "why don't my 360 gamepad triggers/rumble work correctly?".
|
|
// Checking the name is probably good (and hacky) enough,
|
|
// but I'll double check with the num of buttons/axes"
|
|
// ergo720: also added check for xbox one and generic xinput controllers
|
|
lcasename = GetDeviceName();
|
|
std::transform(lcasename.begin(), lcasename.end(), lcasename.begin(), tolower);
|
|
NumButtons = SDL_JoystickNumButtons(Joystick);
|
|
NumAxes = SDL_JoystickNumAxes(Joystick);
|
|
NumHats = SDL_JoystickNumHats(Joystick);
|
|
NumBalls = SDL_JoystickNumBalls(Joystick);
|
|
|
|
if ((std::string::npos != lcasename.find("xbox 360")) &&
|
|
(10 == NumButtons) && (5 == NumAxes) &&
|
|
(1 == NumHats) && (0 == NumBalls))
|
|
{
|
|
// this device won't be used
|
|
m_bIsXInput = true;
|
|
return;
|
|
}
|
|
/* FIXME: is this correct? */
|
|
else if ((std::string::npos != lcasename.find("xbox one")) &&
|
|
(10 == NumButtons) && (5 == NumAxes) &&
|
|
(1 == NumHats) && (0 == NumBalls))
|
|
{
|
|
// this device won't be used
|
|
m_bIsXInput = true;
|
|
return;
|
|
}
|
|
else if (std::string::npos != lcasename.find("xinput"))
|
|
{
|
|
// this device won't be used
|
|
m_bIsXInput = true;
|
|
return;
|
|
}
|
|
else if (NumButtons > 255 || NumAxes > 255 ||
|
|
NumHats > 255 || NumBalls > 255)
|
|
{
|
|
// From Dolphin:
|
|
// "This device is invalid, don't use it.
|
|
// Some crazy devices (HP webcam 2100) end up as HID devices.
|
|
// SDL tries parsing these as joysticks"
|
|
return;
|
|
}
|
|
|
|
// get buttons
|
|
for (i = 0; i != NumButtons; ++i) {
|
|
AddInput(new Button(i, m_Joystick));
|
|
}
|
|
|
|
// get hats
|
|
for (i = 0; i != NumHats; ++i) {
|
|
// each hat gets 4 input instances associated with it (up down left right)
|
|
for (uint8_t d = 0; d != 4; ++d) {
|
|
AddInput(new Hat(i, m_Joystick, d));
|
|
}
|
|
}
|
|
|
|
// get axes
|
|
for (i = 0; i != NumAxes; ++i) {
|
|
// each axis gets a negative and a positive input instance associated with it
|
|
AddInput(new Axis(i, m_Joystick, -32768));
|
|
AddInput(new Axis(i, m_Joystick, 32767));
|
|
}
|
|
|
|
// try to get supported ff effects
|
|
m_Haptic = SDL_HapticOpenFromJoystick(m_Joystick);
|
|
if (m_Haptic)
|
|
{
|
|
const unsigned int SupportedEffects = SDL_HapticQuery(m_Haptic);
|
|
|
|
// constant effect
|
|
if (SupportedEffects & SDL_HAPTIC_CONSTANT) {
|
|
AddOutput(new ConstantEffect(m_Haptic));
|
|
}
|
|
|
|
// ramp effect
|
|
if (SupportedEffects & SDL_HAPTIC_RAMP) {
|
|
AddOutput(new RampEffect(m_Haptic));
|
|
}
|
|
|
|
// sine effect
|
|
if (SupportedEffects & SDL_HAPTIC_SINE) {
|
|
AddOutput(new SineEffect(m_Haptic));
|
|
}
|
|
|
|
// triangle effect
|
|
if (SupportedEffects & SDL_HAPTIC_TRIANGLE) {
|
|
AddOutput(new TriangleEffect(m_Haptic));
|
|
}
|
|
|
|
// left-right effect
|
|
if (SupportedEffects & SDL_HAPTIC_LEFTRIGHT) {
|
|
AddOutput(new LeftRightEffect(m_Haptic));
|
|
}
|
|
}
|
|
else {
|
|
EmuLog(LOG_LEVEL::INFO, "Joystick %i doesn't support any haptic effects", Index);
|
|
}
|
|
|
|
// init dirty flag
|
|
m_bDirty = false;
|
|
}
|
|
|
|
SdlJoystick::~SdlJoystick()
|
|
{
|
|
if (m_Haptic)
|
|
{
|
|
// stop/destroy all effects
|
|
SDL_HapticStopAll(m_Haptic);
|
|
// close haptic first
|
|
SDL_HapticClose(m_Haptic);
|
|
}
|
|
|
|
// close joystick
|
|
SDL_JoystickClose(m_Joystick);
|
|
}
|
|
|
|
std::string SdlJoystick::GetDeviceName() const
|
|
{
|
|
return m_DeviceName;
|
|
}
|
|
|
|
std::string SdlJoystick::GetAPI() const
|
|
{
|
|
return "SDL";
|
|
}
|
|
|
|
SDL_Joystick* SdlJoystick::GetSDLJoystick() const
|
|
{
|
|
return m_Joystick;
|
|
}
|
|
|
|
SDL_JoystickID SdlJoystick::GetId(SDL_JoystickID id) const
|
|
{
|
|
// ignore id argument
|
|
return m_Sdl_ID;
|
|
}
|
|
|
|
bool SdlJoystick::UpdateInput()
|
|
{
|
|
SDL_JoystickUpdate();
|
|
if (m_bDirty) {
|
|
m_bDirty = false;
|
|
return true;
|
|
}
|
|
return m_bDirty;
|
|
}
|
|
|
|
std::string SdlJoystick::ConstantEffect::GetName() const
|
|
{
|
|
return "Constant";
|
|
}
|
|
|
|
std::string SdlJoystick::RampEffect::GetName() const
|
|
{
|
|
return "Ramp";
|
|
}
|
|
|
|
std::string SdlJoystick::SineEffect::GetName() const
|
|
{
|
|
return "Sine";
|
|
}
|
|
|
|
std::string SdlJoystick::TriangleEffect::GetName() const
|
|
{
|
|
return "Triangle";
|
|
}
|
|
|
|
std::string SdlJoystick::LeftRightEffect::GetName() const
|
|
{
|
|
return "LeftRight";
|
|
}
|
|
|
|
std::string SdlJoystick::Button::GetName() const
|
|
{
|
|
std::ostringstream ss;
|
|
ss << "Button " << (int)m_Index;
|
|
return ss.str();
|
|
}
|
|
|
|
std::string SdlJoystick::Axis::GetName() const
|
|
{
|
|
std::ostringstream ss;
|
|
ss << "Axis " << (int)m_Index << (m_Range < 0 ? '-' : '+');
|
|
return ss.str();
|
|
}
|
|
|
|
std::string SdlJoystick::Hat::GetName() const
|
|
{
|
|
static char tmpstr[] = "Hat . .";
|
|
// From Dolphin: "I don't think more than 10 hats are supported"
|
|
tmpstr[4] = (char)('0' + m_Index);
|
|
tmpstr[6] = "NESW"[m_Direction];
|
|
return tmpstr;
|
|
}
|
|
|
|
ControlState SdlJoystick::Button::GetState() const
|
|
{
|
|
return SDL_JoystickGetButton(m_Js, m_Index);
|
|
}
|
|
|
|
ControlState SdlJoystick::Axis::GetState() const
|
|
{
|
|
return std::max(0.0, ControlState(SDL_JoystickGetAxis(m_Js, m_Index)) / m_Range);
|
|
}
|
|
|
|
ControlState SdlJoystick::Hat::GetState() const
|
|
{
|
|
return (SDL_JoystickGetHat(m_Js, m_Index) & (1 << m_Direction)) > 0;
|
|
}
|
|
|
|
void SdlJoystick::HapticEffect::SetState(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
memset(&m_Effect, 0, sizeof(m_Effect));
|
|
if (StateLeft || StateRight)
|
|
{
|
|
SetSDLHapticEffect(StateLeft, StateRight);
|
|
}
|
|
else
|
|
{
|
|
// this module uses type==0 to indicate 'off'
|
|
m_Effect.type = 0;
|
|
}
|
|
Update();
|
|
}
|
|
|
|
void SdlJoystick::HapticEffect::Update()
|
|
{
|
|
if (m_ID == -1 && m_Effect.type > 0)
|
|
{
|
|
m_ID = SDL_HapticNewEffect(m_Haptic, &m_Effect);
|
|
if (m_ID > -1) {
|
|
SDL_HapticRunEffect(m_Haptic, m_ID, 1);
|
|
}
|
|
}
|
|
else if (m_ID > -1 && m_Effect.type == 0)
|
|
{
|
|
SDL_HapticStopEffect(m_Haptic, m_ID);
|
|
SDL_HapticDestroyEffect(m_Haptic, m_ID);
|
|
m_ID = -1;
|
|
}
|
|
else if (m_ID > -1)
|
|
{
|
|
SDL_HapticUpdateEffect(m_Haptic, m_ID, &m_Effect);
|
|
}
|
|
}
|
|
|
|
// I'm not really sure if a simple arithmetic mean is enough here, if someone has a better idea they are welcome
|
|
// to change the code below
|
|
void SdlJoystick::ConstantEffect::SetSDLHapticEffect(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
m_Effect.type = SDL_HAPTIC_CONSTANT;
|
|
m_Effect.constant.length = RUMBLE_LENGTH_MAX;
|
|
m_Effect.constant.level = (Sint16)(((StateLeft + StateRight) / 2) * 0x7FFF);
|
|
}
|
|
|
|
void SdlJoystick::RampEffect::SetSDLHapticEffect(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
m_Effect.type = SDL_HAPTIC_RAMP;
|
|
m_Effect.ramp.length = RUMBLE_LENGTH_MAX;
|
|
m_Effect.ramp.start = (Sint16)(((StateLeft + StateRight) / 2) * 0x7FFF);
|
|
}
|
|
|
|
void SdlJoystick::SineEffect::SetSDLHapticEffect(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
m_Effect.type = SDL_HAPTIC_SINE;
|
|
m_Effect.periodic.period = RUMBLE_PERIOD;
|
|
m_Effect.periodic.magnitude = (Sint16)(((StateLeft + StateRight) / 2) * 0x7FFF);
|
|
m_Effect.periodic.offset = 0;
|
|
m_Effect.periodic.phase = 18000;
|
|
m_Effect.periodic.length = RUMBLE_LENGTH_MAX;
|
|
m_Effect.periodic.delay = 0;
|
|
m_Effect.periodic.attack_length = 0;
|
|
}
|
|
|
|
void SdlJoystick::TriangleEffect::SetSDLHapticEffect(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
m_Effect.type = SDL_HAPTIC_TRIANGLE;
|
|
m_Effect.periodic.period = RUMBLE_PERIOD;
|
|
m_Effect.periodic.magnitude = (Sint16)(((StateLeft + StateRight) / 2) * 0x7FFF);
|
|
m_Effect.periodic.offset = 0;
|
|
m_Effect.periodic.phase = 18000;
|
|
m_Effect.periodic.length = RUMBLE_LENGTH_MAX;
|
|
m_Effect.periodic.delay = 0;
|
|
m_Effect.periodic.attack_length = 0;
|
|
}
|
|
|
|
void SdlJoystick::LeftRightEffect::SetSDLHapticEffect(ControlState StateLeft, ControlState StateRight)
|
|
{
|
|
m_Effect.type = SDL_HAPTIC_LEFTRIGHT;
|
|
m_Effect.leftright.length = RUMBLE_LENGTH_MAX;
|
|
// max ranges tuned to 'feel' similar in magnitude to triangle/sine on xbox360 controller
|
|
m_Effect.leftright.large_magnitude = (Uint16)(StateRight * 0x4000);
|
|
m_Effect.leftright.small_magnitude = (Uint16)(StateLeft * 0xFFFF);
|
|
}
|
|
}
|