// license:BSD-3-Clause // 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 DS4 Xinput Switch X Left X Left X Left X Left X Y Left Y Left Y Left Y Left Y Z Right X Right X Triggers Rx Left trigger Right X Right X Ry Right trigger Right Y Right Y Rz Right Y 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) #include "input_dinput.h" #include "interface/inputseq.h" #include "windows/winutil.h" // emu #include "inpttype.h" // lib/util #include "util/corestr.h" #ifdef SDLMAME_WIN32 #include #include #endif #include #include #include #include // standard windows headers #include #include namespace osd { namespace { 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_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 &&device, DIDEVCAPS const &caps, LPCDIDATAFORMAT format); virtual void poll(bool relative_reset) 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 &&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(bool relative_reset) { // poll the DirectInput immediate state std::lock_guard 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 (unsigned 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, &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 &&device, DIDEVCAPS const &caps, LPCDIDATAFORMAT format); void poll(bool relative_reset) override; void reset() override; virtual void configure(input_device &device) override; private: DIMOUSESTATE2 m_mouse; }; dinput_mouse_device::dinput_mouse_device( std::string &&name, std::string &&id, input_module &module, Microsoft::WRL::ComPtr &&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(bool relative_reset) { // poll if (relative_reset && (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, &m_mouse.lX + axisnum); } // populate the buttons for (int butnum = 0; butnum < m_caps.dwButtons; butnum++) { auto offset = reinterpret_cast(&static_cast(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, &m_mouse.rgbButtons[butnum]); } } //============================================================ // dinput_module - base DirectInput module //============================================================ class dinput_module : public input_module_impl { public: dinput_module(const char* type, const char* name) : input_module_impl(type, name), m_dinput_helper(nullptr) { } virtual int init(osd_interface &osd, osd_options const &options) override { m_dinput_helper = std::make_unique(); int const result = m_dinput_helper->initialize(); if (result) { m_dinput_helper.reset(); return result; } return input_module_impl::init(osd, options); } virtual void exit() override { input_module_impl::exit(); m_dinput_helper.reset(); } virtual void input_init(running_machine &machine) override { input_module_impl::input_init(machine); HRESULT const result = m_dinput_helper->enum_attached_devices( dinput_devclass(), [this] (LPCDIDEVICEINSTANCE instance) { return device_enum_callback(instance); }); if (result != DI_OK) fatalerror("DirectInput: Unable to enumerate devices (result=%08X)\n", uint32_t(result)); } protected: virtual int dinput_devclass() = 0; virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) = 0; std::unique_ptr m_dinput_helper; }; class keyboard_input_dinput : public dinput_module { public: keyboard_input_dinput() : dinput_module(OSD_KEYBOARDINPUT_PROVIDER, "dinput") { } protected: virtual int dinput_devclass() override { return DI8DEVCLASS_KEYBOARD; } virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { // allocate and link in a new device auto devinfo = m_dinput_helper->create_device( *this, instance, &c_dfDIKeyboard, nullptr, background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, [] (auto...) { return true; }); if (devinfo) add_device(DEVICE_CLASS_KEYBOARD, std::move(devinfo)); return DIENUM_CONTINUE; } }; class mouse_input_dinput : public dinput_module { public: mouse_input_dinput() : dinput_module(OSD_MOUSEINPUT_PROVIDER, "dinput") { } protected: virtual int dinput_devclass() override { return DI8DEVCLASS_POINTER; } virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { // allocate and link in a new device auto devinfo = m_dinput_helper->create_device( *this, instance, &c_dfDIMouse2, &c_dfDIMouse, background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, [] (auto const &device, auto const &format) -> bool { // set relative mode HRESULT const result = dinput_api_helper::set_dword_property( device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_REL); if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) { osd_printf_error("DirectInput: Unable to set relative mode for mouse.\n"); return false; } return true; }); if (devinfo) add_device(DEVICE_CLASS_MOUSE, std::move(devinfo)); return DIENUM_CONTINUE; } }; class joystick_input_dinput : public dinput_module { public: joystick_input_dinput() : dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") { } protected: virtual int dinput_devclass() override { return DI8DEVCLASS_GAMECTRL; } virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { // allocate and link in a new device auto devinfo = m_dinput_helper->create_device( *this, instance, &c_dfDIJoystick, nullptr, background_input() ? dinput_cooperative_level::BACKGROUND : dinput_cooperative_level::FOREGROUND, [] (auto const &device, auto const &format) -> bool { // set absolute mode HRESULT const result = dinput_api_helper::set_dword_property( device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_ABS); if ((result != DI_OK) && (result != DI_PROPNOEFFECT)) { osd_printf_error("DirectInput: Unable to set absolute mode for joystick.\n"); return false; } return true; }); if (devinfo) add_device(DEVICE_CLASS_JOYSTICK, std::move(devinfo)); return DIENUM_CONTINUE; } }; } // anonymous namespace //============================================================ // dinput_device - base DirectInput device //============================================================ dinput_device::dinput_device( std::string &&name, std::string &&id, input_module &module, Microsoft::WRL::ComPtr &&device, DIDEVCAPS const &caps, LPCDIDATAFORMAT format) : device_info(std::move(name), std::move(id), module), m_device(std::move(device)), m_caps(caps), m_format(format) { } HRESULT dinput_device::poll_dinput(LPVOID pState) const { HRESULT result; // first poll the device, then get the state result = m_device->Poll(); // handle lost inputs here if ((result == DIERR_INPUTLOST) || (result == DIERR_NOTACQUIRED)) { result = m_device->Acquire(); if ((result == DI_OK) || (result == S_FALSE)) result = m_device->Poll(); } // GetDeviceState returns the immediate state if ((result == DI_OK) || (result == DI_NOEFFECT)) result = m_device->GetDeviceState(m_format->dwDataSize, pState); return result; } std::string dinput_device::item_name(int offset, std::string_view defstring, const char *suffix) const { // query the key name DIDEVICEOBJECTINSTANCE instance = { 0 }; instance.dwSize = sizeof(instance); HRESULT const result = m_device->GetObjectInfo(&instance, offset, DIPH_BYOFFSET); // use the default value if it failed std::string name; if (result != DI_OK) name = defstring; else name = text::from_tstring(instance.tszName); // if no suffix, return as-is if (suffix) name.append(" ").append(suffix); return name; } //============================================================ // dinput_joystick_device - directinput joystick device //============================================================ dinput_joystick_device::dinput_joystick_device( std::string &&name, std::string &&id, input_module &module, Microsoft::WRL::ComPtr &&device, DIDEVCAPS const &caps, LPCDIDATAFORMAT format) : dinput_device(std::move(name), std::move(id), module, std::move(device), caps, format), m_joystick({ { 0 } }) { // cap the number of axes, POVs, and buttons based on the format m_caps.dwAxes = std::min(m_caps.dwAxes, DWORD(8)); m_caps.dwPOVs = std::min(m_caps.dwPOVs, DWORD(4)); m_caps.dwButtons = std::min(m_caps.dwButtons, DWORD(128)); } void dinput_joystick_device::reset() { memset(&m_joystick.state, 0, sizeof(m_joystick.state)); std::fill(std::begin(m_joystick.state.rgdwPOV), std::end(m_joystick.state.rgdwPOV), 0xffff); } void dinput_joystick_device::poll(bool relative_reset) { // poll the device first if (dinput_device::poll_dinput(&m_joystick.state) == DI_OK) { // normalize axis values for (int axisnum = 0; axisnum < 8; 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(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)) 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)) osd_printf_warning("DirectInput: Unable to reset saturation for joystick %s.\n", name()); // populate the axes 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; dipr.diph.dwSize = sizeof(dipr); dipr.diph.dwHeaderSize = sizeof(dipr.diph); dipr.diph.dwObj = offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG); dipr.diph.dwHow = DIPH_BYOFFSET; 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; } m_joystick.rangemin[axisnum] = dipr.lMin; m_joystick.rangemax[axisnum] = dipr.lMax; // populate the item description as well 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), generic_axis_get_state, &m_joystick.state.lX + axisnum); axiscount++; } // 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 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), &dinput_joystick_device::pov_get_state, reinterpret_cast(uintptr_t(povnum * 4 + POVDIR_LEFT))); // right 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), &dinput_joystick_device::pov_get_state, reinterpret_cast(uintptr_t(povnum * 4 + POVDIR_RIGHT))); // up 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), &dinput_joystick_device::pov_get_state, reinterpret_cast(uintptr_t(povnum * 4 + POVDIR_UP))); // down 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), &dinput_joystick_device::pov_get_state, reinterpret_cast(uintptr_t(povnum * 4 + POVDIR_DOWN))); } // populate the buttons for (uint32_t butnum = 0; butnum < m_caps.dwButtons; butnum++) { auto offset = reinterpret_cast(&static_cast(nullptr)->rgbButtons[butnum]); input_item_id itemid; if (butnum < INPUT_MAX_BUTTONS) itemid = input_item_id(ITEM_ID_BUTTON1 + butnum); else if (butnum < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) itemid = input_item_id(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + butnum); else itemid = ITEM_ID_OTHER_SWITCH; input_item_id const actual = device.add_item( item_name(offset, default_button_name(butnum), nullptr), std::string_view(), itemid, generic_button_get_state, &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[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 ((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 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) { auto *const devinfo = static_cast(device_internal); int const povnum = uintptr_t(item_internal) / 4; int const povdir = uintptr_t(item_internal) % 4; // get the current state DWORD const pov = devinfo->m_joystick.state.rgdwPOV[povnum]; // if invalid, return 0 if ((pov & 0xffff) == 0xffff) return 0; // return the current state switch (povdir) { case POVDIR_LEFT: return (pov >= 22500) && (pov <= 31500); case POVDIR_RIGHT: return (pov >= 4500) && (pov <= 13500); case POVDIR_UP: return (pov >= 31500) || (pov <= 4500); case POVDIR_DOWN: return (pov >= 13500) && (pov <= 22500); } return 0; } //============================================================ // dinput_api_helper - DirectInput API helper //============================================================ dinput_api_helper::dinput_api_helper() { } dinput_api_helper::~dinput_api_helper() { } int dinput_api_helper::initialize() { HRESULT result = DirectInput8Create(GetModuleHandleUni(), DIRECTINPUT_VERSION, IID_IDirectInput8, &m_dinput, nullptr); if (result != DI_OK) { return result; } osd_printf_verbose("DirectInput: Using DirectInput %d\n", DIRECTINPUT_VERSION >> 8); return 0; } std::pair, LPCDIDATAFORMAT> dinput_api_helper::open_device( LPCDIDEVICEINSTANCE instance, LPCDIDATAFORMAT format1, LPCDIDATAFORMAT format2, dinput_cooperative_level cooperative_level) { HRESULT result; // attempt to create a device Microsoft::WRL::ComPtr device; result = m_dinput->CreateDevice(instance->guidInstance, &device, nullptr); if (result != DI_OK) { osd_printf_error("DirectInput: Unable to create device.\n"); return std::make_pair(nullptr, nullptr); } // attempt to set the data format LPCDIDATAFORMAT format = format1; result = device->SetDataFormat(format); if ((result != DI_OK) && format2) { // use the secondary format if available osd_printf_verbose("DirectInput: Error setting primary data format, trying secondary format.\n"); format = format2; result = device->SetDataFormat(format); } if (result != DI_OK) { osd_printf_error("DirectInput: Unable to set data format.\n"); return std::make_pair(nullptr, nullptr); } // default window to the first window in the list // For now, we always use the desktop window due to multiple issues: // * MAME recreates windows on toggling fullscreen. DirectInput really doesn't like this. // * DirectInput doesn't like the window used for D3D fullscreen exclusive mode. // * With multiple windows, the first window needs to have focus when using foreground mode. // This makes it impossible to use force feedback as that requires foreground exclusive mode. // The only way to get around this would be to reopen devices on focus changes. [[maybe_unused]] HWND window_handle; DWORD di_cooperative_level; #if defined(OSD_WINDOWS) auto const &window = dynamic_cast(*osd_common_t::window_list().front()); bool const standalone_window = !window.attached_mode(); #elif defined(SDLMAME_WIN32) auto const &window = dynamic_cast(*osd_common_t::window_list().front()); bool const standalone_window = true; #endif if (!standalone_window) { // in attached mode we have to ignore the caller and hook up to the desktop window window_handle = GetDesktopWindow(); di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; } else { #if defined(OSD_WINDOWS) window_handle = window.platform_window(); #elif defined(SDLMAME_WIN32) auto const sdlwindow = window.platform_window(); SDL_SysWMinfo info; SDL_VERSION(&info.version); if (!SDL_GetWindowWMInfo(sdlwindow, &info)) return std::make_pair(nullptr, nullptr); window_handle = info.info.win.window; #endif switch (cooperative_level) { case dinput_cooperative_level::BACKGROUND: di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; break; case dinput_cooperative_level::FOREGROUND: //di_cooperative_level = DISCL_FOREGROUND | DISCL_NONEXCLUSIVE; di_cooperative_level = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE; break; default: throw false; } } // set the cooperative level result = device->SetCooperativeLevel(GetDesktopWindow(), di_cooperative_level); if (result != DI_OK) { osd_printf_error("DirectInput: Unable to set cooperative level.\n"); return std::make_pair(nullptr, nullptr); } // return new device 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 #else // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) #include "input_module.h" namespace osd { namespace { MODULE_NOT_SUPPORTED(keyboard_input_dinput, OSD_KEYBOARDINPUT_PROVIDER, "dinput") MODULE_NOT_SUPPORTED(mouse_input_dinput, OSD_MOUSEINPUT_PROVIDER, "dinput") MODULE_NOT_SUPPORTED(joystick_input_dinput, OSD_JOYSTICKINPUT_PROVIDER, "dinput") } // anonymous namespace } // namespace osd #endif // defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) MODULE_DEFINITION(KEYBOARDINPUT_DINPUT, osd::keyboard_input_dinput) MODULE_DEFINITION(MOUSEINPUT_DINPUT, osd::mouse_input_dinput) MODULE_DEFINITION(JOYSTICKINPUT_DINPUT, osd::joystick_input_dinput)