duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

dinput_source.cpp (15974B)


      1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #define INITGUID
      5 
      6 #include "dinput_source.h"
      7 #include "input_manager.h"
      8 #include "platform_misc.h"
      9 
     10 #include "common/assert.h"
     11 #include "common/log.h"
     12 #include "common/string_util.h"
     13 
     14 #include "fmt/format.h"
     15 
     16 #include <cmath>
     17 #include <limits>
     18 Log_SetChannel(DInputSource);
     19 
     20 using PFNDIRECTINPUT8CREATE = HRESULT(WINAPI*)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID* ppvOut,
     21                                                LPUNKNOWN punkOuter);
     22 using PFNGETDFDIJOYSTICK = LPCDIDATAFORMAT(WINAPI*)();
     23 
     24 DInputSource::DInputSource() = default;
     25 
     26 DInputSource::~DInputSource()
     27 {
     28   m_controllers.clear();
     29   m_dinput.Reset();
     30   if (m_dinput_module)
     31     FreeLibrary(m_dinput_module);
     32 }
     33 
     34 std::array<bool, DInputSource::NUM_HAT_DIRECTIONS> DInputSource::GetHatButtons(DWORD hat)
     35 {
     36   std::array<bool, NUM_HAT_DIRECTIONS> buttons = {};
     37 
     38   const WORD hv = LOWORD(hat);
     39   if (hv != 0xFFFF)
     40   {
     41     if ((hv >= 0 && hv < 9000) || hv >= 31500)
     42       buttons[HAT_DIRECTION_UP] = true;
     43     if (hv >= 4500 && hv < 18000)
     44       buttons[HAT_DIRECTION_RIGHT] = true;
     45     if (hv >= 13500 && hv < 27000)
     46       buttons[HAT_DIRECTION_DOWN] = true;
     47     if (hv >= 22500)
     48       buttons[HAT_DIRECTION_LEFT] = true;
     49   }
     50 
     51   return buttons;
     52 }
     53 
     54 std::string DInputSource::GetDeviceIdentifier(u32 index)
     55 {
     56   return fmt::format("DInput-{}", index);
     57 }
     58 
     59 static constexpr std::array<const char*, DInputSource::NUM_HAT_DIRECTIONS> s_hat_directions = {
     60   {"Up", "Down", "Left", "Right"}};
     61 
     62 bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
     63 {
     64   m_dinput_module = LoadLibraryW(L"dinput8");
     65   if (!m_dinput_module)
     66   {
     67     ERROR_LOG("Failed to load DInput module.");
     68     return false;
     69   }
     70 
     71   PFNDIRECTINPUT8CREATE create =
     72     reinterpret_cast<PFNDIRECTINPUT8CREATE>(GetProcAddress(m_dinput_module, "DirectInput8Create"));
     73   PFNGETDFDIJOYSTICK get_joystick_data_format =
     74     reinterpret_cast<PFNGETDFDIJOYSTICK>(GetProcAddress(m_dinput_module, "GetdfDIJoystick"));
     75   if (!create || !get_joystick_data_format)
     76   {
     77     ERROR_LOG("Failed to get DInput function pointers.");
     78     return false;
     79   }
     80 
     81   HRESULT hr = create(GetModuleHandleA(nullptr), DIRECTINPUT_VERSION, IID_IDirectInput8W,
     82                       reinterpret_cast<LPVOID*>(m_dinput.GetAddressOf()), nullptr);
     83   m_joystick_data_format = get_joystick_data_format();
     84   if (FAILED(hr) || !m_joystick_data_format)
     85   {
     86     ERROR_LOG("DirectInput8Create() failed: {}", static_cast<unsigned>(hr));
     87     return false;
     88   }
     89 
     90   // need to release the lock while we're enumerating, because we call winId().
     91   settings_lock.unlock();
     92   const std::optional<WindowInfo> toplevel_wi(Host::GetTopLevelWindowInfo());
     93   settings_lock.lock();
     94 
     95   if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32)
     96   {
     97     ERROR_LOG("Missing top level window, cannot add DInput devices.");
     98     return false;
     99   }
    100 
    101   m_toplevel_window = static_cast<HWND>(toplevel_wi->window_handle);
    102   ReloadDevices();
    103   return true;
    104 }
    105 
    106 void DInputSource::UpdateSettings(SettingsInterface& si, std::unique_lock<std::mutex>& settings_lock)
    107 {
    108   // noop
    109 }
    110 
    111 static BOOL CALLBACK EnumCallback(LPCDIDEVICEINSTANCEW lpddi, LPVOID pvRef)
    112 {
    113   static_cast<std::vector<DIDEVICEINSTANCEW>*>(pvRef)->push_back(*lpddi);
    114   return DIENUM_CONTINUE;
    115 }
    116 
    117 bool DInputSource::ReloadDevices()
    118 {
    119   // detect any removals
    120   PollEvents();
    121 
    122   // look for new devices
    123   std::vector<DIDEVICEINSTANCEW> devices;
    124   m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY);
    125 
    126   VERBOSE_LOG("Enumerated {} devices", devices.size());
    127 
    128   bool changed = false;
    129   for (DIDEVICEINSTANCEW inst : devices)
    130   {
    131     // do we already have this one?
    132     if (std::any_of(m_controllers.begin(), m_controllers.end(),
    133                     [&inst](const ControllerData& cd) { return inst.guidInstance == cd.guid; }))
    134     {
    135       // yup, so skip it
    136       continue;
    137     }
    138 
    139     ControllerData cd;
    140     cd.guid = inst.guidInstance;
    141     HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr);
    142     if (FAILED(hr)) [[unlikely]]
    143     {
    144       WARNING_LOG("Failed to create instance of device [{}, {}]",
    145                   StringUtil::WideStringToUTF8String(inst.tszProductName),
    146                   StringUtil::WideStringToUTF8String(inst.tszInstanceName));
    147       continue;
    148     }
    149 
    150     const std::string name(StringUtil::WideStringToUTF8String(inst.tszProductName));
    151     if (AddDevice(cd, name))
    152     {
    153       const u32 index = static_cast<u32>(m_controllers.size());
    154       m_controllers.push_back(std::move(cd));
    155       InputManager::OnInputDeviceConnected(GetDeviceIdentifier(index), name);
    156       changed = true;
    157     }
    158   }
    159 
    160   return changed;
    161 }
    162 
    163 void DInputSource::Shutdown()
    164 {
    165   while (!m_controllers.empty())
    166   {
    167     const u32 index = static_cast<u32>(m_controllers.size() - 1);
    168     InputManager::OnInputDeviceDisconnected(
    169       InputBindingKey{{.source_type = InputSourceType::DInput, .source_index = index}},
    170       GetDeviceIdentifier(static_cast<u32>(m_controllers.size() - 1)));
    171     m_controllers.pop_back();
    172   }
    173 }
    174 
    175 bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
    176 {
    177   HRESULT hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_EXCLUSIVE);
    178   if (FAILED(hr))
    179   {
    180     hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
    181     if (FAILED(hr))
    182     {
    183       ERROR_LOG("Failed to set cooperative level for '{}'", name);
    184       return false;
    185     }
    186 
    187     WARNING_LOG("Failed to set exclusive mode for '{}'", name);
    188   }
    189 
    190   hr = cd.device->SetDataFormat(m_joystick_data_format);
    191   if (FAILED(hr))
    192   {
    193     ERROR_LOG("Failed to set data format for '{}'", name);
    194     return false;
    195   }
    196 
    197   hr = cd.device->Acquire();
    198   if (FAILED(hr))
    199   {
    200     ERROR_LOG("Failed to acquire device '{}'", name);
    201     return false;
    202   }
    203 
    204   DIDEVCAPS caps = {};
    205   caps.dwSize = sizeof(caps);
    206   hr = cd.device->GetCapabilities(&caps);
    207   if (FAILED(hr))
    208   {
    209     ERROR_LOG("Failed to get capabilities for '{}'", name);
    210     return false;
    211   }
    212 
    213   cd.num_buttons = caps.dwButtons;
    214 
    215   static constexpr const u32 axis_offsets[] = {OFFSETOF(DIJOYSTATE, lX),           OFFSETOF(DIJOYSTATE, lY),
    216                                                OFFSETOF(DIJOYSTATE, lZ),           OFFSETOF(DIJOYSTATE, lRz),
    217                                                OFFSETOF(DIJOYSTATE, lRx),          OFFSETOF(DIJOYSTATE, lRy),
    218                                                OFFSETOF(DIJOYSTATE, rglSlider[0]), OFFSETOF(DIJOYSTATE, rglSlider[1])};
    219   for (const u32 offset : axis_offsets)
    220   {
    221     // ask for 16 bits of axis range
    222     DIPROPRANGE range = {};
    223     range.diph.dwSize = sizeof(range);
    224     range.diph.dwHeaderSize = sizeof(range.diph);
    225     range.diph.dwHow = DIPH_BYOFFSET;
    226     range.diph.dwObj = static_cast<DWORD>(offset);
    227     range.lMin = std::numeric_limits<s16>::min();
    228     range.lMax = std::numeric_limits<s16>::max();
    229     hr = cd.device->SetProperty(DIPROP_RANGE, &range.diph);
    230 
    231     // did it apply?
    232     if (SUCCEEDED(cd.device->GetProperty(DIPROP_RANGE, &range.diph)))
    233       cd.axis_offsets.push_back(offset);
    234   }
    235 
    236   cd.num_buttons = std::min(static_cast<u32>(caps.dwButtons), static_cast<u32>(std::size(cd.last_state.rgbButtons)));
    237   cd.num_hats = std::min(static_cast<u32>(caps.dwPOVs), static_cast<u32>(std::size(cd.last_state.rgdwPOV)));
    238 
    239   hr = cd.device->Poll();
    240   if (hr == DI_NOEFFECT)
    241     cd.needs_poll = false;
    242   else if (hr != DI_OK)
    243     WARNING_LOG("Polling device '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
    244 
    245   hr = cd.device->GetDeviceState(sizeof(cd.last_state), &cd.last_state);
    246   if (hr != DI_OK)
    247     WARNING_LOG("GetDeviceState() for '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
    248 
    249   INFO_LOG("{} has {} buttons, {} axes, {} hats", name, cd.num_buttons, static_cast<u32>(cd.axis_offsets.size()),
    250            cd.num_hats);
    251 
    252   return (cd.num_buttons > 0 || !cd.axis_offsets.empty() || cd.num_hats > 0);
    253 }
    254 
    255 void DInputSource::PollEvents()
    256 {
    257   for (size_t i = 0; i < m_controllers.size();)
    258   {
    259     ControllerData& cd = m_controllers[i];
    260     if (cd.needs_poll)
    261       cd.device->Poll();
    262 
    263     DIJOYSTATE js;
    264     HRESULT hr = cd.device->GetDeviceState(sizeof(js), &js);
    265     if (hr == DIERR_INPUTLOST || hr == DIERR_NOTACQUIRED)
    266     {
    267       hr = cd.device->Acquire();
    268       if (hr == DI_OK)
    269         hr = cd.device->GetDeviceState(sizeof(js), &js);
    270 
    271       if (hr != DI_OK)
    272       {
    273         InputManager::OnInputDeviceDisconnected(
    274           InputBindingKey{{.source_type = InputSourceType::DInput, .source_index = static_cast<u32>(i)}},
    275           GetDeviceIdentifier(static_cast<u32>(i)));
    276         m_controllers.erase(m_controllers.begin() + i);
    277         continue;
    278       }
    279     }
    280     else if (hr != DI_OK)
    281     {
    282       WARNING_LOG("GetDeviceState() failed: {:08X}", static_cast<unsigned>(hr));
    283       i++;
    284       continue;
    285     }
    286 
    287     CheckForStateChanges(i, js);
    288     i++;
    289   }
    290 }
    291 
    292 std::vector<std::pair<std::string, std::string>> DInputSource::EnumerateDevices()
    293 {
    294   std::vector<std::pair<std::string, std::string>> ret;
    295   for (size_t i = 0; i < m_controllers.size(); i++)
    296   {
    297     DIDEVICEINSTANCEW dii;
    298     dii.dwSize = sizeof(DIDEVICEINSTANCEW);
    299     std::string name;
    300     if (SUCCEEDED(m_controllers[i].device->GetDeviceInfo(&dii)))
    301       name = StringUtil::WideStringToUTF8String(dii.tszProductName);
    302 
    303     if (name.empty())
    304       name = "Unknown";
    305 
    306     ret.emplace_back(GetDeviceIdentifier(static_cast<u32>(i)), std::move(name));
    307   }
    308 
    309   return ret;
    310 }
    311 
    312 std::vector<InputBindingKey> DInputSource::EnumerateMotors()
    313 {
    314   return {};
    315 }
    316 
    317 bool DInputSource::GetGenericBindingMapping(std::string_view device, GenericInputBindingMapping* mapping)
    318 {
    319   return {};
    320 }
    321 
    322 void DInputSource::UpdateMotorState(InputBindingKey key, float intensity)
    323 {
    324   // not supported
    325 }
    326 
    327 void DInputSource::UpdateMotorState(InputBindingKey large_key, InputBindingKey small_key, float large_intensity,
    328                                     float small_intensity)
    329 {
    330   // not supported
    331 }
    332 
    333 std::optional<InputBindingKey> DInputSource::ParseKeyString(std::string_view device, std::string_view binding)
    334 {
    335   if (!device.starts_with("DInput-") || binding.empty())
    336     return std::nullopt;
    337 
    338   const std::optional<s32> player_id = StringUtil::FromChars<s32>(device.substr(7));
    339   if (!player_id.has_value() || player_id.value() < 0)
    340     return std::nullopt;
    341 
    342   InputBindingKey key = {};
    343   key.source_type = InputSourceType::DInput;
    344   key.source_index = static_cast<u32>(player_id.value());
    345 
    346   if (binding.starts_with("+Axis") || binding.starts_with("-Axis"))
    347   {
    348     std::string_view end;
    349     const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(5), 10, &end);
    350     if (!axis_index.has_value())
    351       return std::nullopt;
    352 
    353     key.source_subtype = InputSubclass::ControllerAxis;
    354     key.data = axis_index.value();
    355     key.modifier = (binding[0] == '-') ? InputModifier::Negate : InputModifier::None;
    356     key.invert = (end == "~");
    357     return key;
    358   }
    359   else if (binding.starts_with("FullAxis"))
    360   {
    361     std::string_view end;
    362     const std::optional<u32> axis_index = StringUtil::FromChars<u32>(binding.substr(8), 10, &end);
    363     if (!axis_index.has_value())
    364       return std::nullopt;
    365 
    366     key.source_subtype = InputSubclass::ControllerAxis;
    367     key.data = axis_index.value();
    368     key.modifier = InputModifier::FullAxis;
    369     key.invert = (end == "~");
    370     return key;
    371   }
    372   else if (binding.starts_with("Hat"))
    373   {
    374     if (binding[3] < '0' || binding[3] > '9' || binding.length() < 5)
    375       return std::nullopt;
    376 
    377     const u32 hat_index = binding[3] - '0';
    378     const std::string_view hat_dir(binding.substr(4));
    379     for (u32 i = 0; i < NUM_HAT_DIRECTIONS; i++)
    380     {
    381       if (hat_dir == s_hat_directions[i])
    382       {
    383         key.source_subtype = InputSubclass::ControllerButton;
    384         key.data = MAX_NUM_BUTTONS + hat_index * NUM_HAT_DIRECTIONS + i;
    385         return key;
    386       }
    387     }
    388 
    389     // bad direction
    390     return std::nullopt;
    391   }
    392   else if (binding.starts_with("Button"))
    393   {
    394     const std::optional<u32> button_index = StringUtil::FromChars<u32>(binding.substr(6));
    395     if (!button_index.has_value())
    396       return std::nullopt;
    397 
    398     key.source_subtype = InputSubclass::ControllerButton;
    399     key.data = button_index.value();
    400     return key;
    401   }
    402 
    403   // unknown axis/button
    404   return std::nullopt;
    405 }
    406 
    407 TinyString DInputSource::ConvertKeyToString(InputBindingKey key)
    408 {
    409   TinyString ret;
    410 
    411   if (key.source_type == InputSourceType::DInput)
    412   {
    413     if (key.source_subtype == InputSubclass::ControllerAxis)
    414     {
    415       const char* modifier =
    416         (key.modifier == InputModifier::FullAxis ? "Full" : (key.modifier == InputModifier::Negate ? "-" : "+"));
    417       ret.format("DInput-{}/{}Axis{}{}", u32(key.source_index), modifier, u32(key.data), key.invert ? "~" : "");
    418     }
    419     else if (key.source_subtype == InputSubclass::ControllerButton && key.data >= MAX_NUM_BUTTONS)
    420     {
    421       const u32 hat_num = (key.data - MAX_NUM_BUTTONS) / NUM_HAT_DIRECTIONS;
    422       const u32 hat_dir = (key.data - MAX_NUM_BUTTONS) % NUM_HAT_DIRECTIONS;
    423       ret.format("DInput-{}/Hat{}{}", u32(key.source_index), hat_num, s_hat_directions[hat_dir]);
    424     }
    425     else if (key.source_subtype == InputSubclass::ControllerButton)
    426     {
    427       ret.format("DInput-{}/Button{}", u32(key.source_index), u32(key.data));
    428     }
    429   }
    430 
    431   return ret;
    432 }
    433 
    434 TinyString DInputSource::ConvertKeyToIcon(InputBindingKey key)
    435 {
    436   return {};
    437 }
    438 
    439 void DInputSource::CheckForStateChanges(size_t index, const DIJOYSTATE& new_state)
    440 {
    441   ControllerData& cd = m_controllers[index];
    442   DIJOYSTATE& last_state = cd.last_state;
    443 
    444   for (size_t i = 0; i < cd.axis_offsets.size(); i++)
    445   {
    446     LONG new_value;
    447     LONG old_value;
    448     std::memcpy(&old_value, reinterpret_cast<const u8*>(&cd.last_state) + cd.axis_offsets[i], sizeof(old_value));
    449     std::memcpy(&new_value, reinterpret_cast<const u8*>(&new_state) + cd.axis_offsets[i], sizeof(new_value));
    450     if (old_value != new_value)
    451     {
    452       std::memcpy(reinterpret_cast<u8*>(&cd.last_state) + cd.axis_offsets[i], &new_value, sizeof(new_value));
    453 
    454       // TODO: Use the range from caps?
    455       const float value = static_cast<float>(new_value) / (new_value < 0 ? 32768.0f : 32767.0f);
    456       InputManager::InvokeEvents(
    457         MakeGenericControllerAxisKey(InputSourceType::DInput, static_cast<u32>(index), static_cast<u32>(i)), value,
    458         GenericInputBinding::Unknown);
    459     }
    460   }
    461 
    462   for (u32 i = 0; i < cd.num_buttons; i++)
    463   {
    464     if (last_state.rgbButtons[i] != new_state.rgbButtons[i])
    465     {
    466       last_state.rgbButtons[i] = new_state.rgbButtons[i];
    467 
    468       const float value = (new_state.rgbButtons[i] != 0) ? 1.0f : 0.0f;
    469       InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index), i),
    470                                  value, GenericInputBinding::Unknown);
    471     }
    472   }
    473 
    474   for (u32 i = 0; i < cd.num_hats; i++)
    475   {
    476     if (last_state.rgdwPOV[i] != new_state.rgdwPOV[i])
    477     {
    478       // map hats to the last buttons
    479       const std::array<bool, NUM_HAT_DIRECTIONS> old_buttons(GetHatButtons(last_state.rgdwPOV[i]));
    480       const std::array<bool, NUM_HAT_DIRECTIONS> new_buttons(GetHatButtons(new_state.rgdwPOV[i]));
    481       last_state.rgdwPOV[i] = new_state.rgdwPOV[i];
    482 
    483       for (u32 j = 0; j < NUM_HAT_DIRECTIONS; j++)
    484       {
    485         if (old_buttons[j] != new_buttons[j])
    486         {
    487           const float value = (new_buttons[j] ? 1.0f : 0.0f);
    488           InputManager::InvokeEvents(MakeGenericControllerButtonKey(InputSourceType::DInput, static_cast<u32>(index),
    489                                                                     cd.num_buttons + (i * NUM_HAT_DIRECTIONS) + j),
    490                                      value, GenericInputBinding::Unknown);
    491         }
    492       }
    493     }
    494   }
    495 }
    496 
    497 std::unique_ptr<InputSource> InputSource::CreateDInputSource()
    498 {
    499   return std::make_unique<DInputSource>();
    500 }