This commit is contained in:
Nayla Hanegan 2024-05-12 02:17:59 -04:00
commit 98c174edc4
520 changed files with 74815 additions and 58942 deletions

View file

@ -23,6 +23,7 @@
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/InputBackend.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include "jni/Input/CoreDevice.h"
@ -444,6 +445,23 @@ std::shared_ptr<ciface::Core::Device> FindDevice(jint device_id)
namespace ciface::Android
{
class InputBackend final : public ciface::InputBackend
{
public:
InputBackend(ControllerInterface* controller_interface);
~InputBackend();
void PopulateDevices() override;
private:
void AddDevice(JNIEnv* env, int device_id);
void AddSensorDevice(JNIEnv* env);
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
class AndroidInput : public Core::Device::Input
{
public:
@ -779,7 +797,8 @@ static jintArray CreateKeyCodesArray(JNIEnv* env)
return keycodes_array;
}
void Init()
InputBackend::InputBackend(ControllerInterface* controller_interface)
: ciface::InputBackend(controller_interface)
{
JNIEnv* env = IDCache::GetEnvForThread();
@ -885,7 +904,7 @@ void Init()
s_controller_interface_register_input_device_listener);
}
void Shutdown()
InputBackend::~InputBackend()
{
JNIEnv* env = IDCache::GetEnvForThread();
@ -903,7 +922,7 @@ void Shutdown()
env->DeleteGlobalRef(s_keycodes_array);
}
static void AddDevice(JNIEnv* env, int device_id)
void InputBackend::AddDevice(JNIEnv* env, int device_id)
{
jobject input_device =
env->CallStaticObjectMethod(s_input_device_class, s_input_device_get_device, device_id);
@ -921,7 +940,7 @@ static void AddDevice(JNIEnv* env, int device_id)
if (device->Inputs().empty() && device->Outputs().empty())
return;
g_controller_interface.AddDevice(device);
GetControllerInterface().AddDevice(device);
Core::DeviceQualifier qualifier;
qualifier.FromDevice(device.get());
@ -936,7 +955,7 @@ static void AddDevice(JNIEnv* env, int device_id)
env->DeleteLocalRef(j_qualifier);
}
static void AddSensorDevice(JNIEnv* env)
void InputBackend::AddSensorDevice(JNIEnv* env)
{
// Device sensors (accelerometer, etc.) aren't associated with any Android InputDevice.
// Create an otherwise empty Dolphin input device so that they have somewhere to live.
@ -946,7 +965,7 @@ static void AddSensorDevice(JNIEnv* env)
if (device->Inputs().empty() && device->Outputs().empty())
return;
g_controller_interface.AddDevice(device);
GetControllerInterface().AddDevice(device);
Core::DeviceQualifier qualifier;
qualifier.FromDevice(device.get());
@ -959,7 +978,7 @@ static void AddSensorDevice(JNIEnv* env)
env->DeleteLocalRef(j_qualifier);
}
void PopulateDevices()
void InputBackend::PopulateDevices()
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "Android populating devices");

View file

@ -3,11 +3,10 @@
#pragma once
#include "InputCommon/ControllerInterface/InputBackend.h"
namespace ciface::Android
{
void Init();
void Shutdown();
void PopulateDevices();
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
} // namespace ciface::Android

View file

