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