diff --git a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp index 1fc408ac68..b5d1aa4e6c 100644 --- a/pcsx2-qt/Settings/ControllerBindingWidgets.cpp +++ b/pcsx2-qt/Settings/ControllerBindingWidgets.cpp @@ -220,7 +220,7 @@ void ControllerBindingWidget::doDeviceAutomaticBinding(const QString& device) if (mapping.empty()) { QMessageBox::critical(QtUtils::GetRootWidget(this), tr("Automatic Binding"), - tr("No generic bindings were generated for device '%1'").arg(device)); + tr("No generic bindings were generated for device '%1'. The controller/source may not support automatic mapping.").arg(device)); return; } diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp index 55c223895a..f122c2a91f 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.cpp @@ -32,13 +32,24 @@ ControllerGlobalSettingsWidget::ControllerGlobalSettingsWidget(QWidget* parent, SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLSource, "InputSources", "SDL", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableSDLEnhancedMode, "InputSources", "SDLControllerEnhancedMode", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableMouseMapping, "UI", "EnableMouseMapping", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort1, "Pad", "MultitapPort1", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileBool(sif, m_ui.multitapPort2, "Pad", "MultitapPort2", false); ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerXScale, "Pad", "PointerXScale", 8.0f); ControllerSettingWidgetBinder::BindWidgetToInputProfileFloat(sif, m_ui.pointerYScale, "Pad", "PointerYScale", 8.0f); +#ifdef _WIN32 + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableXInputSource, "InputSources", "XInput", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableDInputSource, "InputSources", "DInput", false); +#else + m_ui.mainLayout->removeWidget(m_ui.xinputGroup); + m_ui.xinputGroup->deleteLater(); + m_ui.xinputGroup = nullptr; + m_ui.mainLayout->removeWidget(m_ui.dinputGroup); + m_ui.dinputGroup->deleteLater(); + m_ui.dinputGroup = nullptr; +#endif + if (dialog->isEditingProfile()) { m_ui.useProfileHotkeyBindings->setChecked(m_dialog->getBoolValue("Pad", "UseProfileHotkeyBindings", false)); diff --git a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui index 0be624b00f..434863a563 100644 --- a/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui +++ b/pcsx2-qt/Settings/ControllerGlobalSettingsWidget.ui @@ -26,33 +26,8 @@ 0 - - - - Detected Devices - - - - - - - 200 - 0 - - - - - 200 - 16777215 - - - - - - - - + SDL Input Source @@ -85,7 +60,7 @@ - + XInput Source @@ -110,7 +85,33 @@ - + + + + DInput Source + + + + + + The DInput source provides support for legacy controllers which do not support XInput. Accessing these controllers via SDL instead is recommended, but DirectInput can be used if they are not compatible with SDL. + + + true + + + + + + + Enable DInput Input Source + + + + + + + Qt::Vertical @@ -123,7 +124,7 @@ - + Profile Settings @@ -149,8 +150,8 @@ - - + + Controller Multitap @@ -182,8 +183,8 @@ - - + + Mouse/Pointer Source @@ -322,6 +323,31 @@ + + + + Detected Devices + + + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + + + + diff --git a/pcsx2/Frontend/DInputSource.cpp b/pcsx2/Frontend/DInputSource.cpp new file mode 100644 index 0000000000..e879e6999e --- /dev/null +++ b/pcsx2/Frontend/DInputSource.cpp @@ -0,0 +1,446 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 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 received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#define INITGUID + +#include "Frontend/DInputSource.h" +#include "Frontend/InputManager.h" +#include "common/Assertions.h" +#include "common/Console.h" +#include "common/StringUtil.h" + +#include "fmt/format.h" +#include +#include + +using PFNDIRECTINPUT8CREATE = HRESULT(WINAPI*)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut, LPUNKNOWN punkOuter); +using PFNGETDFDIJOYSTICK = LPCDIDATAFORMAT(WINAPI*)(); + +DInputSource::DInputSource() = default; + +DInputSource::~DInputSource() +{ + m_controllers.clear(); + m_dinput.Reset(); + if (m_dinput_module) + FreeLibrary(m_dinput_module); +} + +std::array DInputSource::GetHatButtons(DWORD hat) +{ + std::array buttons = {}; + + const WORD hv = LOWORD(hat); + if (hv != 0xFFFF) + { + if ((hv >= 0 && hv < 9000) || hv >= 31500) + buttons[HAT_DIRECTION_UP] = true; + if (hv >= 4500 && hv < 18000) + buttons[HAT_DIRECTION_RIGHT] = true; + if (hv >= 13500 && hv < 27000) + buttons[HAT_DIRECTION_DOWN] = true; + if (hv >= 22500) + buttons[HAT_DIRECTION_LEFT] = true; + } + + return buttons; +} + +std::string DInputSource::GetDeviceIdentifier(u32 index) +{ + return fmt::format("DInput-{}", index); +} + +static constexpr std::array s_hat_directions = {{"Up", "Right", "Down", "Left"}}; + +bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock& settings_lock) +{ + m_dinput_module = LoadLibraryW(L"dinput8"); + if (!m_dinput_module) + { + Console.Error("Failed to load DInput module."); + return false; + } + + PFNDIRECTINPUT8CREATE create = reinterpret_cast(GetProcAddress(m_dinput_module, "DirectInput8Create")); + PFNGETDFDIJOYSTICK get_joystick_data_format = reinterpret_cast(GetProcAddress(m_dinput_module, "GetdfDIJoystick")); + if (!create || !get_joystick_data_format) + { + Console.Error("Failed to get DInput function pointers."); + return false; + } + + HRESULT hr = create( + GetModuleHandleA(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W, reinterpret_cast(m_dinput.GetAddressOf()), nullptr); + m_joystick_data_format = get_joystick_data_format(); + if (FAILED(hr) || !m_joystick_data_format) + { + Console.Error("DirectInput8Create() failed: %08X", hr); + return false; + } + + // need to release the lock while we're enumerating, because we call winId(). + settings_lock.unlock(); + const std::optional toplevel_wi(Host::GetTopLevelWindowInfo()); + if (toplevel_wi.has_value() && toplevel_wi->type == WindowInfo::Type::Win32) + AddDevices(static_cast(toplevel_wi->window_handle)); + else + Console.Error("Missing top level window, cannot add DInput devices."); + settings_lock.lock(); + + return true; +} + +void DInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) +{ + // noop +} + +void DInputSource::Shutdown() +{ + while (!m_controllers.empty()) + { + Host::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast(m_controllers.size() - 1))); + m_controllers.pop_back(); + } +} + +static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef) +{ + static_cast*>(pvRef)->push_back(*lpddi); + return DIENUM_CONTINUE; +} + +void DInputSource::AddDevices(HWND toplevel_window) +{ + std::vector devices; + m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY); + + DevCon.WriteLn("Enumerated %zu devices", devices.size()); + + for (DIDEVICEINSTANCEW inst : devices) + { + ControllerData cd; + HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr); + if (FAILED(hr)) + { + Console.Warning("Failed to create instance of device [%s, %s]", inst.tszProductName, inst.tszInstanceName); + continue; + } + + const std::string name(StringUtil::WideStringToUTF8String(inst.tszProductName)); + if (AddDevice(cd, toplevel_window, name)) + { + const u32 index = static_cast(m_controllers.size()); + m_controllers.push_back(std::move(cd)); + Host::OnInputDeviceConnected(GetDeviceIdentifier(index), name); + } + } +} + +bool DInputSource::AddDevice(ControllerData& cd, HWND toplevel_window, const std::string& name) +{ + HRESULT hr = cd.device->SetCooperativeLevel(toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE); + if (FAILED(hr)) + { + hr = cd.device->SetCooperativeLevel(toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE); + if (FAILED(hr)) + { + Console.Error("Failed to set cooperative level for '%s'", name.c_str()); + return false; + } + + Console.Warning("Failed to set exclusive mode for '%s'", name.c_str()); + } + + hr = cd.device->SetDataFormat(m_joystick_data_format); + if (FAILED(hr)) + { + Console.Error("Failed to set data format for '%s'", name.c_str()); + return false; + } + + hr = cd.device->Acquire(); + if (FAILED(hr)) + { + Console.Error("Failed to acquire device '%s'", name.c_str()); + return false; + } + + DIDEVCAPS caps = {}; + caps.dwSize = sizeof(caps); + hr = cd.device->GetCapabilities(&caps); + if (FAILED(hr)) + { + Console.Error("Failed to get capabilities for '%s'", name.c_str()); + return false; + } + + cd.num_buttons = caps.dwButtons; + + static constexpr const u32 axis_offsets[] = {offsetof(DIJOYSTATE, lX), offsetof(DIJOYSTATE, lY), offsetof(DIJOYSTATE, lZ), + offsetof(DIJOYSTATE, lRz), offsetof(DIJOYSTATE, lRx), offsetof(DIJOYSTATE, lRy), offsetof(DIJOYSTATE, rglSlider[0]), + offsetof(DIJOYSTATE, rglSlider[1])}; + for (const u32 offset : axis_offsets) + { + // ask for 16 bits of axis range + DIPROPRANGE range = {}; + range.diph.dwSize = sizeof(range); + range.diph.dwHeaderSize = sizeof(range.diph); + range.diph.dwHow = DIPH_BYOFFSET; + range.diph.dwObj = static_cast(offset); + range.lMin = std::numeric_limits::min(); + range.lMax = std::numeric_limits::max(); + hr = cd.device->SetProperty(DIPROP_RANGE, &range.diph); + + // did it apply? + if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph))) + cd.axis_offsets.push_back(offset); + } + + cd.num_hats = caps.dwPOVs; + + hr = cd.device->Poll(); + if (hr == DI_NOEFFECT) + cd.needs_poll = false; + else if (hr != DI_OK) + Console.Warning("Polling device '%s' failed: %08X", name.c_str(), hr); + + hr = cd.device->GetDeviceState(sizeof(cd.last_state), &cd.last_state); + if (hr != DI_OK) + Console.Warning("GetDeviceState() for '%s' failed: %08X", name.c_str(), hr); + + Console.WriteLn( + "%s has %u buttons, %u axes, %u hats", name.c_str(), cd.num_buttons, static_cast(cd.axis_offsets.size()), cd.num_hats); + + return (cd.num_buttons > 0 || !cd.axis_offsets.empty() || cd.num_hats > 0); +} + +void DInputSource::PollEvents() +{ + for (size_t i = 0; i < m_controllers.size();) + { + ControllerData& cd = m_controllers[i]; + if (cd.needs_poll) + cd.device->Poll(); + + DIJOYSTATE js; + HRESULT hr = cd.device->GetDeviceState(sizeof(js), &js); + if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED) + { + hr = cd.device->Acquire(); + if (hr == DI_OK) + hr = cd.device->GetDeviceState(sizeof(js), &js); + + if (hr != DI_OK) + { + Host::OnInputDeviceDisconnected(GetDeviceIdentifier(static_cast(i))); + m_controllers.erase(m_controllers.begin() + i); + continue; + } + } + else if (hr != DI_OK) + { + Console.Warning("GetDeviceState() failed: %08X", hr); + i++; + continue; + } + + CheckForStateChanges(i, js); + i++; + } +} + +std::vector> DInputSource::EnumerateDevices() +{ + std::vector> ret; + for (size_t i = 0; i < m_controllers.size(); i++) + { + DIDEVICEINSTANCEW dii = {sizeof(DIDEVICEINSTANCEW)}; + std::string name; + if (SUCCEEDED(m_controllers[i].device->GetDeviceInfo(&dii))) + name = StringUtil::WideStringToUTF8String(dii.tszProductName); + + if (name.empty()) + name = "Unknown"; + + ret.emplace_back(GetDeviceIdentifier(static_cast(i)), std::move(name)); + } + + return ret; +} + +std::vector DInputSource::EnumerateMotors() +{ + return {}; +} + +bool DInputSource::GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) +{ + return {}; +} + +void DInputSource::UpdateMotorState(InputBindingKey key, float intensity) +{ + // not supported +} + +void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) +{ + // not supported +} + +std::optional DInputSource::ParseKeyString(const std::string_view& device, const std::string_view& binding) +{ + if (!StringUtil::StartsWith(device, "DInput-") || binding.empty()) + return std::nullopt; + + const std::optional player_id = StringUtil::FromChars(device.substr(7)); + if (!player_id.has_value() || player_id.value() < 0) + return std::nullopt; + + InputBindingKey key = {}; + key.source_type = InputSourceType::DInput; + key.source_index = static_cast(player_id.value()); + + if (StringUtil::StartsWith(binding, "+Axis") || StringUtil::StartsWith(binding, "-Axis")) + { + const std::optional axis_index = StringUtil::FromChars(binding.substr(5)); + if (!axis_index.has_value()) + return std::nullopt; + + key.source_subtype = InputSubclass::ControllerAxis; + key.data = axis_index.value(); + key.negative = (binding[0] == '-'); + return key; + } + else if (StringUtil::StartsWith(binding, "Hat")) + { + if (binding[3] < '0' || binding[3] > '9' || binding.length() < 5) + return std::nullopt; + + const u32 hat_index = binding[3] - '0'; + const std::string_view hat_dir(binding.substr(4)); + for (u32 i = 0; i < NUM_HAT_DIRECTIONS; i++) + { + if (hat_dir == s_hat_directions[i]) + { + key.source_subtype = InputSubclass::ControllerButton; + key.data = MAX_NUM_BUTTONS + hat_index * NUM_HAT_DIRECTIONS + i; + return key; + } + } + + // bad direction + return std::nullopt; + } + else if (StringUtil::StartsWith(binding, "Button")) + { + const std::optional button_index = StringUtil::FromChars(binding.substr(6)); + if (!button_index.has_value()) + return std::nullopt; + + key.source_subtype = InputSubclass::ControllerButton; + key.data = button_index.value(); + return key; + } + + // unknown axis/button + return std::nullopt; +} + +std::string DInputSource::ConvertKeyToString(InputBindingKey key) +{ + std::string ret; + + if (key.source_type == InputSourceType::DInput) + { + if (key.source_subtype == InputSubclass::ControllerAxis) + { + ret = fmt::format("DInput-{}/{}Axis{}", u32(key.source_index), key.negative ? '-' : '+', u32(key.data)); + } + else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS) + { + const u32 hat_num = (key.data - MAX_NUM_BUTTONS) / NUM_HAT_DIRECTIONS; + const u32 hat_dir = (key.data - MAX_NUM_BUTTONS) % NUM_HAT_DIRECTIONS; + ret = fmt::format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]); + } + else if (key.source_subtype == InputSubclass::ControllerButton) + { + ret = fmt::format("DInput-{}/Button{}", u32(key.source_index), u32(key.data)); + } + } + + return ret; +} + +void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state) +{ + ControllerData& cd = m_controllers[index]; + DIJOYSTATE& last_state = cd.last_state; + + for (size_t i = 0; i < cd.axis_offsets.size(); i++) + { + LONG new_value; + LONG old_value; + std::memcpy(&old_value, reinterpret_cast(&cd.last_state) + cd.axis_offsets[i], sizeof(old_value)); + std::memcpy(&new_value, reinterpret_cast(&new_state) + cd.axis_offsets[i], sizeof(new_value)); + if (old_value != new_value) + { + std::memcpy(reinterpret_cast(&cd.last_state) + cd.axis_offsets[i], &new_value, sizeof(new_value)); + + // TODO: Use the range from caps? + const float value = static_cast(new_value) / (new_value < 0 ? 32768.0f : 32767.0f); + InputManager::InvokeEvents(MakeGenericControllerAxisKey(InputSourceType::DInput, static_cast(index), static_cast(i)), + value, GenericInputBinding::Unknown); + } + } + + for (u32 i = 0; i < cd.num_buttons; i++) + { + if (last_state.rgbButtons[i] != new_state.rgbButtons[i]) + { + last_state.rgbButtons[i] = new_state.rgbButtons[i]; + + const float value = (new_state.rgbButtons[i] != 0) ? 1.0f : 0.0f; + InputManager::InvokeEvents( + MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast(index), i), value, GenericInputBinding::Unknown); + } + } + + for (u32 i = 0; i < cd.num_hats; i++) + { + if (last_state.rgdwPOV[i] != new_state.rgdwPOV[i]) + { + // map hats to the last buttons + const std::array old_buttons(GetHatButtons(last_state.rgdwPOV[i])); + const std::array new_buttons(GetHatButtons(new_state.rgdwPOV[i])); + last_state.rgdwPOV[i] = new_state.rgdwPOV[i]; + + for (u32 j = 0; j < NUM_HAT_DIRECTIONS; j++) + { + if (old_buttons[j] != new_buttons[j]) + { + const float value = (new_buttons[j] ? 1.0f : 0.0f); + InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast(index), + cd.num_buttons + (i * NUM_HAT_DIRECTIONS) + j), + value, GenericInputBinding::Unknown); + } + } + } + } +} diff --git a/pcsx2/Frontend/DInputSource.h b/pcsx2/Frontend/DInputSource.h new file mode 100644 index 0000000000..b5bd42a05c --- /dev/null +++ b/pcsx2/Frontend/DInputSource.h @@ -0,0 +1,93 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2022 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 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 received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once +#define DIRECTINPUT_VERSION 0x0800 +#include "common/RedtapeWindows.h" +#include "Frontend/InputSource.h" +#include +#include +#include +#include +#include +#include + +class DInputSource final : public InputSource +{ +public: + enum HAT_DIRECTION : u32 + { + HAT_DIRECTION_UP = 0, + HAT_DIRECTION_DOWN = 1, + HAT_DIRECTION_LEFT = 2, + HAT_DIRECTION_RIGHT = 3, + NUM_HAT_DIRECTIONS = 4, + }; + + enum : u32 + { + MAX_NUM_BUTTONS = 32, + }; + + DInputSource(); + ~DInputSource() override; + + bool Initialize(SettingsInterface& si, std::unique_lock& settings_lock) override; + void UpdateSettings(SettingsInterface& si, std::unique_lock& settings_lock) override; + void Shutdown() override; + + void PollEvents() override; + std::vector> EnumerateDevices() override; + std::vector EnumerateMotors() override; + bool GetGenericBindingMapping(const std::string_view& device, GenericInputBindingMapping* mapping) override; + void UpdateMotorState(InputBindingKey key, float intensity) override; + void UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity, float small_intensity) override; + + std::optional ParseKeyString(const std::string_view& device, const std::string_view& binding) override; + std::string ConvertKeyToString(InputBindingKey key) override; + +private: + template + using ComPtr = Microsoft::WRL::ComPtr; + + struct ControllerData + { + ComPtr device; + DIJOYSTATE last_state = {}; + std::vector axis_offsets; + u32 num_buttons = 0; + + // NOTE: We expose hats as num_buttons + (hat_index * 4) + direction. + u32 num_hats = 0; + + bool needs_poll = true; + }; + + using ControllerDataArray = std::vector; + + static std::array GetHatButtons(DWORD hat); + static std::string GetDeviceIdentifier(u32 index); + + void AddDevices(HWND toplevel_window); + bool AddDevice(ControllerData& cd, HWND toplevel_window, const std::string& name); + + void CheckForStateChanges(size_t index, const DIJOYSTATE& new_state); + + ControllerDataArray m_controllers; + + HMODULE m_dinput_module{}; + LPCDIDATAFORMAT m_joystick_data_format{}; + ComPtr m_dinput; +}; diff --git a/pcsx2/Frontend/InputManager.cpp b/pcsx2/Frontend/InputManager.cpp index 0fbf987e93..b4289f11c0 100644 --- a/pcsx2/Frontend/InputManager.cpp +++ b/pcsx2/Frontend/InputManager.cpp @@ -392,6 +392,7 @@ static std::array(InputSourceType::Count)> s_input "Keyboard", "Mouse", #ifdef _WIN32 + "DInput", "XInput", #endif #ifdef SDL_BUILD @@ -418,6 +419,9 @@ bool InputManager::GetInputSourceDefaultEnabled(InputSourceType type) return true; #ifdef _WIN32 + case InputSourceType::DInput: + return false; + case InputSourceType::XInput: // Disable xinput by default if we have SDL. #ifdef SDL_BUILD @@ -1177,6 +1181,7 @@ static void UpdateInputSourceState(SettingsInterface& si, std::unique_lock& settings_lock) { #ifdef _WIN32 + UpdateInputSourceState(si, settings_lock, InputSourceType::DInput); UpdateInputSourceState(si, settings_lock, InputSourceType::XInput); #endif #ifdef SDL_BUILD diff --git a/pcsx2/Frontend/InputManager.h b/pcsx2/Frontend/InputManager.h index e1a67d82b6..4eb53d5c89 100644 --- a/pcsx2/Frontend/InputManager.h +++ b/pcsx2/Frontend/InputManager.h @@ -32,7 +32,7 @@ enum class InputSourceType : u32 Keyboard, Pointer, #ifdef _WIN32 - //DInput, + DInput, XInput, #endif #ifdef SDL_BUILD diff --git a/pcsx2/pcsx2core.vcxproj b/pcsx2/pcsx2core.vcxproj index 2ef4c94201..448a30d758 100644 --- a/pcsx2/pcsx2core.vcxproj +++ b/pcsx2/pcsx2core.vcxproj @@ -193,6 +193,7 @@ + @@ -519,6 +520,7 @@ + diff --git a/pcsx2/pcsx2core.vcxproj.filters b/pcsx2/pcsx2core.vcxproj.filters index 4845da9e5f..48823a76d1 100644 --- a/pcsx2/pcsx2core.vcxproj.filters +++ b/pcsx2/pcsx2core.vcxproj.filters @@ -1293,6 +1293,9 @@ Host + + Host + @@ -2156,6 +2159,9 @@ Host + + Host +