@ -59,25 +59,25 @@ void ControllerInterface::Initialize(const WindowSystemInfo& wsi)
m_populating_devices_counter = 1;
#ifdef CIFACE_USE_WIN32
ciface::Win32::Init(wsi.render_window);
m_input_backends.emplace_back(ciface::Win32::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_XLIB
// nothing needed
m_input_backends.emplace_back(ciface::XInput2::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_OSX
// nothing needed for Quartz
m_input_backends.emplace_back(ciface::Quartz::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_SDL
m_input_backends.emplace_back(ciface::SDL::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_ANDROID
ciface::Android::Init();
m_input_backends.emplace_back(ciface::Android::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_EVDEV
m_input_backends.emplace_back(ciface::evdev::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_PIPES
// nothing needed
m_input_backends.emplace_back(ciface::Pipes::CreateInputBackend(this));
#endif
#ifdef CIFACE_USE_DUALSHOCKUDPCLIENT
m_input_backends.emplace_back(ciface::DualShockUDPClient::CreateInputBackend(this));
@ -128,22 +128,20 @@ void ControllerInterface::RefreshDevices(RefreshReason reason)
// or removing them as we are populating them (causing missing or duplicate devices).
std::lock_guard lk_population(m_devices_population_mutex);
#if defined(CIFACE_USE_WIN32) && !defined(CIFACE_USE_XLIB) && !defined(CIFACE_USE_OSX)
// If only the window changed, avoid removing and re-adding all devices.
// Instead only refresh devices that require the window handle.
if (reason == RefreshReason::WindowChangeOnly)
{
m_populating_devices_counter.fetch_add(1);
// No need to do anything else in this case.
// Only (Win32) DInput needs the window handle to be updated.
ciface::Win32::ChangeWindow(m_wsi.render_window);
for (auto& backend : m_input_backends)
backend->HandleWindowChange();
if (m_populating_devices_counter.fetch_sub(1) == 1)
InvokeDevicesChangedCallbacks();
return;
}
#endif
m_populating_devices_counter.fetch_add(1);
@ -159,26 +157,6 @@ void ControllerInterface::RefreshDevices(RefreshReason reason)
// do it async, to not risk the emulated controllers default config loading not finding a default
// device.
#ifdef CIFACE_USE_WIN32
ciface::Win32::PopulateDevices(m_wsi.render_window);
#endif
#ifdef CIFACE_USE_XLIB
if (m_wsi.type == WindowSystemType::X11)
ciface::XInput2::PopulateDevices(m_wsi.render_window);
#endif
#ifdef CIFACE_USE_OSX
if (m_wsi.type == WindowSystemType::MacOS)
{
ciface::Quartz::PopulateDevices(m_wsi.render_window);
}
#endif
#ifdef CIFACE_USE_ANDROID
ciface::Android::PopulateDevices();
#endif
#ifdef CIFACE_USE_PIPES
ciface::Pipes::PopulateDevices();
#endif
for (auto& backend : m_input_backends)
backend->PopulateDevices();
@ -217,19 +195,6 @@ void ControllerInterface::Shutdown()
// Update control references so shared_ptr<Device>s are freed up BEFORE we shutdown the backends.
ClearDevices();
#ifdef CIFACE_USE_WIN32
ciface::Win32::DeInit();
#endif
#ifdef CIFACE_USE_XLIB
// nothing needed
#endif
#ifdef CIFACE_USE_OSX
ciface::Quartz::DeInit();
#endif
#ifdef CIFACE_USE_ANDROID
ciface::Android::Shutdown();
#endif
// Empty the container of input backends to deconstruct and deinitialize them.
m_input_backends.clear();
@ -423,6 +388,11 @@ ciface::InputChannel ControllerInterface::GetCurrentInputChannel()
return tls_input_channel;
}
WindowSystemInfo ControllerInterface::GetWindowSystemInfo() const
{
return m_wsi;
}
void ControllerInterface::SetAspectRatioAdjustment(float value)
{
m_aspect_ratio_adjustment = value;

View file

@ -122,6 +122,8 @@ public:
static void SetCurrentInputChannel(ciface::InputChannel);
static ciface::InputChannel GetCurrentInputChannel();
WindowSystemInfo GetWindowSystemInfo() const;
private:
void ClearDevices();

View file

@ -125,6 +125,11 @@ bool Device::Control::IsMatchingName(std::string_view name) const
return GetName() == name;
}
bool Device::Control::IsHidden() const
{
return false;
}
ControlState Device::FullAnalogSurface::GetState() const
{
return (1 + std::max(0.0, m_high.GetState()) - std::max(0.0, m_low.GetState())) / 2;
@ -141,6 +146,11 @@ bool Device::FullAnalogSurface::IsDetectable() const
return m_low.IsDetectable() && m_high.IsDetectable();
}
bool Device::FullAnalogSurface::IsHidden() const
{
return m_low.IsHidden() && m_high.IsHidden();
}
bool Device::FullAnalogSurface::IsMatchingName(std::string_view name) const
{
if (Control::IsMatchingName(name))

View file

@ -64,6 +64,10 @@ public:
// May be overridden to allow multiple valid names.
// Useful for backwards-compatible configurations when names change.
virtual bool IsMatchingName(std::string_view name) const;
// May be overridden to hide in UI.
// Useful for backwards-compatible configurations when names change.
virtual bool IsHidden() const;
};
//
@ -164,6 +168,7 @@ protected:
ControlState GetState() const override;
std::string GetName() const override;
bool IsDetectable() const override;
bool IsHidden() const override;
bool IsMatchingName(std::string_view name) const override;
private:

View file

@ -7,16 +7,13 @@
#include <string>
#include <thread>
#include <Windows.h>
#include "Common/Event.h"
#include "Common/Flag.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"
#ifdef _WIN32
#include <Windows.h>
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/ControllerInterface/DInput/DInput8.h"
#elif __APPLE__
#include "InputCommon/ControllerInterface/ForceFeedback/OSX/DirectInputAdapter.h"
#endif
namespace ciface::ForceFeedback
{

View file

@ -1,196 +0,0 @@
// Copyright 2014 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* The OS X Force Feedback API is very similar to the DirectInput API,
* but it is no longer object-oriented and all prefixes have been changed.
*
* Our implementation uses the Windows API names so we need to adapt
* for these differences on OS X.
*/
#pragma once
#include <atomic>
typedef LONG* LPLONG; // Missing type for ForceFeedback.h
#include <CoreFoundation/CoreFoundation.h>
#include <ForceFeedback/ForceFeedback.h>
#include "Common/CommonTypes.h" // for LONG
#include "DirectInputConstants.h" // Not stricty necessary
namespace ciface::ForceFeedback
{
// Prototypes
class IUnknownImpl;
class FFEffectAdapter;
class FFDeviceAdapter;
// Structs
typedef FFCAPABILITIES DICAPABILITIES;
typedef FFCONDITION DICONDITION;
typedef FFCONSTANTFORCE DICONSTANTFORCE;
typedef FFCUSTOMFORCE DICUSTOMFORCE;
typedef FFEFFECT DIEFFECT;
typedef FFEFFESCAPE DIEFFESCAPE;
typedef FFENVELOPE DIENVELOPE;
typedef FFPERIODIC DIPERIODIC;
typedef FFRAMPFORCE DIRAMPFORCE;
// Other types
typedef CFUUIDRef GUID;
typedef FFDeviceAdapter* FFDeviceAdapterReference;
typedef FFEffectAdapter* FFEffectAdapterReference;
typedef FFDeviceAdapterReference LPDIRECTINPUTDEVICE8;
typedef FFEffectAdapterReference LPDIRECTINPUTEFFECT;
// Property structures
#define DIPH_DEVICE 0
typedef struct DIPROPHEADER
{
DWORD dwSize;
DWORD dwHeaderSize;
DWORD dwObj;
DWORD dwHow;
} DIPROPHEADER, *LPDIPROPHEADER;
typedef struct DIPROPDWORD
{
DIPROPHEADER diph;
DWORD dwData;
} DIPROPDWORD, *LPDIPROPDWORD;
class IUnknownImpl : public IUnknown
{
private:
std::atomic<ULONG> m_cRef;
public:
IUnknownImpl() : m_cRef(1) {}
virtual ~IUnknownImpl() {}
HRESULT QueryInterface(REFIID iid, LPVOID* ppv)
{
*ppv = nullptr;
if (CFEqual(&iid, IUnknownUUID))
*ppv = this;
if (nullptr == *ppv)
return E_NOINTERFACE;
((IUnknown*)*ppv)->AddRef();
return S_OK;
}
ULONG AddRef() { return ++m_cRef; }
ULONG Release()
{
if (--m_cRef == 0)
delete this;
return m_cRef;
}
};
class FFEffectAdapter : public IUnknownImpl
{
private:
// Only used for destruction
FFDeviceObjectReference m_device;
public:
FFEffectObjectReference m_effect;
FFEffectAdapter(FFDeviceObjectReference device, FFEffectObjectReference effect)
: m_device(device), m_effect(effect)
{
}
~FFEffectAdapter() { FFDeviceReleaseEffect(m_device, m_effect); }
HRESULT Download() { return FFEffectDownload(m_effect); }
HRESULT Escape(FFEFFESCAPE* pFFEffectEscape) { return FFEffectEscape(m_effect, pFFEffectEscape); }
HRESULT GetEffectStatus(FFEffectStatusFlag* pFlags)
{
return FFEffectGetEffectStatus(m_effect, pFlags);
}
HRESULT GetParameters(FFEFFECT* pFFEffect, FFEffectParameterFlag flags)
{
return FFEffectGetParameters(m_effect, pFFEffect, flags);
}
HRESULT SetParameters(FFEFFECT* pFFEffect, FFEffectParameterFlag flags)
{
return FFEffectSetParameters(m_effect, pFFEffect, flags);
}
HRESULT Start(UInt32 iterations, FFEffectStartFlag flags)
{
return FFEffectStart(m_effect, iterations, flags);
}
HRESULT Stop() { return FFEffectStop(m_effect); }
HRESULT Unload() { return FFEffectUnload(m_effect); }
};
class FFDeviceAdapter : public IUnknownImpl
{
public:
FFDeviceObjectReference m_device;
FFDeviceAdapter(FFDeviceObjectReference device) : m_device(device) {}
~FFDeviceAdapter() { FFReleaseDevice(m_device); }
static HRESULT Create(io_service_t hidDevice, FFDeviceAdapterReference* pDeviceReference)
{
FFDeviceObjectReference ref;
HRESULT hr = FFCreateDevice(hidDevice, &ref);
if (SUCCEEDED(hr))
*pDeviceReference = new FFDeviceAdapter(ref);
return hr;
}
HRESULT CreateEffect(CFUUIDRef uuidRef, FFEFFECT* pEffectDefinition,
FFEffectAdapterReference* pEffectReference, IUnknown* punkOuter)
{
FFEffectObjectReference ref;
HRESULT hr = FFDeviceCreateEffect(m_device, uuidRef, pEffectDefinition, &ref);
if (SUCCEEDED(hr))
*pEffectReference = new FFEffectAdapter(m_device, ref);
return hr;
}
HRESULT Escape(FFEFFESCAPE* pFFEffectEscape) { return FFDeviceEscape(m_device, pFFEffectEscape); }
HRESULT GetForceFeedbackState(FFState* pFFState)
{
return FFDeviceGetForceFeedbackState(m_device, pFFState);
}
HRESULT SendForceFeedbackCommand(FFCommandFlag flags)
{
return FFDeviceSendForceFeedbackCommand(m_device, flags);
}
HRESULT SetCooperativeLevel(void* taskIdentifier, FFCooperativeLevelFlag flags)
{
return FFDeviceSetCooperativeLevel(m_device, taskIdentifier, flags);
}
HRESULT SetProperty(FFProperty property, const LPDIPROPHEADER pdiph)
{
// There are only two properties supported
if (property != DIPROP_FFGAIN && property != DIPROP_AUTOCENTER)
return DIERR_UNSUPPORTED;
// And they are both device properties
if (pdiph->dwHow != DIPH_DEVICE)
return DIERR_INVALIDPARAM;
UInt32 value = ((const LPDIPROPDWORD)pdiph)->dwData;
return FFDeviceSetForceFeedbackProperty(m_device, property, &value);
}
};
} // namespace ciface::ForceFeedback

View file

@ -1,146 +0,0 @@
// Copyright 2014 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
/*
* Define all constants from ForceFeedbackConstants.h with DirectInput prefixes.
*
* No effort was made to confirm if all definitions are actually supported by
* DirectInput, so some of these definitions may actually only exist on Mac OS X.
*/
// UUIDs
#define GUID_ConstantForce kFFEffectType_ConstantForce_ID
#define GUID_CustomForce kFFEffectType_CustomForce_ID
#define GUID_Damper kFFEffectType_Damper_ID
#define GUID_Friction kFFEffectType_Friction_ID
#define GUID_Inertia kFFEffectType_Inertia_ID
#define GUID_RampForce kFFEffectType_RampForce_ID
#define GUID_SawtoothDown kFFEffectType_SawtoothDown_ID
#define GUID_SawtoothUp kFFEffectType_SawtoothUp_ID
#define GUID_Sine kFFEffectType_Sine_ID
#define GUID_Spring kFFEffectType_Spring_ID
#define GUID_Square kFFEffectType_Square_ID
#define GUID_Triangle kFFEffectType_Triangle_ID
// Miscellaneous
#define DI_DEGREES FF_DEGREES
#define DI_DOWNLOADSKIPPED FF_DOWNLOADSKIPPED
#define DI_EFFECTRESTARTED FF_EFFECTRESTARTED
#define DI_FALSE FF_FALSE
#define DI_FFNOMINALMAX FF_FFNOMINALMAX
#define DI_INFINITE FF_INFINITE
#define DI_OK FF_OK
#define DI_SECONDS FF_SECONDS
#define DI_TRUNCATED FF_TRUNCATED
#define DI_TRUNCATEDANDRESTARTED FF_TRUNCATEDANDRESTARTED
#define DIEFF_OBJECTOFFSETS FFEFF_OBJECTOFFSETS
#define DIERR_DEVICEFULL FFERR_DEVICEFULL
#define DIERR_DEVICENOTREG FFERR_DEVICENOTREG
#define DIERR_DEVICEPAUSED FFERR_DEVICEPAUSED
#define DIERR_DEVICERELEASED FFERR_DEVICERELEASED
#define DIERR_EFFECTPLAYING FFERR_EFFECTPLAYING
#define DIERR_EFFECTTYPEMISMATCH FFERR_EFFECTTYPEMISMATCH
#define DIERR_EFFECTTYPENOTSUPPORTED FFERR_EFFECTTYPENOTSUPPORTED
#define DIERR_GENERIC FFERR_GENERIC
#define DIERR_HASEFFECTS FFERR_HASEFFECTS
#define DIERR_INCOMPLETEEFFECT FFERR_INCOMPLETEEFFECT
#define DIERR_INTERNAL FFERR_INTERNAL
#define DIERR_INVALIDDOWNLOADID FFERR_INVALIDDOWNLOADID
#define DIERR_INVALIDPARAM FFERR_INVALIDPARAM
#define DIERR_MOREDATA FFERR_MOREDATA
#define DIERR_NOINTERFACE FFERR_NOINTERFACE
#define DIERR_NOTDOWNLOADED FFERR_NOTDOWNLOADED
#define DIERR_NOTINITIALIZED FFERR_NOTINITIALIZED
#define DIERR_OUTOFMEMORY FFERR_OUTOFMEMORY
#define DIERR_UNPLUGGED FFERR_UNPLUGGED
#define DIERR_UNSUPPORTED FFERR_UNSUPPORTED
#define DIERR_UNSUPPORTEDAXIS FFERR_UNSUPPORTEDAXIS
#define DIJOFS_X FFJOFS_X
#define DIJOFS_Y FFJOFS_Y
#define DIJOFS_Z FFJOFS_Z
// FFCapabilitiesEffectSubType
#define DICAP_ST_KINESTHETIC FFCAP_ST_KINESTHETIC
#define DICAP_ST_VIBRATION FFCAP_ST_VIBRATION
// FFCapabilitiesEffectType
#define DICAP_ET_CONSTANTFORCE FFCAP_ET_CONSTANTFORCE
#define DICAP_ET_RAMPFORCE FFCAP_ET_RAMPFORCE
#define DICAP_ET_SQUARE FFCAP_ET_SQUARE
#define DICAP_ET_SINE FFCAP_ET_SINE
#define DICAP_ET_TRIANGLE FFCAP_ET_TRIANGLE
#define DICAP_ET_SAWTOOTHUP FFCAP_ET_SAWTOOTHUP
#define DICAP_ET_SAWTOOTHDOWN FFCAP_ET_SAWTOOTHDOWN
#define DICAP_ET_SPRING FFCAP_ET_SPRING
#define DICAP_ET_DAMPER FFCAP_ET_DAMPER
#define DICAP_ET_INERTIA FFCAP_ET_INERTIA
#define DICAP_ET_FRICTION FFCAP_ET_FRICTION
#define DICAP_ET_CUSTOMFORCE FFCAP_ET_CUSTOMFORCE
// FFCommandFlag
#define DISFFC_RESET FFSFFC_RESET
#define DISFFC_STOPALL FFSFFC_STOPALL
#define DISFFC_PAUSE FFSFFC_PAUSE
#define DISFFC_CONTINUE FFSFFC_CONTINUE
#define DISFFC_SETACTUATORSON FFSFFC_SETACTUATORSON
#define DISFFC_SETACTUATORSOFF FFSFFC_SETACTUATORSOFF
// FFCooperativeLevelFlag
#define DISCL_EXCLUSIVE FFSCL_EXCLUSIVE
#define DISCL_NONEXCLUSIVE FFSCL_NONEXCLUSIVE
#define DISCL_FOREGROUND FFSCL_FOREGROUND
#define DISCL_BACKGROUND FFSCL_BACKGROUND
// FFCoordinateSystemFlag
#define DIEFF_CARTESIAN FFEFF_CARTESIAN
#define DIEFF_POLAR FFEFF_POLAR
#define DIEFF_SPHERICAL FFEFF_SPHERICAL
// FFEffectParameterFlag
#define DIEP_DURATION FFEP_DURATION
#define DIEP_SAMPLEPERIOD FFEP_SAMPLEPERIOD
#define DIEP_GAIN FFEP_GAIN
#define DIEP_TRIGGERBUTTON FFEP_TRIGGERBUTTON
#define DIEP_TRIGGERREPEATINTERVAL FFEP_TRIGGERREPEATINTERVAL
#define DIEP_AXES FFEP_AXES
#define DIEP_DIRECTION FFEP_DIRECTION
#define DIEP_ENVELOPE FFEP_ENVELOPE
#define DIEP_TYPESPECIFICPARAMS FFEP_TYPESPECIFICPARAMS
#define DIEP_STARTDELAY FFEP_STARTDELAY
#define DIEP_ALLPARAMS FFEP_ALLPARAMS
#define DIEP_START FFEP_START
#define DIEP_NORESTART FFEP_NORESTART
#define DIEP_NODOWNLOAD FFEP_NODOWNLOAD
#define DIEB_NOTRIGGER FFEB_NOTRIGGER
// FFEffectStartFlag
#define DIES_SOLO FFES_SOLO
#define DIES_NODOWNLOAD FFES_NODOWNLOAD
// FFEffectStatusFlag
#define DIEGES_NOTPLAYING FFEGES_NOTPLAYING
#define DIEGES_PLAYING FFEGES_PLAYING
#define DIEGES_EMULATED FFEGES_EMULATED
// FFProperty
#define DIPROP_FFGAIN FFPROP_FFGAIN
#define DIPROP_AUTOCENTER FFPROP_AUTOCENTER
// not defined in ForceFeedbackConstants.h
#define DIPROPAUTOCENTER_OFF 0
#define DIPROPAUTOCENTER_ON 1
// FFState
#define DIGFFS_EMPTY FFGFFS_EMPTY
#define DIGFFS_STOPPED FFGFFS_STOPPED
#define DIGFFS_PAUSED FFGFFS_PAUSED
#define DIGFFS_ACTUATORSON FFGFFS_ACTUATORSON
#define DIGFFS_ACTUATORSOFF FFGFFS_ACTUATORSOFF
#define DIGFFS_POWERON FFGFFS_POWERON
#define DIGFFS_POWEROFF FFGFFS_POWEROFF
#define DIGFFS_SAFETYSWITCHON FFGFFS_SAFETYSWITCHON
#define DIGFFS_SAFETYSWITCHOFF FFGFFS_SAFETYSWITCHOFF
#define DIGFFS_USERFFSWITCHON FFGFFS_USERFFSWITCHON
#define DIGFFS_USERFFSWITCHOFF FFGFFS_USERFFSWITCHOFF
#define DIGFFS_DEVICELOST FFGFFS_DEVICELOST

View file

@ -16,6 +16,10 @@ void InputBackend::UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>&
{
}
void InputBackend::HandleWindowChange()
{
}
ControllerInterface& InputBackend::GetControllerInterface()
{
return m_controller_interface;

View file

@ -28,6 +28,8 @@ public:
// just add them to the removal list if necessary.
virtual void UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>& devices_to_remove);
virtual void HandleWindowChange();
ControllerInterface& GetControllerInterface();
private:

View file

@ -39,7 +39,19 @@ static double StringToDouble(const std::string& text)
return result;
}
void PopulateDevices()
class InputBackend final : public ciface::InputBackend
{
public:
using ciface::InputBackend::InputBackend;
void PopulateDevices() override;
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
void InputBackend::PopulateDevices()
{
// Search the Pipes directory for files that we can open in read-only,
// non-blocking mode. The device name is the virtual name of the file.

View file

@ -21,7 +21,7 @@ namespace ciface::Pipes
// SET {L, R} [0, 1]
// SET {MAIN, C} [0, 1] [0, 1]
void PopulateDevices();
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
class PipeDevice : public Core::Device
{

View file

@ -3,8 +3,13 @@
#pragma once
#include <string>
#include "InputCommon/ControllerInterface/InputBackend.h"
namespace ciface::Quartz
{
void PopulateDevices(void* window);
void DeInit();
std::string GetSourceName();
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
} // namespace ciface::Quartz

View file

@ -7,15 +7,40 @@
namespace ciface::Quartz
{
void PopulateDevices(void* window)
std::string GetSourceName()
{
if (!window)
return "Quartz";
}
class InputBackend final : public ciface::InputBackend
{
public:
using ciface::InputBackend::InputBackend;
void PopulateDevices() override;
void HandleWindowChange() override;
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
void InputBackend::HandleWindowChange()
{
const std::string source_name = GetSourceName();
GetControllerInterface().RemoveDevice(
[&](const auto* dev) { return dev->GetSource() == source_name; }, true);
PopulateDevices();
}
void InputBackend::PopulateDevices()
{
const WindowSystemInfo wsi = GetControllerInterface().GetWindowSystemInfo();
if (wsi.type != WindowSystemType::MacOS)
return;
g_controller_interface.AddDevice(std::make_shared<KeyboardAndMouse>(window));
GetControllerInterface().AddDevice(std::make_shared<KeyboardAndMouse>(wsi.render_window));
}
void DeInit()
{
}
} // namespace ciface::Quartz

View file

@ -12,6 +12,7 @@
#include "Core/Host.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/Quartz/Quartz.h"
/// Helper class to get window position data from threads other than the main thread
@interface DolWindowPositionObserver : NSObject
@ -279,7 +280,7 @@ std::string KeyboardAndMouse::GetName() const
std::string KeyboardAndMouse::GetSource() const
{
return "Quartz";
return Quartz::GetSourceName();
}
ControlState KeyboardAndMouse::Cursor::GetState() const

View file

@ -3,14 +3,17 @@
#include "InputCommon/ControllerInterface/SDL/SDL.h"
#include <SDL_haptic.h>
#include <thread>
#include <unordered_set>
#include <vector>
#include <SDL.h>
#include <SDL_haptic.h>
#include "Common/Event.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#ifdef _WIN32
@ -22,8 +25,258 @@ namespace ciface::Core
class Device;
}
namespace
{
std::string GetLegacyButtonName(int index)
{
return "Button " + std::to_string(index);
}
std::string GetLegacyAxisName(int index, int range)
{
return "Axis " + std::to_string(index) + (range < 0 ? '-' : '+');
}
std::string GetLegacyHatName(int index, int direction)
{
return "Hat " + std::to_string(index) + ' ' + "NESW"[direction];
}
constexpr int GetDirectionFromHatMask(u8 mask)
{
return MathUtil::IntLog2(mask);
}
static_assert(GetDirectionFromHatMask(SDL_HAT_UP) == 0);
static_assert(GetDirectionFromHatMask(SDL_HAT_LEFT) == 3);
bool IsTriggerAxis(int index)
{
// First 4 axes are for the analog sticks, the rest are for the triggers
return index >= 4;
}
} // namespace
namespace ciface::SDL
{
class GameController : public Core::Device
{
private:
// GameController inputs
class Button : public Core::Device::Input
{
public:
std::string GetName() const override;
Button(SDL_GameController* gc, SDL_GameControllerButton button) : m_gc(gc), m_button(button) {}
ControlState GetState() const override;
bool IsMatchingName(std::string_view name) const override;
private:
SDL_GameController* const m_gc;
const SDL_GameControllerButton m_button;
};
class Axis : public Core::Device::Input
{
public:
std::string GetName() const override;
Axis(SDL_GameController* gc, Sint16 range, SDL_GameControllerAxis axis)
: m_gc(gc), m_range(range), m_axis(axis)
{
}
ControlState GetState() const override;
private:
SDL_GameController* const m_gc;
const Sint16 m_range;
const SDL_GameControllerAxis m_axis;
};
// Legacy inputs
class LegacyButton : public Core::Device::Input
{
public:
std::string GetName() const override { return GetLegacyButtonName(m_index); }
LegacyButton(SDL_Joystick* js, int index) : m_js(js), m_index(index) {}
ControlState GetState() const override;
private:
SDL_Joystick* const m_js;
const int m_index;
};
class LegacyAxis : public Core::Device::Input
{
public:
std::string GetName() const override { return GetLegacyAxisName(m_index, m_range); }
LegacyAxis(SDL_Joystick* js, int index, s16 range, bool is_handled_elsewhere)
: m_js(js), m_index(index), m_range(range), m_is_handled_elsewhere(is_handled_elsewhere)
{
}
ControlState GetState() const override;
bool IsHidden() const override { return m_is_handled_elsewhere; }
bool IsDetectable() const override { return !IsHidden(); }
private:
SDL_Joystick* const m_js;
const int m_index;
const s16 m_range;
const bool m_is_handled_elsewhere;
};
class LegacyHat : public Input
{
public:
std::string GetName() const override { return GetLegacyHatName(m_index, m_direction); }
LegacyHat(SDL_Joystick* js, int index, u8 direction)
: m_js(js), m_index(index), m_direction(direction)
{
}
ControlState GetState() const override;
private:
SDL_Joystick* const m_js;
const int m_index;
const u8 m_direction;
};
// Rumble
template <int LowEnable, int HighEnable, int SuffixIndex>
class GenericMotor : public Output
{
public:
explicit GenericMotor(SDL_GameController* gc) : m_gc(gc) {}
std::string GetName() const override
{
return std::string("Motor") + motor_suffixes[SuffixIndex];
}
void SetState(ControlState state) override
{
Uint16 rumble = state * std::numeric_limits<Uint16>::max();
SDL_GameControllerRumble(m_gc, rumble * LowEnable, rumble * HighEnable, RUMBLE_LENGTH_MS);
}
private:
SDL_GameController* const m_gc;
};
static constexpr const char* motor_suffixes[] = {"", " L", " R"};
using Motor = GenericMotor<1, 1, 0>;
using MotorL = GenericMotor<1, 0, 1>;
using MotorR = GenericMotor<0, 1, 2>;
class HapticEffect : public Output
{
public:
HapticEffect(SDL_Haptic* haptic);
~HapticEffect();
protected:
virtual bool UpdateParameters(s16 value) = 0;
static void SetDirection(SDL_HapticDirection* dir);
SDL_HapticEffect m_effect = {};
static constexpr u16 DISABLED_EFFECT_TYPE = 0;
private:
virtual void SetState(ControlState state) override final;
void UpdateEffect();
SDL_Haptic* const m_haptic;
int m_id = -1;
};
class ConstantEffect : public HapticEffect
{
public:
ConstantEffect(SDL_Haptic* haptic);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
};
class RampEffect : public HapticEffect
{
public:
RampEffect(SDL_Haptic* haptic);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
};
class PeriodicEffect : public HapticEffect
{
public:
PeriodicEffect(SDL_Haptic* haptic, u16 waveform);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
const u16 m_waveform;
};
class LeftRightEffect : public HapticEffect
{
public:
enum class Motor : u8
{
Weak,
Strong,
};
LeftRightEffect(SDL_Haptic* haptic, Motor motor);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
const Motor m_motor;
};
class MotionInput : public Input
{
public:
MotionInput(std::string name, SDL_GameController* gc, SDL_SensorType type, int index,
ControlState scale)
: m_name(std::move(name)), m_gc(gc), m_type(type), m_index(index), m_scale(scale){};
std::string GetName() const override { return m_name; };
bool IsDetectable() const override { return false; };
ControlState GetState() const override;
private:
std::string m_name;
SDL_GameController* const m_gc;
SDL_SensorType const m_type;
int const m_index;
ControlState const m_scale;
};
public:
GameController(SDL_GameController* const gamecontroller, SDL_Joystick* const joystick,
const int sdl_index);
~GameController();
std::string GetName() const override;
std::string GetSource() const override;
int GetSDLIndex() const;
private:
SDL_GameController* const m_gamecontroller;
std::string m_name;
int m_sdl_index;
SDL_Joystick* const m_joystick;
SDL_Haptic* m_haptic = nullptr;
};
class InputBackend final : public ciface::InputBackend
{
public:
@ -340,48 +593,26 @@ GameController::GameController(SDL_GameController* const gamecontroller,
name = SDL_JoystickName(joystick);
m_name = name != nullptr ? name : "Unknown";
// If a Joystick Button has a GameController equivalent, don't detect it
int n_legacy_buttons = SDL_JoystickNumButtons(joystick);
if (n_legacy_buttons < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumButtons(): {}", SDL_GetError());
n_legacy_buttons = 0;
}
int n_legacy_axes = SDL_JoystickNumAxes(joystick);
if (n_legacy_axes < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumAxes(): {}", SDL_GetError());
n_legacy_axes = 0;
}
int n_legacy_hats = SDL_JoystickNumHats(joystick);
if (n_legacy_hats < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumHats(): {}", SDL_GetError());
n_legacy_hats = 0;
}
std::vector<bool> is_button_mapped(static_cast<size_t>(n_legacy_buttons), false);
std::vector<bool> is_axis_mapped(static_cast<size_t>(n_legacy_axes), false);
std::vector<bool> is_hat_mapped(static_cast<size_t>(n_legacy_hats), false);
// If a Joystick input has a GameController equivalent button/hat we don't add it.
// "Equivalent" axes are still added as hidden/undetectable inputs to handle
// loading of existing configs which may use "full surface" inputs.
// Otherwise handling those would require dealing with gamepad specific quirks.
std::unordered_set<int> registered_buttons;
std::unordered_set<int> registered_hats;
std::unordered_set<int> registered_axes;
const auto register_mapping = [&](const SDL_GameControllerButtonBind& bind) {
switch (bind.bindType)
{
case SDL_CONTROLLER_BINDTYPE_NONE:
return;
case SDL_CONTROLLER_BINDTYPE_BUTTON:
if (bind.value.button >= 0 && bind.value.button < n_legacy_buttons)
is_button_mapped[bind.value.button] = true;
break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
if (bind.value.axis >= 0 && bind.value.axis < n_legacy_axes)
is_axis_mapped[bind.value.axis] = true;
registered_buttons.insert(bind.value.button);
break;
case SDL_CONTROLLER_BINDTYPE_HAT:
if (bind.value.hat.hat >= 0 && bind.value.hat.hat < n_legacy_hats)
is_hat_mapped[bind.value.hat.hat] = true;
registered_hats.insert(bind.value.hat.hat);
break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
registered_axes.insert(bind.value.axis);
break;
default:
break;
}
};
@ -397,6 +628,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
if (SDL_GameControllerHasButton(m_gamecontroller, button))
{
AddInput(new Button(gamecontroller, button));
register_mapping(SDL_GameControllerGetBindForButton(gamecontroller, button));
}
}
@ -407,20 +639,21 @@ GameController::GameController(SDL_GameController* const gamecontroller,
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(i);
if (SDL_GameControllerHasAxis(m_gamecontroller, axis))
{
// First 4 axes are for the analog sticks, the rest are for the triggers
if (i < 4)
if (IsTriggerAxis(axis))
{
AddInput(new Axis(m_gamecontroller, 32767, axis));
}
else
{
// Each axis gets a negative and a positive input instance associated with it
AddInput(new Axis(m_gamecontroller, -32768, axis));
AddInput(new Axis(m_gamecontroller, 32767, axis));
}
else
{
AddInput(new Axis(m_gamecontroller, 32767, axis));
}
register_mapping(SDL_GameControllerGetBindForAxis(gamecontroller, axis));
}
}
// Rumble
if (SDL_GameControllerHasRumble(m_gamecontroller))
{
@ -430,14 +663,14 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
// Motion
const auto add_sensor = [this](SDL_SensorType type, std::string_view name,
const auto add_sensor = [this](SDL_SensorType type, std::string_view sensor_name,
const SDLMotionAxisList& axes) {
if (SDL_GameControllerSetSensorEnabled(m_gamecontroller, type, SDL_TRUE) == 0)
{
for (const SDLMotionAxis& axis : axes)
{
AddInput(new MotionInput(fmt::format("{} {}", name, axis.name), m_gamecontroller, type,
axis.index, axis.scale));
AddInput(new MotionInput(fmt::format("{} {}", sensor_name, axis.name), m_gamecontroller,
type, axis.index, axis.scale));
}
}
};
@ -453,23 +686,51 @@ GameController::GameController(SDL_GameController* const gamecontroller,
// Legacy inputs
// Buttons
int n_legacy_buttons = SDL_JoystickNumButtons(joystick);
if (n_legacy_buttons < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumButtons(): {}", SDL_GetError());
n_legacy_buttons = 0;
}
for (int i = 0; i != n_legacy_buttons; ++i)
AddInput(new LegacyButton(m_joystick, i, !is_button_mapped[i]));
{
if (registered_buttons.contains(i))
continue;
AddInput(new LegacyButton(m_joystick, i));
}
// Axes
int n_legacy_axes = SDL_JoystickNumAxes(joystick);
if (n_legacy_axes < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumAxes(): {}", SDL_GetError());
n_legacy_axes = 0;
}
for (int i = 0; i != n_legacy_axes; ++i)
{
const bool is_registered = registered_axes.contains(i);
// each axis gets a negative and a positive input instance associated with it
AddAnalogInputs(new LegacyAxis(m_joystick, i, -32768, !is_axis_mapped[i]),
new LegacyAxis(m_joystick, i, 32767, !is_axis_mapped[i]));
AddAnalogInputs(new LegacyAxis(m_joystick, i, -32768, is_registered),
new LegacyAxis(m_joystick, i, 32767, is_registered));
}
// Hats
int n_legacy_hats = SDL_JoystickNumHats(joystick);
if (n_legacy_hats < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumHats(): {}", SDL_GetError());
n_legacy_hats = 0;
}
for (int i = 0; i != n_legacy_hats; ++i)
{
if (registered_hats.contains(i))
continue;
// each hat gets 4 input instances associated with it, (up down left right)
for (u8 d = 0; d != 4; ++d)
AddInput(new LegacyHat(m_joystick, i, d, !is_hat_mapped[i]));
AddInput(new LegacyHat(m_joystick, i, d));
}
// Haptics
@ -531,39 +792,6 @@ GameController::~GameController()
SDL_JoystickClose(m_joystick);
}
std::string GameController::Motor::GetName() const
{
return "Motor";
}
void GameController::Motor::SetState(ControlState state)
{
Uint16 rumble = state * std::numeric_limits<Uint16>::max();
SDL_GameControllerRumble(m_gc, rumble, rumble, std::numeric_limits<Uint32>::max());
}
std::string GameController::MotorL::GetName() const
{
return "Motor L";
}
void GameController::MotorL::SetState(ControlState state)
{
Uint16 rumble = state * std::numeric_limits<Uint16>::max();
SDL_GameControllerRumble(m_gc, rumble, 0, std::numeric_limits<Uint32>::max());
}
std::string GameController::MotorR::GetName() const
{
return "Motor R";
}
void GameController::MotorR::SetState(ControlState state)
{
Uint16 rumble = state * std::numeric_limits<Uint16>::max();
SDL_GameControllerRumble(m_gc, 0, rumble, std::numeric_limits<Uint32>::max());
}
void InputBackend::UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>& devices_to_remove)
{
SDL_GameControllerUpdate();
@ -591,8 +819,7 @@ std::string GameController::Button::GetName() const
std::string GameController::Axis::GetName() const
{
// The triggers are only positive, and must not have a sign
if (m_axis >= 4)
if (IsTriggerAxis(m_axis))
return std::string(s_sdl_axis_names[m_axis]);
bool negative = m_range < 0;
@ -628,7 +855,19 @@ bool GameController::Button::IsMatchingName(std::string_view name) const
return GetName() == "Button W";
if (name == "Button Y")
return GetName() == "Button N";
return false;
// Match legacy names.
const auto bind = SDL_GameControllerGetBindForButton(m_gc, m_button);
switch (bind.bindType)
{
case SDL_CONTROLLER_BINDTYPE_BUTTON:
return name == GetLegacyButtonName(bind.value.button);
case SDL_CONTROLLER_BINDTYPE_HAT:
return name == GetLegacyHatName(bind.value.hat.hat,
GetDirectionFromHatMask(u8(bind.value.hat.hat_mask)));
default:
return false;
}
}
ControlState GameController::MotionInput::GetState() const
@ -639,21 +878,6 @@ ControlState GameController::MotionInput::GetState() const
}
// Legacy input
std::string GameController::LegacyButton::GetName() const
{
return "Button " + std::to_string(m_index);
}
std::string GameController::LegacyAxis::GetName() const
{
return "Axis " + std::to_string(m_index) + (m_range < 0 ? '-' : '+');
}
std::string GameController::LegacyHat::GetName() const
{
return "Hat " + std::to_string(m_index) + ' ' + "NESW"[m_direction];
}
ControlState GameController::LegacyButton::GetState() const
{
return SDL_JoystickGetButton(m_js, m_index);

View file

@ -3,242 +3,9 @@
#pragma once
#include <SDL.h>
#include "InputCommon/ControllerInterface/CoreDevice.h"
#include "InputCommon/ControllerInterface/InputBackend.h"
namespace ciface::SDL
{
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
class GameController : public Core::Device
{
private:
// GameController inputs
class Button : public Core::Device::Input
{
public:
std::string GetName() const override;
Button(SDL_GameController* gc, SDL_GameControllerButton button) : m_gc(gc), m_button(button) {}
ControlState GetState() const override;
bool IsMatchingName(std::string_view name) const override;
private:
SDL_GameController* const m_gc;
const SDL_GameControllerButton m_button;
};
class Axis : public Core::Device::Input
{
public:
std::string GetName() const override;
Axis(SDL_GameController* gc, Sint16 range, SDL_GameControllerAxis axis)
: m_gc(gc), m_range(range), m_axis(axis)
{
}
ControlState GetState() const override;
private:
SDL_GameController* const m_gc;
const Sint16 m_range;
const SDL_GameControllerAxis m_axis;
};
// Legacy inputs
class LegacyButton : public Core::Device::Input
{
public:
std::string GetName() const override;
LegacyButton(SDL_Joystick* js, int index, bool is_detectable)
: m_js(js), m_index(index), m_is_detectable(is_detectable)
{
}
bool IsDetectable() const override { return m_is_detectable; }
ControlState GetState() const override;
private:
SDL_Joystick* const m_js;
const int m_index;
const bool m_is_detectable;
};
class LegacyAxis : public Core::Device::Input
{
public:
std::string GetName() const override;
LegacyAxis(SDL_Joystick* js, int index, s16 range, bool is_detectable)
: m_js(js), m_index(index), m_range(range), m_is_detectable(is_detectable)
{
}
bool IsDetectable() const override { return m_is_detectable; }
ControlState GetState() const override;
private:
SDL_Joystick* const m_js;
const int m_index;
const s16 m_range;
const bool m_is_detectable;
};
class LegacyHat : public Input
{
public:
std::string GetName() const override;
LegacyHat(SDL_Joystick* js, int index, u8 direction, bool is_detectable)
: m_js(js), m_index(index), m_direction(direction), m_is_detectable(is_detectable)
{
}
bool IsDetectable() const override { return m_is_detectable; }
ControlState GetState() const override;
private:
SDL_Joystick* const m_js;
const int m_index;
const u8 m_direction;
const bool m_is_detectable;
};
// Rumble
class Motor : public Output
{
public:
explicit Motor(SDL_GameController* gc) : m_gc(gc) {}
std::string GetName() const override;
void SetState(ControlState state) override;
private:
SDL_GameController* const m_gc;
};
class MotorL : public Output
{
public:
explicit MotorL(SDL_GameController* gc) : m_gc(gc) {}
std::string GetName() const override;
void SetState(ControlState state) override;
private:
SDL_GameController* const m_gc;
};
class MotorR : public Output
{
public:
explicit MotorR(SDL_GameController* gc) : m_gc(gc) {}
std::string GetName() const override;
void SetState(ControlState state) override;
private:
SDL_GameController* const m_gc;
};
class HapticEffect : public Output
{
public:
HapticEffect(SDL_Haptic* haptic);
~HapticEffect();
protected:
virtual bool UpdateParameters(s16 value) = 0;
static void SetDirection(SDL_HapticDirection* dir);
SDL_HapticEffect m_effect = {};
static constexpr u16 DISABLED_EFFECT_TYPE = 0;
private:
virtual void SetState(ControlState state) override final;
void UpdateEffect();
SDL_Haptic* const m_haptic;
int m_id = -1;
};
class ConstantEffect : public HapticEffect
{
public:
ConstantEffect(SDL_Haptic* haptic);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
};
class RampEffect : public HapticEffect
{
public:
RampEffect(SDL_Haptic* haptic);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
};
class PeriodicEffect : public HapticEffect
{
public:
PeriodicEffect(SDL_Haptic* haptic, u16 waveform);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
const u16 m_waveform;
};
class LeftRightEffect : public HapticEffect
{
public:
enum class Motor : u8
{
Weak,
Strong,
};
LeftRightEffect(SDL_Haptic* haptic, Motor motor);
std::string GetName() const override;
private:
bool UpdateParameters(s16 value) override;
const Motor m_motor;
};
class MotionInput : public Input
{
public:
MotionInput(std::string name, SDL_GameController* gc, SDL_SensorType type, int index,
ControlState scale)
: m_name(std::move(name)), m_gc(gc), m_type(type), m_index(index), m_scale(scale){};
std::string GetName() const override { return m_name; };
bool IsDetectable() const override { return false; };
ControlState GetState() const override;
private:
std::string m_name;
SDL_GameController* const m_gc;
SDL_SensorType const m_type;
int const m_index;
ControlState const m_scale;
};
public:
GameController(SDL_GameController* const gamecontroller, SDL_Joystick* const joystick,
const int sdl_index);
~GameController();
std::string GetName() const override;
std::string GetSource() const override;
int GetSDLIndex() const;
private:
SDL_GameController* const m_gamecontroller;
std::string m_name;
int m_sdl_index;
SDL_Joystick* const m_joystick;
SDL_Haptic* m_haptic = nullptr;
};
} // namespace ciface::SDL

View file

@ -201,6 +201,17 @@ Device::Device(std::unique_ptr<WiimoteReal::Wiimote> wiimote) : m_wiimote(std::m
AddInput(new UndetectableAnalogInput<float>(&m_ir_state.distance, "IR Distance", 1));
// Raw IR Objects.
for (std::size_t i = 0; i < 4; ++i)
{
AddInput(new UndetectableAnalogInput<float>(&m_ir_state.raw_ir_object_position[i].x,
fmt::format("IR Object {} X", i + 1), 1));
AddInput(new UndetectableAnalogInput<float>(&m_ir_state.raw_ir_object_position[i].y,
fmt::format("IR Object {} Y", i + 1), 1));
AddInput(new UndetectableAnalogInput<float>(&m_ir_state.raw_ir_object_size[i],
fmt::format("IR Object {} Size", i + 1), 1));
}
// Raw gyroscope.
static constexpr std::array<std::array<const char*, 2>, 3> gyro_names = {{
{"Gyro Pitch Down", "Gyro Pitch Up"},
@ -1178,8 +1189,7 @@ void Device::ProcessInputReport(WiimoteReal::Report& report)
// Process IR data.
if (manipulator->HasIR() && m_ir_state.IsFullyConfigured())
{
m_ir_state.ProcessData(
Common::BitCastPtr<std::array<WiimoteEmu::IRBasic, 2>>(manipulator->GetIRDataPtr()));
m_ir_state.ProcessData(*manipulator);
}
// Process extension data.
@ -1251,7 +1261,7 @@ void Device::UpdateOrientation()
float(MathUtil::PI);
}
void Device::IRState::ProcessData(const std::array<WiimoteEmu::IRBasic, 2>& data)
void Device::IRState::ProcessData(const DataReportManipulator& manipulator)
{
// A better implementation might extrapolate points when they fall out of camera view.
// But just averaging visible points actually seems to work very well.
@ -1263,18 +1273,54 @@ void Device::IRState::ProcessData(const std::array<WiimoteEmu::IRBasic, 2>& data
const auto camera_max = IRObject(WiimoteEmu::CameraLogic::CAMERA_RES_X - 1,
WiimoteEmu::CameraLogic::CAMERA_RES_Y - 1);
const auto add_point = [&](IRObject point) {
const auto add_point = [&](IRObject point, u8 size, size_t idx) {
// Non-visible points are 0xFF-filled.
if (point.y > camera_max.y)
{
raw_ir_object_position[idx].x = 0.0f;
raw_ir_object_position[idx].y = 0.0f;
raw_ir_object_size[idx] = 0.0f;
return;
}
raw_ir_object_position[idx].x = static_cast<float>(point.x) / camera_max.x;
raw_ir_object_position[idx].y = static_cast<float>(point.y) / camera_max.y;
raw_ir_object_size[idx] = static_cast<float>(size) / 15.0f;
points.Push(Common::Vec2(point));
};
for (auto& block : data)
size_t object_index = 0;
switch (manipulator.GetIRReportFormat())
{
add_point(block.GetObject1());
add_point(block.GetObject2());
case IRReportFormat::Basic:
{
const std::array<WiimoteEmu::IRBasic, 2> data =
Common::BitCastPtr<std::array<WiimoteEmu::IRBasic, 2>>(manipulator.GetIRDataPtr());
for (const auto& block : data)
{
// size is not reported by IRBasic, just assume a typical size
add_point(block.GetObject1(), 2, object_index);
++object_index;
add_point(block.GetObject2(), 2, object_index);
++object_index;
}
break;
}
case IRReportFormat::Extended:
{
const std::array<WiimoteEmu::IRExtended, 4> data =
Common::BitCastPtr<std::array<WiimoteEmu::IRExtended, 4>>(manipulator.GetIRDataPtr());
for (const auto& object : data)
{
add_point(object.GetPosition(), object.size, object_index);
++object_index;
}
break;
}
default:
// unsupported format
return;
}
is_hidden = !points.Count();

View file

@ -126,7 +126,7 @@ private:
{
static u32 GetDesiredIRSensitivity();
void ProcessData(const std::array<WiimoteEmu::IRBasic, 2>&);
void ProcessData(const DataReportManipulator& manipulator);
bool IsFullyConfigured() const;
u32 current_sensitivity = u32(-1);
@ -139,6 +139,9 @@ private:
float distance = 0;
bool is_hidden = true;
std::array<Common::Vec2, 4> raw_ir_object_position;
std::array<float, 4> raw_ir_object_size;
};
class ReportHandler

View file

@ -20,13 +20,25 @@
#pragma comment(lib, "OneCoreUAP.Lib")
// Dolphin's render window
static HWND s_hwnd;
static std::mutex s_populate_mutex;
// TODO is this really needed?
static Common::Flag s_first_populate_devices_asked;
static HCMNOTIFICATION s_notify_handle;
namespace ciface::Win32
{
class InputBackend final : public ciface::InputBackend
{
public:
InputBackend(ControllerInterface* controller_interface);
~InputBackend();
void PopulateDevices() override;
void HandleWindowChange() override;
HWND GetHWND();
};
} // namespace ciface::Win32
_Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALLBACK
OnDevicesChanged(_In_ HCMNOTIFICATION hNotify, _In_opt_ PVOID Context,
_In_ CM_NOTIFY_ACTION Action,
@ -43,8 +55,9 @@ _Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALL
std::lock_guard lk_population(s_populate_mutex);
// TODO: we could easily use the message passed alongside this event, which tells
// whether a device was added or removed, to avoid removing old, still connected, devices
g_controller_interface.PlatformPopulateDevices([] {
ciface::DInput::PopulateDevices(s_hwnd);
g_controller_interface.PlatformPopulateDevices([&] {
ciface::DInput::PopulateDevices(
static_cast<ciface::Win32::InputBackend*>(Context)->GetHWND());
ciface::XInput::PopulateDevices();
});
}
@ -52,10 +65,21 @@ _Pre_satisfies_(EventDataSize >= sizeof(CM_NOTIFY_EVENT_DATA)) static DWORD CALL
return ERROR_SUCCESS;
}
void ciface::Win32::Init(void* hwnd)
namespace ciface::Win32
{
s_hwnd = static_cast<HWND>(hwnd);
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
HWND InputBackend::GetHWND()
{
return static_cast<HWND>(GetControllerInterface().GetWindowSystemInfo().render_window);
}
InputBackend::InputBackend(ControllerInterface* controller_interface)
: ciface::InputBackend(controller_interface)
{
XInput::Init();
WGInput::Init();
@ -63,35 +87,32 @@ void ciface::Win32::Init(void* hwnd)
.FilterType = CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE,
.u{.DeviceInterface{.ClassGuid = GUID_DEVINTERFACE_HID}}};
const CONFIGRET cfg_rv =
CM_Register_Notification(&notify_filter, nullptr, OnDevicesChanged, &s_notify_handle);
CM_Register_Notification(&notify_filter, this, OnDevicesChanged, &s_notify_handle);
if (cfg_rv != CR_SUCCESS)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "CM_Register_Notification failed: {:x}", cfg_rv);
}
}
void ciface::Win32::PopulateDevices(void* hwnd)
void InputBackend::PopulateDevices()
{
s_hwnd = static_cast<HWND>(hwnd);
std::lock_guard lk_population(s_populate_mutex);
s_first_populate_devices_asked.Set();
ciface::DInput::PopulateDevices(s_hwnd);
ciface::DInput::PopulateDevices(GetHWND());
ciface::XInput::PopulateDevices();
ciface::WGInput::PopulateDevices();
}
void ciface::Win32::ChangeWindow(void* hwnd)
void InputBackend::HandleWindowChange()
{
s_hwnd = static_cast<HWND>(hwnd);
std::lock_guard lk_population(s_populate_mutex);
ciface::DInput::ChangeWindow(s_hwnd);
ciface::DInput::ChangeWindow(GetHWND());
}
void ciface::Win32::DeInit()
InputBackend::~InputBackend()
{
s_first_populate_devices_asked.Clear();
DInput::DeInit();
s_hwnd = nullptr;
if (s_notify_handle)
{
@ -106,3 +127,5 @@ void ciface::Win32::DeInit()
XInput::DeInit();
WGInput::DeInit();
}
} // namespace ciface::Win32

View file

@ -3,10 +3,9 @@
#pragma once
#include "InputCommon/ControllerInterface/InputBackend.h"
namespace ciface::Win32
{
void Init(void* hwnd);
void PopulateDevices(void* hwnd);
void ChangeWindow(void* hwnd);
void DeInit();
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
} // namespace ciface::Win32

View file

@ -66,9 +66,38 @@ constexpr int XINPUT_MAJOR = 2, XINPUT_MINOR = 1;
namespace ciface::XInput2
{
// This function will add zero or more KeyboardMouse objects to devices.
void PopulateDevices(void* const hwnd)
constexpr std::string_view SOURCE_NAME = "XInput2";
class InputBackend final : public ciface::InputBackend
{
public:
using ciface::InputBackend::InputBackend;
void PopulateDevices() override;
void HandleWindowChange() override;
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
{
return std::make_unique<InputBackend>(controller_interface);
}
void InputBackend::HandleWindowChange()
{
GetControllerInterface().RemoveDevice(
[](const auto* dev) { return dev->GetSource() == SOURCE_NAME; }, true);
PopulateDevices();
}
// This function will add zero or more KeyboardMouse objects to devices.
void InputBackend::PopulateDevices()
{
const WindowSystemInfo wsi = GetControllerInterface().GetWindowSystemInfo();
if (wsi.type != WindowSystemType::X11)
return;
const auto hwnd = wsi.render_window;
Display* dpy = XOpenDisplay(nullptr);
// xi_opcode is important; it will be used to identify XInput events by
@ -119,7 +148,7 @@ void PopulateDevices(void* const hwnd)
}
// Since current_master is a master pointer, its attachment must
// be a master keyboard.
g_controller_interface.AddDevice(
GetControllerInterface().AddDevice(
std::make_shared<KeyboardMouse>((Window)hwnd, xi_opcode, current_master->deviceid,
current_master->attachment, scroll_increment));
}
@ -382,7 +411,7 @@ std::string KeyboardMouse::GetName() const
std::string KeyboardMouse::GetSource() const
{
return "XInput2";
return std::string(SOURCE_NAME);
}
KeyboardMouse::Key::Key(Display* const display, KeyCode keycode, const char* keyboard)

View file

@ -16,10 +16,11 @@ extern "C" {
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/InputBackend.h"
namespace ciface::XInput2
{
void PopulateDevices(void* const hwnd);
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface);
class KeyboardMouse : public Core::Device
{

View file

@ -21,6 +21,7 @@
#include "Common/ScopeGuard.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "Common/WorkQueueThread.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
namespace ciface::evdev
@ -34,6 +35,12 @@ public:
void RemoveDevnodeObject(const std::string&);
// Linux has the strange behavior that closing file descriptors of event devices can be
// surprisingly slow, in the range of 20-70 milliseconds. For modern systems that have maybe 30
// event devices this can quickly add up, leading to visibly slow startup. So we close FDs on a
// separate thread *shrug*
void CloseDescriptor(int fd) { m_cleanup_thread.Push(fd); }
private:
std::shared_ptr<evdevDevice>
FindDeviceWithUniqueIDAndPhysicalLocation(const char* unique_id, const char* physical_location);
@ -55,6 +62,8 @@ private:
// as devices can be destroyed by any thread at any time. As of now it's protected
// by ControllerInterface::m_devices_population_mutex.
std::map<std::string, std::weak_ptr<evdevDevice>> m_devnode_objects;
Common::WorkQueueThread<int> m_cleanup_thread;
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
@ -273,7 +282,7 @@ void InputBackend::AddDeviceNode(const char* devnode)
if (libevdev_new_from_fd(fd, &dev) != 0)
{
// This usually fails because the device node isn't an evdev device, such as /dev/input/js0
close(fd);
CloseDescriptor(fd);
return;
}
@ -415,7 +424,7 @@ void InputBackend::StopHotplugThread()
}
InputBackend::InputBackend(ControllerInterface* controller_interface)
: ciface::InputBackend(controller_interface)
: ciface::InputBackend(controller_interface), m_cleanup_thread("evdev cleanup", close)
{
StartHotplugThread();
}
@ -665,7 +674,7 @@ evdevDevice::~evdevDevice()
{
m_input_backend.RemoveDevnodeObject(node.devnode);
libevdev_free(node.device);
close(node.fd);
m_input_backend.CloseDescriptor(node.fd);
}
}