diff options
Diffstat (limited to 'src/osd/modules/input/input_dinput.cpp')
-rw-r--r-- | src/osd/modules/input/input_dinput.cpp | 912 |
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 |