summaryrefslogtreecommitdiffstatshomepage
path: root/src/osd/modules/input/input_dinput.cpp
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2023-02-18 06:18:45 +1100
committer Vas Crabb <vas@vastheman.com>2023-02-18 06:18:45 +1100
commitd4589e0b29d6085072ff635d87c3d95c21813a58 (patch)
treec0d3283ecdd99bd380fc155f4e09b7f4bd573116 /src/osd/modules/input/input_dinput.cpp
parent20d7135179ffe9959f4bfb759a0e5735b7aaaf15 (diff)
Input refactoring:
osd/modules/input, emu/inpttype.cpp: Made most default joystick assignments supplied by input modules. Input modules take available controls into consideration when generating default assignments. emu/inpttype.ipp: Added a separate "Back" UI input separate from Cancel. You may want an easier to hit combination for moving to the previous menu than for exiting or cancelling input. They both default to Escape. emu/inpttype.ipp: Added a UI Help control. Currently only used by analog inputs menu emu/inpttype.h: Moved I/O port field type enum to its own header and sorted UI controls so they appear in a more logical order. ui: Don't use UI Select to restore defaults - people should be getting used to the UI Clear input by now. UI Select cycles multi-value items instead. ui/inputmap.cpp: Don't use immediate cancel to cycle between clearing and restoring default assignment (use UI Clear instead). osd: Reduced the number of files needing to include the dreaded emu.h. Got some implementation out of headers.
Diffstat (limited to 'src/osd/modules/input/input_dinput.cpp')
-rw-r--r--src/osd/modules/input/input_dinput.cpp912
1 files changed, 773 insertions, 139 deletions
diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp
index 2ac7c7458fd..8642f693d03 100644
--- a/src/osd/modules/input/input_dinput.cpp
+++ b/src/osd/modules/input/input_dinput.cpp
@@ -1,22 +1,108 @@
// license:BSD-3-Clause
-// copyright-holders:Aaron Giles, Brad Hughes
+// copyright-holders:Aaron Giles, Brad Hughes, Vas Crabb
//============================================================
//
// input_dinput.cpp - Windows DirectInput support
//
//============================================================
+/*
+
+DirectInput joystick input is a bit of a mess. It gives eight axes
+called X, Y, Z, Rx, Ry, Rz, slider 0 and slider 1. The driver can
+assign arbitrary physical axes to these axes. Up to four hat switches
+are supported, giving a direction in hundredths of degrees. In theory,
+this supports dial-like controls with an arbitrary number of stops. In
+practice, it just makes dealing with 8-way hat switches more complicated
+and prevents contradictory inputs from being reported altogether.
+
+You may get a vague indication of the type of controller, and you can
+obtain usage information for HID controllers.
+
+The Windows HID driver supposedly uses the following mappings:
+
+0x01 Generic Desktop 0x30 X X
+0x01 Generic Desktop 0x31 Y Y
+0x01 Generic Desktop 0x32 Z Z
+0x01 Generic Desktop 0x33 Rx Rx
+0x01 Generic Desktop 0x34 Ry Ry
+0x01 Generic Desktop 0x35 Rz Rz
+0x01 Generic Desktop 0x36 Slider Slider
+0x01 Generic Desktop 0x37 Dial Slider
+0x01 Generic Desktop 0x39 Hat Switch POV Hat
+0x02 Simulation 0xBA Rudder Rz
+0x02 Simulation 0xBB Throttle Slider
+0x02 Simulation 0xC4 Accelerator Y
+0x02 Simulation 0xC5 Brake Rz
+0x02 Simulation 0xC8 Steering X
+
+Anything without an explicit mapping is treated as a button.
+
+The WinMM driver supposedly uses the following axis mappings:
+
+X X
+Y Y
+Z Slider
+R Rz
+U Slider
+V Slider
+
+The actual mapping used by various controllers doesn't match what you
+might expect from the HID mapping.
+
+Gamepads:
+
+Axis Logitech Xinput Switch
+X Left X Left X Left X
+Y Left Y Left Y Left Y
+Z Right X Triggers
+Rx Right X Right X
+Ry Right Y Right Y
+Rz Right Y
+
+Thrustmaster controllers:
+
+Axis HOTAS Side stick Throttle/Pedals Dual Throttles Triple Throttles Driving
+X Aileron Aileron Mini Stick X Left Throttle Right Brake Steering
+Y Elevator Elevator Mini stick Y Right Throttle Left Brake Brake
+Z Throttle Throttle Flaps Rudder
+Rx Right Brake Right Brake Right Brake Left Throttle
+Ry Left Brake Left Brake Left Brake Centre Throttle
+Rz Twist Rudder Rocker Air Brake Right Throttle Accelerator
+Slider 0 Rocker Throttle Antenna Rudder Clutch
+Slider 1 Rudder Rudder
+
+Logitech controllers:
+
+Axis Pro Wheels
+X Steering
+Y
+Z
+Rx Accelerator
+Ry Brake
+Rz Clutch
+Slider 0
+Slider 1
+
+MFG Crosswind pedals:
+
+X Left Brake
+Y Right Brake
+Rz Rudder
+
+*/
#include "modules/osdmodule.h"
#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32)
-// emu
-#include "emu.h" // put this here before Windows headers define interface as a macro
-
#include "input_dinput.h"
+#include "interface/inputseq.h"
#include "windows/winutil.h"
+// emu
+#include "inpttype.h"
+
// lib/util
#include "util/corestr.h"
@@ -26,6 +112,7 @@
#endif
#include <algorithm>
+#include <cmath>
#include <iterator>
#include <memory>
@@ -44,8 +131,180 @@ BOOL CALLBACK device_enum_interface_callback(LPCDIDEVICEINSTANCE instance, LPVOI
}
+std::string guid_to_string(GUID const &guid)
+{
+ // size of a GUID string with dashes plus NUL terminator
+ char guid_string[37];
+ snprintf(
+ guid_string, std::size(guid_string),
+ "%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ guid.Data1, guid.Data2, guid.Data3,
+ guid.Data4[0], guid.Data4[1], guid.Data4[2],
+ guid.Data4[3], guid.Data4[4], guid.Data4[5],
+ guid.Data4[6], guid.Data4[7]);
+
+ return guid_string;
+}
+
+
+
//============================================================
-// dinput_module - base directinput module
+// dinput_keyboard_device - DirectInput keyboard device
+//============================================================
+
+class dinput_keyboard_device : public dinput_device
+{
+public:
+ dinput_keyboard_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
+ DIDEVCAPS const &caps,
+ LPCDIDATAFORMAT format);
+
+ virtual void poll() override;
+ virtual void reset() override;
+ virtual void configure(input_device &device) override;
+
+private:
+ std::mutex m_device_lock;
+ keyboard_state m_keyboard;
+};
+
+dinput_keyboard_device::dinput_keyboard_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
+ DIDEVCAPS const &caps,
+ LPCDIDATAFORMAT format) :
+ dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format),
+ m_keyboard({ { 0 } })
+{
+}
+
+void dinput_keyboard_device::poll()
+{
+ // poll the DirectInput immediate state
+ std::lock_guard<std::mutex> scope_lock(m_device_lock);
+ poll_dinput(&m_keyboard.state);
+}
+
+void dinput_keyboard_device::reset()
+{
+ memset(&m_keyboard.state, 0, sizeof(m_keyboard.state));
+}
+
+void dinput_keyboard_device::configure(input_device &device)
+{
+ // populate it
+ char defname[20];
+ for (int keynum = 0; keynum < MAX_KEYS; keynum++)
+ {
+ input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum);
+
+ // generate/fetch the name
+ snprintf(defname, std::size(defname), "Scan%03d", keynum);
+
+ // add the item to the device
+ device.add_item(
+ item_name(keynum, defname, nullptr),
+ strmakeupper(defname),
+ itemid,
+ generic_button_get_state<uint8_t>,
+ &m_keyboard.state[keynum]);
+ }
+}
+
+
+//============================================================
+// dinput_mouse_device - DirectInput mouse device
+//============================================================
+
+class dinput_mouse_device : public dinput_device
+{
+public:
+ dinput_mouse_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
+ DIDEVCAPS const &caps,
+ LPCDIDATAFORMAT format);
+
+ void poll() override;
+ void reset() override;
+ virtual void configure(input_device &device) override;
+
+private:
+ mouse_state m_mouse;
+};
+
+dinput_mouse_device::dinput_mouse_device(
+ std::string &&name,
+ std::string &&id,
+ input_module &module,
+ Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
+ DIDEVCAPS const &caps,
+ LPCDIDATAFORMAT format) :
+ dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format),
+ m_mouse({0})
+{
+ // cap the number of axes and buttons based on the format
+ m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(3));
+ m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD((m_format == &c_dfDIMouse) ? 4 : 8));
+}
+
+void dinput_mouse_device::poll()
+{
+ // poll
+ if (poll_dinput(&m_mouse) == DI_OK)
+ {
+ // scale the axis data
+ m_mouse.lX *= input_device::RELATIVE_PER_PIXEL;
+ m_mouse.lY *= input_device::RELATIVE_PER_PIXEL;
+ m_mouse.lZ *= input_device::RELATIVE_PER_PIXEL;
+ }
+}
+
+void dinput_mouse_device::reset()
+{
+ memset(&m_mouse, 0, sizeof(m_mouse));
+}
+
+void dinput_mouse_device::configure(input_device &device)
+{
+ // populate the axes
+ for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++)
+ {
+ // add to the mouse device and optionally to the gun device as well
+ device.add_item(
+ item_name(offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr),
+ std::string_view(),
+ input_item_id(ITEM_ID_XAXIS + axisnum),
+ generic_axis_get_state<LONG>,
+ &m_mouse.lX + axisnum);
+ }
+
+ // populate the buttons
+ for (int butnum = 0; butnum < m_caps.dwButtons; butnum++)
+ {
+ auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]);
+
+ // add to the mouse device
+ device.add_item(
+ item_name(offset, default_button_name(butnum), nullptr),
+ std::string_view(),
+ input_item_id(ITEM_ID_BUTTON1 + butnum),
+ generic_button_get_state<BYTE>,
+ &m_mouse.rgbButtons[butnum]);
+ }
+}
+
+
+//============================================================
+// dinput_module - base DirectInput module
//============================================================
class dinput_module : public input_module_impl<dinput_device, osd_common_t>, public device_enum_interface
@@ -220,7 +479,7 @@ public:
//============================================================
-// dinput_device - base directinput device
+// dinput_device - base DirectInput device
//============================================================
dinput_device::dinput_device(
@@ -282,122 +541,6 @@ std::string dinput_device::item_name(int offset, std::string_view defstring, con
//============================================================
-// dinput_keyboard_device - directinput keyboard device
-//============================================================
-
-dinput_keyboard_device::dinput_keyboard_device(
- std::string &&name,
- std::string &&id,
- input_module &module,
- Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
- DIDEVCAPS const &caps,
- LPCDIDATAFORMAT format) :
- dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format),
- m_keyboard({ { 0 } })
-{
-}
-
-void dinput_keyboard_device::poll()
-{
- // poll the DirectInput immediate state
- std::lock_guard<std::mutex> scope_lock(m_device_lock);
- poll_dinput(&m_keyboard.state);
-}
-
-void dinput_keyboard_device::reset()
-{
- memset(&m_keyboard.state, 0, sizeof(m_keyboard.state));
-}
-
-void dinput_keyboard_device::configure(input_device &device)
-{
- // populate it
- char defname[20];
- for (int keynum = 0; keynum < MAX_KEYS; keynum++)
- {
- input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum);
-
- // generate/fetch the name
- snprintf(defname, std::size(defname), "Scan%03d", keynum);
-
- // add the item to the device
- device.add_item(
- item_name(keynum, defname, nullptr),
- strmakeupper(defname),
- itemid,
- generic_button_get_state<std::uint8_t>,
- &m_keyboard.state[keynum]);
- }
-}
-
-
-//============================================================
-// dinput_mouse_device - directinput mouse device
-//============================================================
-
-dinput_mouse_device::dinput_mouse_device(
- std::string &&name,
- std::string &&id,
- input_module &module,
- Microsoft::WRL::ComPtr<IDirectInputDevice8> &&device,
- DIDEVCAPS const &caps,
- LPCDIDATAFORMAT format) :
- dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format),
- m_mouse({0})
-{
- // cap the number of axes and buttons based on the format
- m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(3));
- m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD((m_format == &c_dfDIMouse) ? 4 : 8));
-}
-
-void dinput_mouse_device::poll()
-{
- // poll
- if (poll_dinput(&m_mouse) == DI_OK)
- {
- // scale the axis data
- m_mouse.lX *= input_device::RELATIVE_PER_PIXEL;
- m_mouse.lY *= input_device::RELATIVE_PER_PIXEL;
- m_mouse.lZ *= input_device::RELATIVE_PER_PIXEL;
- }
-}
-
-void dinput_mouse_device::reset()
-{
- memset(&m_mouse, 0, sizeof(m_mouse));
-}
-
-void dinput_mouse_device::configure(input_device &device)
-{
- // populate the axes
- for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++)
- {
- // add to the mouse device and optionally to the gun device as well
- device.add_item(
- item_name(offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr),
- std::string_view(),
- input_item_id(ITEM_ID_XAXIS + axisnum),
- generic_axis_get_state<LONG>,
- &m_mouse.lX + axisnum);
- }
-
- // populate the buttons
- for (int butnum = 0; butnum < m_caps.dwButtons; butnum++)
- {
- auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]);
-
- // add to the mouse device
- device.add_item(
- item_name(offset, default_button_name(butnum), nullptr),
- std::string_view(),
- input_item_id(ITEM_ID_BUTTON1 + butnum),
- generic_button_get_state<BYTE>,
- &m_mouse.rgbButtons[butnum]);
- }
-}
-
-
-//============================================================
// dinput_joystick_device - directinput joystick device
//============================================================
@@ -431,28 +574,57 @@ void dinput_joystick_device::poll()
// normalize axis values
for (int axisnum = 0; axisnum < 8; axisnum++)
{
- LONG *const axis = &m_joystick.state.lX + axisnum;
- *axis = normalize_absolute_axis(*axis, m_joystick.rangemin[axisnum], m_joystick.rangemax[axisnum]);
+ auto const range = m_joystick.rangemax[axisnum] - m_joystick.rangemin[axisnum];
+ if (range)
+ {
+ // assumes output range is symmetrical
+ LONG *const axis = &m_joystick.state.lX + axisnum;
+ double const offset = *axis - m_joystick.rangemin[axisnum];
+ double const scaled = offset * double(input_device::ABSOLUTE_MAX - input_device::ABSOLUTE_MIN) / double(range);
+ *axis = lround(std::clamp<double>(scaled + input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MAX));
+ }
}
}
}
void dinput_joystick_device::configure(input_device &device)
{
+ input_device::assignment_vector assignments;
HRESULT result;
+ // get device information - it gives clues about axis usage
+ DIDEVICEINSTANCE info;
+ info.dwSize = sizeof(info);
+ result = m_device->GetDeviceInfo(&info);
+ bool hid = false;
+ uint8_t type = DI8DEVTYPE_DEVICE;
+ uint8_t subtype = 0;
+ if (result == DI_OK)
+ {
+ hid = (info.dwDevType & DIDEVTYPE_HID) != 0;
+ type = info.dwDevType & 0x00ff;
+ subtype = (info.dwDevType >> 8) & 0x00ff;
+ osd_printf_verbose(
+ "DirectInput: Device type=0x%02X subtype=0x%02X HID=%u\n",
+ type,
+ subtype,
+ hid ? "yes" : "no");
+ }
+
// turn off deadzone; we do our own calculations
result = dinput_api_helper::set_dword_property(m_device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0);
- if (result != DI_OK && result != DI_PROPNOEFFECT)
+ if ((result != DI_OK) && (result != DI_PROPNOEFFECT))
osd_printf_warning("DirectInput: Unable to reset deadzone for joystick %s.\n", name());
// turn off saturation; we do our own calculations
result = dinput_api_helper::set_dword_property(m_device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000);
- if (result != DI_OK && result != DI_PROPNOEFFECT)
+ if ((result != DI_OK) && (result != DI_PROPNOEFFECT))
osd_printf_warning("DirectInput: Unable to reset saturation for joystick %s.\n", name());
// populate the axes
- for (uint32_t axisnum = 0, axiscount = 0; axiscount < m_caps.dwAxes && axisnum < 8; axisnum++)
+ input_item_id axisitems[8];
+ std::fill(std::begin(axisitems), std::end(axisitems), ITEM_ID_INVALID);
+ for (uint32_t axisnum = 0, axiscount = 0; (axiscount < m_caps.dwAxes) && (axisnum < 8); axisnum++)
{
// fetch the range of this axis
DIPROPRANGE dipr;
@@ -463,6 +635,7 @@ void dinput_joystick_device::configure(input_device &device)
result = m_device->GetProperty(DIPROP_RANGE, &dipr.diph);
if (result != DI_OK)
{
+ // this is normal when axes are skipped, e.g. X/Y/Z present, rX/rY absent, rZ present
osd_printf_verbose("DirectInput: Unable to get properties for joystick %s axis %u.\n", name(), axisnum);
continue;
}
@@ -471,7 +644,7 @@ void dinput_joystick_device::configure(input_device &device)
m_joystick.rangemax[axisnum] = dipr.lMax;
// populate the item description as well
- device.add_item(
+ axisitems[axisnum] = device.add_item(
item_name(offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr),
std::string_view(),
input_item_id(ITEM_ID_XAXIS + axisnum),
@@ -481,38 +654,114 @@ void dinput_joystick_device::configure(input_device &device)
axiscount++;
}
- // populate the POVs
+ // take a guess at which axes might be pedals depending on type and remap onto negative half of range
+ input_item_id pedalitems[3] = { ITEM_ID_INVALID, ITEM_ID_INVALID, ITEM_ID_INVALID };
+ if (DI8DEVTYPE_FLIGHT == type)
+ {
+ // Rx/Ry are often used for brakes
+ bool const rxpedal = (ITEM_ID_INVALID != axisitems[3]) && !m_joystick.rangemin[3] && (0 < m_joystick.rangemax[3]);
+ bool const rypedal = (ITEM_ID_INVALID != axisitems[4]) && !m_joystick.rangemin[4] && (0 < m_joystick.rangemax[4]);
+ if (rxpedal && rypedal)
+ {
+ pedalitems[0] = axisitems[3];
+ m_joystick.rangemin[3] = m_joystick.rangemax[3];
+ m_joystick.rangemax[3] = -m_joystick.rangemax[3];
+
+ pedalitems[1] = axisitems[4];
+ m_joystick.rangemin[4] = m_joystick.rangemax[4];
+ m_joystick.rangemax[4] = -m_joystick.rangemax[4];
+ }
+ }
+ else if (DI8DEVTYPE_DRIVING == type)
+ {
+ bool const ypedal = (ITEM_ID_INVALID != axisitems[1]) && !m_joystick.rangemin[1] && (0 < m_joystick.rangemax[1]);
+ bool const rxpedal = (ITEM_ID_INVALID != axisitems[3]) && !m_joystick.rangemin[3] && (0 < m_joystick.rangemax[3]);
+ bool const rypedal = (ITEM_ID_INVALID != axisitems[4]) && !m_joystick.rangemin[4] && (0 < m_joystick.rangemax[4]);
+ bool const rzpedal = (ITEM_ID_INVALID != axisitems[5]) && !m_joystick.rangemin[5] && (0 < m_joystick.rangemax[5]);
+ bool const s0pedal = (ITEM_ID_INVALID != axisitems[6]) && !m_joystick.rangemin[6] && (0 < m_joystick.rangemax[6]);
+ if (DI8DEVTYPEDRIVING_DUALPEDALS == subtype)
+ {
+ // dual pedals are usually Y and Rz
+ if (ypedal && rzpedal)
+ {
+ pedalitems[0] = axisitems[1];
+ m_joystick.rangemin[1] = m_joystick.rangemax[1];
+ m_joystick.rangemax[1] = -m_joystick.rangemax[1];
+
+ pedalitems[1] = axisitems[5];
+ m_joystick.rangemin[5] = m_joystick.rangemax[5];
+ m_joystick.rangemax[5] = -m_joystick.rangemax[5];
+ }
+ }
+ else if (DI8DEVTYPEDRIVING_THREEPEDALS == subtype)
+ {
+ // triple pedals may be Y, Rz and slider 0, or Rx, Ry and Rz
+ if (ypedal && rzpedal && s0pedal)
+ {
+ pedalitems[0] = axisitems[1];
+ m_joystick.rangemin[1] = m_joystick.rangemax[1];
+ m_joystick.rangemax[1] = -m_joystick.rangemax[1];
+
+ pedalitems[1] = axisitems[5];
+ m_joystick.rangemin[5] = m_joystick.rangemax[5];
+ m_joystick.rangemax[5] = -m_joystick.rangemax[5];
+
+ pedalitems[2] = axisitems[6];
+ m_joystick.rangemin[6] = m_joystick.rangemax[6];
+ m_joystick.rangemax[6] = -m_joystick.rangemax[6];
+ }
+ else if (rxpedal && rypedal && rzpedal)
+ {
+ pedalitems[0] = axisitems[3];
+ m_joystick.rangemin[3] = m_joystick.rangemax[3];
+ m_joystick.rangemax[3] = -m_joystick.rangemax[3];
+
+ pedalitems[1] = axisitems[4];
+ m_joystick.rangemin[4] = m_joystick.rangemax[4];
+ m_joystick.rangemax[4] = -m_joystick.rangemax[4];
+
+ pedalitems[2] = axisitems[5];
+ m_joystick.rangemin[5] = m_joystick.rangemax[5];
+ m_joystick.rangemax[5] = -m_joystick.rangemax[5];
+ }
+ }
+ }
+
+ // populate the POV hats
+ input_item_id povitems[4][4];
+ for (auto &pov : povitems)
+ std::fill(std::begin(pov), std::end(pov), ITEM_ID_INVALID);
for (uint32_t povnum = 0; povnum < m_caps.dwPOVs; povnum++)
{
// left
- device.add_item(
+ povitems[povnum][0] = device.add_item(
item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Left"),
std::string_view(),
- input_item_id(povnum * 4 + ITEM_ID_HAT1LEFT),
+ input_item_id((povnum * 4) + ITEM_ID_HAT1LEFT),
&dinput_joystick_device::pov_get_state,
reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_LEFT)));
// right
- device.add_item(
+ povitems[povnum][1] = device.add_item(
item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Right"),
std::string_view(),
- input_item_id(povnum * 4 + ITEM_ID_HAT1RIGHT),
+ input_item_id((povnum * 4) + ITEM_ID_HAT1RIGHT),
&dinput_joystick_device::pov_get_state,
reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_RIGHT)));
// up
- device.add_item(
+ povitems[povnum][2] = device.add_item(
item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Up"),
std::string_view(),
- input_item_id(povnum * 4 + ITEM_ID_HAT1UP),
+ input_item_id((povnum * 4) + ITEM_ID_HAT1UP),
&dinput_joystick_device::pov_get_state,
reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_UP)));
// down
- device.add_item(
+ povitems[povnum][3] = device.add_item(
item_name(offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), "Down"),
std::string_view(),
- input_item_id(povnum * 4 + ITEM_ID_HAT1DOWN),
+ input_item_id((povnum * 4) + ITEM_ID_HAT1DOWN),
&dinput_joystick_device::pov_get_state,
reinterpret_cast<void *>(uintptr_t(povnum * 4 + POVDIR_DOWN)));
}
@@ -530,13 +779,386 @@ void dinput_joystick_device::configure(input_device &device)
else
itemid = ITEM_ID_OTHER_SWITCH;
- device.add_item(
+ input_item_id const actual = device.add_item(
item_name(offset, default_button_name(butnum), nullptr),
std::string_view(),
itemid,
generic_button_get_state<BYTE>,
&m_joystick.state.rgbButtons[butnum]);
+
+ // there are sixteen action button types
+ if (butnum < 16)
+ {
+ input_seq const seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, actual));
+ assignments.emplace_back(ioport_type(IPT_BUTTON1 + butnum), SEQ_TYPE_STANDARD, seq);
+
+ // assign the first few buttons to UI actions and pedals
+ // TODO: don't map pedals for driving controls that have them present
+ switch (butnum)
+ {
+ case 0:
+ assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back(IPT_UI_SELECT, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 1:
+ assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back((3 > m_caps.dwButtons) ? IPT_UI_CLEAR : IPT_UI_BACK, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 2:
+ assignments.emplace_back(IPT_PEDAL3, SEQ_TYPE_INCREMENT, seq);
+ assignments.emplace_back(IPT_UI_CLEAR, SEQ_TYPE_STANDARD, seq);
+ break;
+ case 3:
+ assignments.emplace_back(IPT_UI_HELP, SEQ_TYPE_STANDARD, seq);
+ break;
+ }
+ }
+ }
+
+ // add default assignments depending on type
+ if (DI8DEVTYPE_FLIGHT == type)
+ {
+ if (((ITEM_ID_INVALID == axisitems[0]) || (ITEM_ID_INVALID == axisitems[1])) && (1 <= m_caps.dwPOVs))
+ {
+ // X or Y missing, fall back to using POV hat for navigation
+ add_directional_assignments(
+ assignments,
+ axisitems[0],
+ axisitems[1],
+ povitems[0][0],
+ povitems[0][1],
+ povitems[0][2],
+ povitems[0][3]);
+
+ // try using throttle for zoom/focus
+ if (ITEM_ID_INVALID != axisitems[2])
+ {
+ input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[2]));
+ input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[2]));
+ if (ITEM_ID_INVALID != axisitems[5])
+ {
+ assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq);
+ assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq);
+ }
+ else
+ {
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, posseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, negseq);
+ }
+ }
+
+ // try using twist/rudder for next/previous group
+ if (ITEM_ID_INVALID != axisitems[5])
+ {
+ input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[5]));
+ input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[5]));
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, posseq);
+ }
+ }
+ else
+ {
+ // only use stick for primary navigation/movement
+ add_directional_assignments(
+ assignments,
+ axisitems[0],
+ axisitems[1],
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID);
+
+ // try using hat for secondary navigation functions
+ if (1 <= m_caps.dwPOVs)
+ {
+ input_seq const leftseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][0]));
+ input_seq const rightseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][1]));
+ input_seq const upseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][2]));
+ input_seq const downseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NONE, povitems[0][3]));
+ if ((ITEM_ID_INVALID != axisitems[2]) || (ITEM_ID_INVALID != axisitems[5]))
+ {
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, leftseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, rightseq);
+ assignments.emplace_back(IPT_UI_PAGE_UP, SEQ_TYPE_STANDARD, upseq);
+ assignments.emplace_back(IPT_UI_PAGE_DOWN, SEQ_TYPE_STANDARD, downseq);
+ }
+ else
+ {
+ assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, leftseq);
+ assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, rightseq);
+ assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, leftseq);
+ assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, rightseq);
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, upseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, downseq);
+ }
+ }
+
+ // try using throttle for zoom
+ if (ITEM_ID_INVALID != axisitems[2])
+ {
+ input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[2]));
+ input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[2]));
+ if ((1 <= m_caps.dwPOVs) || (ITEM_ID_INVALID != axisitems[5]))
+ {
+ assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq);
+ assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq);
+ if ((1 > m_caps.dwPOVs) || (ITEM_ID_INVALID == axisitems[5]))
+ {
+ assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq);
+ }
+ }
+ else
+ {
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, posseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, negseq);
+ }
+ }
+
+ // try using twist/rudder for focus next/previous
+ if (ITEM_ID_INVALID != axisitems[5])
+ {
+ input_seq const negseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[5]));
+ input_seq const posseq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[5]));
+ if (1 <= m_caps.dwPOVs)
+ {
+ assignments.emplace_back(IPT_UI_FOCUS_PREV, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_FOCUS_NEXT, SEQ_TYPE_STANDARD, posseq);
+ if (ITEM_ID_INVALID == axisitems[2])
+ {
+ assignments.emplace_back(IPT_UI_ZOOM_IN, SEQ_TYPE_STANDARD, posseq);
+ assignments.emplace_back(IPT_UI_ZOOM_OUT, SEQ_TYPE_STANDARD, negseq);
+ }
+ }
+ else
+ {
+ assignments.emplace_back(IPT_UI_PREV_GROUP, SEQ_TYPE_STANDARD, negseq);
+ assignments.emplace_back(IPT_UI_NEXT_GROUP, SEQ_TYPE_STANDARD, posseq);
+ }
+ }
+ }
+
+ // Z or slider 0 is usually the throttle - use one of them for joystick Z
+ add_assignment(
+ assignments,
+ IPT_AD_STICK_Z,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_NONE,
+ { axisitems[2], axisitems[6] });
+
+ // use Z for the first two pedals if present
+ if (ITEM_ID_INVALID != axisitems[2])
+ {
+ // TODO: use Rx/Ry as well if they appear to be brakes
+ input_seq const pedal1seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, axisitems[2]));
+ input_seq const pedal2seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axisitems[2]));
+ assignments.emplace_back(IPT_PEDAL, SEQ_TYPE_STANDARD, pedal1seq);
+ assignments.emplace_back(IPT_PEDAL2, SEQ_TYPE_STANDARD, pedal2seq);
+ }
+ }
+ else if (DI8DEVTYPE_DRIVING == type)
+ {
+ // use the wheel and D-pad for navigation and directional controls
+ add_directional_assignments(
+ assignments,
+ axisitems[0],
+ ITEM_ID_INVALID,
+ povitems[0][0],
+ povitems[0][1],
+ povitems[0][2],
+ povitems[0][3]);
+
+ // check subtype to determine how pedals should be assigned
+ if (DI8DEVTYPEDRIVING_COMBINEDPEDALS == subtype)
+ {
+ if (ITEM_ID_INVALID != axisitems[1])
+ {
+ // put first two pedals on opposite sides of Y axis
+ assignments.emplace_back(
+ IPT_PEDAL,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_POS, axisitems[1])));
+ assignments.emplace_back(
+ IPT_PEDAL2,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, axisitems[1])));
+
+ // use for previous/next group as well
+ assignments.emplace_back(
+ IPT_UI_NEXT_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, axisitems[1])));
+ assignments.emplace_back(
+ IPT_UI_PREV_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, axisitems[1])));
+ }
+ }
+ else
+ {
+ // see if we have individual pedals
+ if (ITEM_ID_INVALID != pedalitems[0])
+ {
+ assignments.emplace_back(
+ IPT_PEDAL,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[0])));
+ assignments.emplace_back(
+ IPT_UI_NEXT_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[0])));
+ }
+ if (ITEM_ID_INVALID != pedalitems[1])
+ {
+ assignments.emplace_back(
+ IPT_PEDAL2,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[1])));
+ assignments.emplace_back(
+ IPT_UI_PREV_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[1])));
+ }
+ if (ITEM_ID_INVALID != pedalitems[2])
+ {
+ assignments.emplace_back(
+ IPT_PEDAL3,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_ABSOLUTE, ITEM_MODIFIER_NEG, pedalitems[2])));
+ assignments.emplace_back(
+ IPT_UI_FOCUS_NEXT,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalitems[2])));
+ }
+ }
+ }
+ else
+ {
+ // assume this is a gamepad - see if it looks like it has dual analog sticks
+ input_item_id stickaxes[2][2] = {
+ { axisitems[0], axisitems[1] },
+ { ITEM_ID_INVALID, ITEM_ID_INVALID } };
+ input_item_id pedalaxis = ITEM_ID_INVALID;
+ if ((ITEM_ID_INVALID != axisitems[3]) && (ITEM_ID_INVALID != axisitems[4]))
+ {
+ // assume Rx/Ry are right stick and Z is triggers if present
+ stickaxes[1][0] = axisitems[3];
+ stickaxes[1][1] = axisitems[4];
+ pedalaxis = axisitems[2];
+ add_twin_stick_assignments(
+ assignments,
+ stickaxes[0][0],
+ stickaxes[0][1],
+ stickaxes[1][0],
+ stickaxes[1][1],
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID);
+ }
+ else if ((ITEM_ID_INVALID != axisitems[2]) && (ITEM_ID_INVALID != axisitems[5]))
+ {
+ // assume Z/Rz are right stick
+ stickaxes[1][0] = axisitems[2];
+ stickaxes[1][1] = axisitems[5];
+ add_twin_stick_assignments(
+ assignments,
+ stickaxes[0][0],
+ stickaxes[0][1],
+ stickaxes[1][0],
+ stickaxes[1][1],
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID,
+ ITEM_ID_INVALID);
+ }
+ else
+ {
+ // if Z is present, use it as secondary Y
+ stickaxes[1][1] = axisitems[2];
+ }
+
+ // try to find a "complete" stick for primary movement controls
+ input_item_id diraxis[2][2];
+ choose_primary_stick(diraxis, stickaxes[0][0], stickaxes[0][1], stickaxes[1][0], stickaxes[1][1]);
+ add_directional_assignments(
+ assignments,
+ diraxis[0][0],
+ diraxis[0][1],
+ povitems[0][0],
+ povitems[0][1],
+ povitems[0][2],
+ povitems[0][3]);
+
+ // assign a secondary stick axis to joystick Z
+ add_assignment(
+ assignments,
+ IPT_AD_STICK_Z,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_NONE,
+ { diraxis[1][1], diraxis[1][0] });
+
+ // try to find a suitable axis to use for the first two pedals
+ add_assignment(
+ assignments,
+ IPT_PEDAL,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_NEG,
+ { pedalaxis, diraxis[1][1], diraxis[0][1] });
+ add_assignment(
+ assignments,
+ IPT_PEDAL2,
+ SEQ_TYPE_STANDARD,
+ ITEM_CLASS_ABSOLUTE,
+ ITEM_MODIFIER_POS,
+ { pedalaxis, diraxis[1][1], diraxis[0][1] });
+
+ // try to choose an axis for previous/next group
+ if (ITEM_ID_INVALID != pedalaxis)
+ {
+ // this is reversed because right trigger is negative direction
+ assignments.emplace_back(
+ IPT_UI_PREV_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_POS, pedalaxis)));
+ assignments.emplace_back(
+ IPT_UI_NEXT_GROUP,
+ SEQ_TYPE_STANDARD,
+ input_seq(make_code(ITEM_CLASS_SWITCH, ITEM_MODIFIER_NEG, pedalaxis)));
+ pedalaxis = ITEM_ID_INVALID;
+ }
+ else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][1]))
+ {
+ // took secondary Y
+ }
+ else if (consume_axis_pair(assignments, IPT_UI_PREV_GROUP, IPT_UI_NEXT_GROUP, diraxis[1][0]))
+ {
+ // took secondary X
+ }
+
+ // use secondary Y for page up/down if available
+ consume_axis_pair(assignments, IPT_UI_PAGE_UP, IPT_UI_PAGE_DOWN, diraxis[1][1]);
+
+ // put focus previous/next and zoom on secondary X if available
+ add_axis_pair_assignment(assignments, IPT_UI_FOCUS_PREV, IPT_UI_FOCUS_NEXT, diraxis[1][0]);
+ add_axis_pair_assignment(assignments, IPT_UI_ZOOM_OUT, IPT_UI_ZOOM_IN, diraxis[1][0]);
}
+
+ // set default assignments
+ device.set_default_assignments(std::move(assignments));
}
int32_t dinput_joystick_device::pov_get_state(void *device_internal, void *item_internal)
@@ -580,7 +1202,7 @@ dinput_api_helper::~dinput_api_helper()
int dinput_api_helper::initialize()
{
- HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, reinterpret_cast<void **>(m_dinput.GetAddressOf()), nullptr);
+ HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, &m_dinput, nullptr);
if (result != DI_OK)
{
return result;
@@ -683,6 +1305,18 @@ std::pair<Microsoft::WRL::ComPtr<IDirectInputDevice8>, LPCDIDATAFORMAT> dinput_a
return std::make_pair(std::move(device), format);
}
+
+std::string dinput_api_helper::make_id(LPCDIDEVICEINSTANCE instance)
+{
+ // use name, product GUID and instance GUID as identifier
+ return
+ text::from_tstring(instance->tszInstanceName) +
+ " product_" +
+ guid_to_string(instance->guidProduct) +
+ " instance_" +
+ guid_to_string(instance->guidInstance);
+}
+
} // namespace osd