diff options
Diffstat (limited to 'src/osd/modules/input/input_dinput.cpp')
-rw-r--r-- | src/osd/modules/input/input_dinput.cpp | 1540 |
1 files changed, 1122 insertions, 418 deletions
diff --git a/src/osd/modules/input/input_dinput.cpp b/src/osd/modules/input/input_dinput.cpp index 0b5eb348dc0..6f539c2f7fc 100644 --- a/src/osd/modules/input/input_dinput.cpp +++ b/src/osd/modules/input/input_dinput.cpp @@ -1,281 +1,354 @@ // 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 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 "input_module.h" #include "modules/osdmodule.h" -#if defined(OSD_WINDOWS) +#if defined(OSD_WINDOWS) || defined(SDLMAME_WIN32) -// standard windows headers -#include <windows.h> -#include <initguid.h> -#include <tchar.h> -#include <wrl/client.h> +#include "input_dinput.h" -// undef WINNT for dinput.h to prevent duplicate definition -#undef WINNT -#include <dinput.h> -#undef interface +#include "interface/inputseq.h" +#include "windows/winutil.h" -#include <mutex> +// emu +#include "inpttype.h" -// MAME headers -#include "emu.h" -#include "strconv.h" +// lib/util +#include "util/corestr.h" -// MAMEOS headers -#include "window.h" -#include "winutil.h" +#ifdef SDLMAME_WIN32 +#include <SDL2/SDL.h> +#include <SDL2/SDL_syswm.h> +#endif -#include "input_common.h" -#include "input_windows.h" -#include "input_dinput.h" +#include <algorithm> +#include <cmath> +#include <iterator> +#include <memory> -using namespace Microsoft::WRL; +// standard windows headers +#include <initguid.h> +#include <tchar.h> -static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal); -//============================================================ -// dinput_set_dword_property -//============================================================ +namespace osd { -#if DIRECTINPUT_VERSION >= 0x0800 -static HRESULT dinput_set_dword_property(ComPtr<IDirectInputDevice8> device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) -#else -static HRESULT dinput_set_dword_property(ComPtr<IDirectInputDevice> device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) -#endif +namespace { + +std::string guid_to_string(GUID const &guid) { - DIPROPDWORD dipdw; + // 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; +} - dipdw.diph.dwSize = sizeof(dipdw); - dipdw.diph.dwHeaderSize = sizeof(dipdw.diph); - dipdw.diph.dwObj = object; - dipdw.diph.dwHow = how; - dipdw.dwData = value; - return device->SetProperty(property_guid, &dipdw.diph); -} //============================================================ -// dinput_device - base directinput device +// dinput_keyboard_device - DirectInput keyboard device //============================================================ -dinput_device::dinput_device(running_machine &machine, const char *name, const char *id, input_device_class deviceclass, input_module &module) - : device_info(machine, name, id, deviceclass, module), - dinput({nullptr}) +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(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_device::~dinput_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 } }) { - if (dinput.device != nullptr) - dinput.device.Reset(); } -HRESULT dinput_device::poll_dinput(LPVOID pState) const +void dinput_keyboard_device::poll(bool relative_reset) { - HRESULT result; - - // first poll the device, then get the state -#if DIRECTINPUT_VERSION >= 0x0800 - dinput.device->Poll(); -#else - if (dinput.device2 != nullptr) - dinput.device2->Poll(); -#endif + // poll the DirectInput immediate state + std::lock_guard<std::mutex> scope_lock(m_device_lock); + poll_dinput(&m_keyboard.state); +} - // GetDeviceState returns the immediate state - result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); +void dinput_keyboard_device::reset() +{ + memset(&m_keyboard.state, 0, sizeof(m_keyboard.state)); +} - // handle lost inputs here - if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) +void dinput_keyboard_device::configure(input_device &device) +{ + // populate it + char defname[20]; + for (unsigned keynum = 0; keynum < MAX_KEYS; keynum++) { - result = dinput.device->Acquire(); - if (result == DI_OK) - result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); + 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]); } - - return result; } + //============================================================ -// dinput_keyboard_device - directinput keyboard device +// dinput_mouse_device - DirectInput mouse device //============================================================ -dinput_keyboard_device::dinput_keyboard_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_KEYBOARD, module), - keyboard({{0}}) +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(bool relative_reset) override; + void reset() override; + virtual void configure(input_device &device) override; + +private: + DIMOUSESTATE2 m_mouse; +}; -// Polls the direct input immediate state -void dinput_keyboard_device::poll() +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}) { - std::lock_guard<std::mutex> scope_lock(m_device_lock); - - // Poll the state - dinput_device::poll_dinput(&keyboard.state); + // 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_keyboard_device::reset() -{ - memset(&keyboard.state, 0, sizeof(keyboard.state)); -} - -//============================================================ -// dinput_api_helper - DirectInput API helper -//============================================================ - -dinput_api_helper::dinput_api_helper(int version) - : m_dinput(nullptr), - m_dinput_version(version), - m_dinput_create_prt(nullptr) +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; + } } -dinput_api_helper::~dinput_api_helper() +void dinput_mouse_device::reset() { - m_dinput.Reset(); + memset(&m_mouse, 0, sizeof(m_mouse)); } -int dinput_api_helper::initialize() +void dinput_mouse_device::configure(input_device &device) { - HRESULT result; - -#if DIRECTINPUT_VERSION >= 0x0800 - if (m_dinput_version >= 0x0800) + // populate the axes + for (int axisnum = 0; axisnum < m_caps.dwAxes; axisnum++) { - result = DirectInput8Create(GetModuleHandleUni(), m_dinput_version, IID_IDirectInput8, reinterpret_cast<void **>(m_dinput.GetAddressOf()), nullptr); - if (result != DI_OK) - { - m_dinput_version = 0; - return result; - } + // 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); } - else -#endif - { - m_dinput_dll = osd::dynamic_module::open({ "dinput.dll" }); - m_dinput_create_prt = m_dinput_dll->bind<dinput_create_fn>("DirectInputCreateW"); - if (m_dinput_create_prt == nullptr) - { - osd_printf_verbose("Legacy DirectInput library dinput.dll is not available\n"); - return ERROR_DLL_NOT_FOUND; - } + // populate the buttons + for (int butnum = 0; butnum < m_caps.dwButtons; butnum++) + { + auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); - // first attempt to initialize DirectInput at v7 - m_dinput_version = 0x0700; - result = (*m_dinput_create_prt)(GetModuleHandleUni(), m_dinput_version, m_dinput.GetAddressOf(), nullptr); - if (result != DI_OK) - { - // if that fails, try version 5 - m_dinput_version = 0x0500; - result = (*m_dinput_create_prt)(GetModuleHandleUni(), m_dinput_version, m_dinput.GetAddressOf(), nullptr); - if (result != DI_OK) - { - m_dinput_version = 0; - return result; - } - } + // 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]); } - - osd_printf_verbose("DirectInput: Using DirectInput %d\n", m_dinput_version >> 8); - return 0; -} - - -HRESULT dinput_api_helper::enum_attached_devices(int devclass, device_enum_interface *enumerate_interface, void *state) const -{ - device_enum_interface::dinput_callback_context ctx; - ctx.self = enumerate_interface; - ctx.state = state; - - return m_dinput->EnumDevices(devclass, device_enum_interface::enum_callback, &ctx, DIEDFL_ATTACHEDONLY); } //============================================================ -// dinput_module - base directinput module +// dinput_module - base DirectInput module //============================================================ -class dinput_module : public wininput_module, public device_enum_interface +class dinput_module : public input_module_impl<dinput_device, osd_common_t> { -protected: - std::unique_ptr<dinput_api_helper> m_dinput_helper; - public: - dinput_module(const char* type, const char* name) - : wininput_module(type, name), - m_dinput_helper(nullptr) + dinput_module(const char* type, const char* name) : + input_module_impl<dinput_device, osd_common_t>(type, name), + m_dinput_helper(nullptr) { } - int init_internal() override + virtual int init(osd_interface &osd, osd_options const &options) override { - m_dinput_helper = std::make_unique<dinput_api_helper>(DIRECTINPUT_VERSION); - int result = m_dinput_helper->initialize(); - if (result != 0) + m_dinput_helper = std::make_unique<dinput_api_helper>(); + int const result = m_dinput_helper->initialize(); + if (result) + { + m_dinput_helper.reset(); return result; + } - return 0; + return input_module_impl<dinput_device, osd_common_t>::init(osd, options); } - void exit() override + virtual void exit() override { - wininput_module::exit(); - m_dinput_helper.reset(); - } + input_module_impl<dinput_device, osd_common_t>::exit(); - void input_init(running_machine &machine) override - { - HRESULT result = m_dinput_helper->enum_attached_devices(dinput_devclass(), this, &machine); - if (result != DI_OK) - fatalerror("DirectInput: Unable to enumerate keyboards (result=%08X)\n", static_cast<uint32_t>(result)); + m_dinput_helper.reset(); } - static std::string device_item_name(dinput_device * devinfo, int offset, const char * defstring, const TCHAR * suffix) + virtual void input_init(running_machine &machine) override { - DIDEVICEOBJECTINSTANCE instance = { 0 }; - HRESULT result; - - // query the key name - instance.dwSize = sizeof(instance); - result = devinfo->dinput.device->GetObjectInfo(&instance, offset, DIPH_BYOFFSET); + input_module_impl<dinput_device, osd_common_t>::input_init(machine); - // if we got an error and have no default string, just return nullptr + HRESULT const result = m_dinput_helper->enum_attached_devices( + dinput_devclass(), + [this] (LPCDIDEVICEINSTANCE instance) { return device_enum_callback(instance); }); if (result != DI_OK) - { - if (defstring == nullptr) - return nullptr; - - // Return the default value - return std::string(defstring); - } - - // convert the name to utf8 - std::string namestring = osd::text::from_tstring(instance.tszName); - - // if no suffix, return as-is - if (suffix == nullptr) - return namestring; - - // convert the suffix to utf8 - std::string suffix_utf8 = osd::text::from_tstring(suffix); - - // Concat the name and suffix - return namestring + " " + suffix_utf8; + 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<dinput_api_helper> m_dinput_helper; }; + class keyboard_input_dinput : public dinput_module { public: @@ -284,69 +357,30 @@ public: { } - int dinput_devclass() override +protected: + virtual int dinput_devclass() override { -#if DIRECTINPUT_VERSION >= 0x0800 return DI8DEVCLASS_KEYBOARD; -#else - return DIDEVTYPE_KEYBOARD; -#endif } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { - running_machine &machine = *static_cast<running_machine *>(ref); - dinput_keyboard_device *devinfo; - int keynum; - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_keyboard_device>(machine, *this, instance, &c_dfDIKeyboard, nullptr, dinput_cooperative_level::FOREGROUND); - if (devinfo == nullptr) - goto exit; - - // populate it - for (keynum = 0; keynum < MAX_KEYS; keynum++) - { - input_item_id itemid = keyboard_trans_table::instance().map_di_scancode_to_itemid(keynum); - char defname[20]; - std::string name; - - // generate/fetch the name - snprintf(defname, ARRAY_LENGTH(defname), "Scan%03d", keynum); - name = device_item_name(devinfo, keynum, defname, nullptr); + auto devinfo = m_dinput_helper->create_device<dinput_keyboard_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)); - // add the item to the device - devinfo->device()->add_item(name.c_str(), itemid, generic_button_get_state<std::uint8_t>, &devinfo->keyboard.state[keynum]); - } - - exit: return DIENUM_CONTINUE; } }; -dinput_mouse_device::dinput_mouse_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_MOUSE, module), - mouse({0}) -{ -} - -void dinput_mouse_device::poll() -{ - // poll - dinput_device::poll_dinput(&mouse); - - // scale the axis data - mouse.lX *= INPUT_RELATIVE_PER_PIXEL; - mouse.lY *= INPUT_RELATIVE_PER_PIXEL; - mouse.lZ *= INPUT_RELATIVE_PER_PIXEL; -} - -void dinput_mouse_device::reset() -{ - memset(&mouse, 0, sizeof(mouse)); -} - class mouse_input_dinput : public dinput_module { public: @@ -355,284 +389,954 @@ public: { } - int dinput_devclass() override +protected: + virtual int dinput_devclass() override { -#if DIRECTINPUT_VERSION >= 0x0800 return DI8DEVCLASS_POINTER; -#else - return DIDEVTYPE_MOUSE; -#endif } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override { - dinput_mouse_device *devinfo = nullptr; - running_machine &machine = *static_cast<running_machine *>(ref); - int axisnum, butnum; - HRESULT result; - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_mouse_device>(machine, *this, instance, &c_dfDIMouse2, &c_dfDIMouse, dinput_cooperative_level::FOREGROUND); - if (devinfo == nullptr) - goto exit; + auto devinfo = m_dinput_helper->create_device<dinput_mouse_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)); - // set relative mode on the mouse device - result = dinput_set_dword_property(devinfo->dinput.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 %u (%s)\n", static_cast<unsigned int>(devicelist()->size()), devinfo->name()); - goto error; - } + return DIENUM_CONTINUE; + } +}; - // cap the number of axes and buttons based on the format - devinfo->dinput.caps.dwAxes = std::min(devinfo->dinput.caps.dwAxes, DWORD(3)); - devinfo->dinput.caps.dwButtons = std::min(devinfo->dinput.caps.dwButtons, DWORD((devinfo->dinput.format == &c_dfDIMouse) ? 4 : 8)); - // populate the axes - for (axisnum = 0; axisnum < devinfo->dinput.caps.dwAxes; axisnum++) - { - // add to the mouse device and optionally to the gun device as well - std::string name = device_item_name(devinfo, offsetof(DIMOUSESTATE, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr); - devinfo->device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &devinfo->mouse.lX + axisnum); - } +class joystick_input_dinput : public dinput_module +{ +public: + joystick_input_dinput() : + dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") + { + } - // populate the buttons - for (butnum = 0; butnum < devinfo->dinput.caps.dwButtons; butnum++) - { - uintptr_t offset = reinterpret_cast<uintptr_t>(&static_cast<DIMOUSESTATE *>(nullptr)->rgbButtons[butnum]); +protected: + virtual int dinput_devclass() override + { + return DI8DEVCLASS_GAMECTRL; + } - // add to the mouse device - std::string name = device_item_name(devinfo, offset, default_button_name(butnum), nullptr); - devinfo->device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum), - generic_button_get_state<BYTE>, - &devinfo->mouse.rgbButtons[butnum]); - } + virtual BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance) override + { + // allocate and link in a new device + auto devinfo = m_dinput_helper->create_device<dinput_joystick_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)); - exit: return DIENUM_CONTINUE; - - error: - if (devinfo != nullptr) - devicelist()->free_device(devinfo); - goto exit; } }; -dinput_joystick_device::dinput_joystick_device(running_machine &machine, const char *name, const char *id, input_module &module) - : dinput_device(machine, name, id, DEVICE_CLASS_JOYSTICK, module), - joystick({{0}}) +} // anonymous namespace + + +//============================================================ +// dinput_device - base DirectInput device +//============================================================ + +dinput_device::dinput_device( + std::string &&name, + std::string &&id, + input_module &module, + Microsoft::WRL::ComPtr<IDirectInputDevice8> &&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) { } -void dinput_joystick_device::reset() +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 { - memset(&joystick.state, 0, sizeof(joystick.state)); + // 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; } -void dinput_joystick_device::poll() + +//============================================================ +// dinput_joystick_device - directinput joystick device +//============================================================ + +dinput_joystick_device::dinput_joystick_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_joystick({ { 0 } }) { - int axisnum; + // 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)); +} - // poll the device first - if (dinput_device::poll_dinput(&joystick.state) != ERROR_SUCCESS) - return; +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); +} - // normalize axis values - for (axisnum = 0; axisnum < 8; axisnum++) +void dinput_joystick_device::poll(bool relative_reset) +{ + // poll the device first + if (dinput_device::poll_dinput(&m_joystick.state) == DI_OK) { - LONG *axis = (&joystick.state.lX) + axisnum; - *axis = normalize_absolute_axis(*axis, joystick.rangemin[axisnum], joystick.rangemax[axisnum]); + // 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<double>(scaled + input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MIN, input_device::ABSOLUTE_MAX)); + } + } } } -int dinput_joystick_device::configure() +void dinput_joystick_device::configure(input_device &device) { + input_device::assignment_vector assignments; HRESULT result; - uint32_t axisnum, axiscount; - - auto devicelist = static_cast<input_module_base&>(module()).devicelist(); - // temporary approximation of index - int devindex = devicelist->size(); - - // set absolute mode - result = dinput_set_dword_property(dinput.device, DIPROP_AXISMODE, 0, DIPH_DEVICE, DIPROPAXISMODE_ABS); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to set absolute mode for joystick %d (%s)\n", devindex, name()); + // 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_set_dword_property(dinput.device, DIPROP_DEADZONE, 0, DIPH_DEVICE, 0); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to reset deadzone for joystick %d (%s)\n", devindex, name()); + 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_set_dword_property(dinput.device, DIPROP_SATURATION, 0, DIPH_DEVICE, 10000); - if (result != DI_OK && result != DI_PROPNOEFFECT) - osd_printf_warning("DirectInput: Unable to reset saturation for joystick %d (%s)\n", devindex, name()); - - // cap the number of axes, POVs, and buttons based on the format - dinput.caps.dwAxes = std::min(dinput.caps.dwAxes, DWORD(8)); - dinput.caps.dwPOVs = std::min(dinput.caps.dwPOVs, DWORD(4)); - dinput.caps.dwButtons = std::min(dinput.caps.dwButtons, DWORD(128)); + 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 - for (axisnum = axiscount = 0; axiscount < dinput.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++) { - DIPROPRANGE dipr; - std::string name; - // 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 = dinput.device->GetProperty(DIPROP_RANGE, &dipr.diph); + 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; + } - joystick.rangemin[axisnum] = dipr.lMin; - joystick.rangemax[axisnum] = dipr.lMax; + m_joystick.rangemin[axisnum] = dipr.lMin; + m_joystick.rangemax[axisnum] = dipr.lMax; // populate the item description as well - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, lX) + axisnum * sizeof(LONG), default_axis_name[axisnum], nullptr); - device()->add_item( - name.c_str(), - static_cast<input_item_id>(ITEM_ID_XAXIS + axisnum), - generic_axis_get_state<LONG>, - &joystick.state.lX + axisnum); + 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<LONG>, + &m_joystick.state.lX + axisnum); axiscount++; } - // populate the POVs - for (uint32_t povnum = 0; povnum < dinput.caps.dwPOVs; povnum++) + // 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) { - std::string name; + // 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 - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("L")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_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<void *>(uintptr_t(povnum * 4 + POVDIR_LEFT))); // right - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("R")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_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<void *>(uintptr_t(povnum * 4 + POVDIR_RIGHT))); // up - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("U")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_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<void *>(uintptr_t(povnum * 4 + POVDIR_UP))); // down - name = dinput_module::device_item_name(this, offsetof(DIJOYSTATE2, rgdwPOV) + povnum * sizeof(DWORD), default_pov_name(povnum), TEXT("D")); - device()->add_item(name.c_str(), ITEM_ID_OTHER_SWITCH, dinput_joystick_pov_get_state, reinterpret_cast<void *>(static_cast<uintptr_t>(povnum * 4 + POVDIR_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<void *>(uintptr_t(povnum * 4 + POVDIR_DOWN))); } // populate the buttons - for (uint32_t butnum = 0; butnum < dinput.caps.dwButtons; butnum++) + for (uint32_t butnum = 0; butnum < m_caps.dwButtons; butnum++) { - uintptr_t offset = reinterpret_cast<uintptr_t>(&static_cast<DIJOYSTATE2 *>(nullptr)->rgbButtons[butnum]); - std::string name = dinput_module::device_item_name(this, offset, default_button_name(butnum), nullptr); + auto offset = reinterpret_cast<uintptr_t>(&static_cast<DIJOYSTATE2 *>(nullptr)->rgbButtons[butnum]); input_item_id itemid; - if (butnum < INPUT_MAX_BUTTONS) - itemid = static_cast<input_item_id>(ITEM_ID_BUTTON1 + butnum); + itemid = input_item_id(ITEM_ID_BUTTON1 + butnum); else if (butnum < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) - itemid = static_cast<input_item_id>(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + butnum); + itemid = input_item_id(ITEM_ID_ADD_SWITCH1 - INPUT_MAX_BUTTONS + butnum); else itemid = ITEM_ID_OTHER_SWITCH; - device()->add_item(name.c_str(), itemid, generic_button_get_state<BYTE>, &joystick.state.rgbButtons[butnum]); - } + 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]); - return 0; -} + // 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); -class joystick_input_dinput : public dinput_module -{ -public: - joystick_input_dinput() : - dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") - { + // 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; + } + } } - int dinput_devclass() override + // add default assignments depending on type + if (DI8DEVTYPE_FLIGHT == type) { -#if DIRECTINPUT_VERSION >= 0x0800 - return DI8DEVCLASS_GAMECTRL; -#else - return DIDEVTYPE_JOYSTICK; -#endif - } + 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); + } + } - BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override - { - dinput_cooperative_level cooperative_level = dinput_cooperative_level::FOREGROUND; - running_machine &machine = *static_cast<running_machine *>(ref); - dinput_joystick_device *devinfo; - int result = 0; + // 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); + } + } - if (!osd_common_t::s_window_list.empty() && osd_common_t::s_window_list.front()->win_has_menu()) - cooperative_level = dinput_cooperative_level::BACKGROUND; + // 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); + } + } - // allocate and link in a new device - devinfo = m_dinput_helper->create_device<dinput_joystick_device>(machine, *this, instance, &c_dfDIJoystick, nullptr, cooperative_level); - if (devinfo == nullptr) - goto exit; + // 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); + } + } + } - result = devinfo->configure(); - if (result != 0) + // 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]) { - osd_printf_error("Failed to configure DI Joystick device. Error 0x%x\n", static_cast<unsigned int>(result)); + // 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]; } - exit: - return DIENUM_CONTINUE; + // 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]); } -}; -//============================================================ -// dinput_joystick_pov_get_state -//============================================================ + // set default assignments + device.set_default_assignments(std::move(assignments)); +} -static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal) +int32_t dinput_joystick_device::pov_get_state(void *device_internal, void *item_internal) { - dinput_joystick_device *devinfo = static_cast<dinput_joystick_device *>(device_internal); - int povnum = reinterpret_cast<uintptr_t>(item_internal) / 4; - int povdir = reinterpret_cast<uintptr_t>(item_internal) % 4; - int32_t result = 0; - DWORD pov; + auto *const devinfo = static_cast<dinput_joystick_device *>(device_internal); + int const povnum = uintptr_t(item_internal) / 4; + int const povdir = uintptr_t(item_internal) % 4; // get the current state - devinfo->module().poll_if_necessary(devinfo->machine()); - pov = devinfo->joystick.state.rgdwPOV[povnum]; + DWORD const pov = devinfo->m_joystick.state.rgdwPOV[povnum]; // if invalid, return 0 if ((pov & 0xffff) == 0xffff) - return result; + return 0; // return the current state switch (povdir) { - case POVDIR_LEFT: result = (pov >= 22500 && pov <= 31500); break; - case POVDIR_RIGHT: result = (pov >= 4500 && pov <= 13500); break; - case POVDIR_UP: result = (pov >= 31500 || pov <= 4500); break; - case POVDIR_DOWN: result = (pov >= 13500 && pov <= 22500); break; + 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 result; + + 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<Microsoft::WRL::ComPtr<IDirectInputDevice8>, 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<IDirectInputDevice8> 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<win_window_info &>(*osd_common_t::window_list().front()); + bool const standalone_window = !window.attached_mode(); +#elif defined(SDLMAME_WIN32) + auto const &window = dynamic_cast<sdl_window_info &>(*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); } -#else +} // 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") -#endif -MODULE_DEFINITION(KEYBOARDINPUT_DINPUT, keyboard_input_dinput) -MODULE_DEFINITION(MOUSEINPUT_DINPUT, mouse_input_dinput) -MODULE_DEFINITION(JOYSTICKINPUT_DINPUT, joystick_input_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) |