// license:BSD-3-Clause // copyright-holders:Aaron Giles, Brad Hughes //============================================================ // // input_dinput.cpp - Windows DirectInput support // //============================================================ #include "input_module.h" #include "modules/osdmodule.h" #if defined(OSD_WINDOWS) // standard windows headers #include #include #include #include // undef WINNT for dinput.h to prevent duplicate definition #undef WINNT #include #undef interface #include // MAME headers #include "emu.h" #include "strconv.h" // MAMEOS headers #include "window.h" #include "winutil.h" #include "input_common.h" #include "input_windows.h" #include "input_dinput.h" using namespace Microsoft::WRL; static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal); //============================================================ // dinput_set_dword_property //============================================================ #if DIRECTINPUT_VERSION >= 0x0800 static HRESULT dinput_set_dword_property(ComPtr device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) #else static HRESULT dinput_set_dword_property(ComPtr device, REFGUID property_guid, DWORD object, DWORD how, DWORD value) #endif { DIPROPDWORD dipdw; 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_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}) { } dinput_device::~dinput_device() { if (dinput.device != nullptr) dinput.device.Reset(); } HRESULT dinput_device::poll_dinput(LPVOID pState) const { 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 // GetDeviceState returns the immediate state result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); // handle lost inputs here if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) { result = dinput.device->Acquire(); if (result == DI_OK) result = dinput.device->GetDeviceState(dinput.format->dwDataSize, pState); } return result; } //============================================================ // dinput_keyboard_device - directinput keyboard 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}}) { } // Polls the direct input immediate state void dinput_keyboard_device::poll() { std::lock_guard scope_lock(m_device_lock); // Poll the state dinput_device::poll_dinput(&keyboard.state); } 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) { } dinput_api_helper::~dinput_api_helper() { m_dinput.Reset(); } int dinput_api_helper::initialize() { HRESULT result; #if DIRECTINPUT_VERSION >= 0x0800 if (m_dinput_version >= 0x0800) { result = DirectInput8Create(GetModuleHandleUni(), m_dinput_version, IID_IDirectInput8, reinterpret_cast(m_dinput.GetAddressOf()), nullptr); if (result != DI_OK) { m_dinput_version = 0; return result; } } else #endif { m_dinput_dll = osd::dynamic_module::open({ "dinput.dll" }); m_dinput_create_prt = m_dinput_dll->bind("DirectInputCreateW"); if (m_dinput_create_prt == nullptr) { osd_printf_verbose("Legacy DirectInput library dinput.dll is not available\n"); return ERROR_DLL_NOT_FOUND; } // 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; } } } 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 //============================================================ class dinput_module : public wininput_module, public device_enum_interface { protected: std::unique_ptr m_dinput_helper; public: dinput_module(const char* type, const char* name) : wininput_module(type, name), m_dinput_helper(nullptr) { } int init_internal() override { m_dinput_helper = std::make_unique(DIRECTINPUT_VERSION); int result = m_dinput_helper->initialize(); if (result != 0) return result; return 0; } void exit() override { wininput_module::exit(); m_dinput_helper.reset(); } 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(result)); } static std::string device_item_name(dinput_device * devinfo, int offset, const char * defstring, const TCHAR * suffix) { DIDEVICEOBJECTINSTANCE instance = { 0 }; HRESULT result; // query the key name instance.dwSize = sizeof(instance); result = devinfo->dinput.device->GetObjectInfo(&instance, offset, DIPH_BYOFFSET); // if we got an error and have no default string, just return nullptr 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; } protected: virtual int dinput_devclass() = 0; }; class keyboard_input_dinput : public dinput_module { public: keyboard_input_dinput() : dinput_module(OSD_KEYBOARDINPUT_PROVIDER, "dinput") { } 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 { running_machine &machine = *static_cast(ref); dinput_keyboard_device *devinfo; int keynum; // allocate and link in a new device devinfo = m_dinput_helper->create_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); // add the item to the device devinfo->device()->add_item(name.c_str(), itemid, generic_button_get_state, &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: mouse_input_dinput() : dinput_module(OSD_MOUSEINPUT_PROVIDER, "dinput") { } 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 { dinput_mouse_device *devinfo = nullptr; running_machine &machine = *static_cast(ref); int axisnum, butnum; HRESULT result; // allocate and link in a new device devinfo = m_dinput_helper->create_device(machine, *this, instance, &c_dfDIMouse2, &c_dfDIMouse, dinput_cooperative_level::FOREGROUND); if (devinfo == nullptr) goto exit; // 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(devicelist()->size()), devinfo->name()); goto error; } // 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(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &devinfo->mouse.lX + axisnum); } // populate the buttons for (butnum = 0; butnum < devinfo->dinput.caps.dwButtons; butnum++) { uintptr_t offset = reinterpret_cast(&static_cast(nullptr)->rgbButtons[butnum]); // 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(ITEM_ID_BUTTON1 + butnum), generic_button_get_state, &devinfo->mouse.rgbButtons[butnum]); } 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}}) { } void dinput_joystick_device::reset() { memset(&joystick.state, 0, sizeof(joystick.state)); } void dinput_joystick_device::poll() { int axisnum; // poll the device first if (dinput_device::poll_dinput(&joystick.state) != ERROR_SUCCESS) return; // normalize axis values for (axisnum = 0; axisnum < 8; axisnum++) { LONG *axis = (&joystick.state.lX) + axisnum; *axis = normalize_absolute_axis(*axis, joystick.rangemin[axisnum], joystick.rangemax[axisnum]); } } int dinput_joystick_device::configure() { HRESULT result; uint32_t axisnum, axiscount; auto devicelist = static_cast(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()); // 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()); // 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)); // populate the axes for (axisnum = axiscount = 0; axiscount < dinput.caps.dwAxes && axisnum < 8; axisnum++) { DIPROPRANGE dipr; std::string name; // fetch the range of this axis 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); if (result != DI_OK) continue; joystick.rangemin[axisnum] = dipr.lMin; 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(ITEM_ID_XAXIS + axisnum), generic_axis_get_state, &joystick.state.lX + axisnum); axiscount++; } // populate the POVs for (uint32_t povnum = 0; povnum < dinput.caps.dwPOVs; povnum++) { std::string name; // 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(static_cast(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(static_cast(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(static_cast(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(static_cast(povnum * 4 + POVDIR_DOWN))); } // populate the buttons for (uint32_t butnum = 0; butnum < dinput.caps.dwButtons; butnum++) { uintptr_t offset = reinterpret_cast(&static_cast(nullptr)->rgbButtons[butnum]); std::string name = dinput_module::device_item_name(this, offset, default_button_name(butnum), nullptr); input_item_id itemid; if (butnum < INPUT_MAX_BUTTONS) itemid = static_cast(ITEM_ID_BUTTON1 + butnum); else if (butnum < INPUT_MAX_BUTTONS + INPUT_MAX_ADD_SWITCH) itemid = static_cast(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, &joystick.state.rgbButtons[butnum]); } return 0; } class joystick_input_dinput : public dinput_module { public: joystick_input_dinput() : dinput_module(OSD_JOYSTICKINPUT_PROVIDER, "dinput") { } int dinput_devclass() override { #if DIRECTINPUT_VERSION >= 0x0800 return DI8DEVCLASS_GAMECTRL; #else return DIDEVTYPE_JOYSTICK; #endif } BOOL device_enum_callback(LPCDIDEVICEINSTANCE instance, LPVOID ref) override { dinput_cooperative_level cooperative_level = dinput_cooperative_level::FOREGROUND; running_machine &machine = *static_cast(ref); dinput_joystick_device *devinfo; int result = 0; if (!osd_common_t::s_window_list.empty() && osd_common_t::s_window_list.front()->win_has_menu()) cooperative_level = dinput_cooperative_level::BACKGROUND; // allocate and link in a new device devinfo = m_dinput_helper->create_device(machine, *this, instance, &c_dfDIJoystick, nullptr, cooperative_level); if (devinfo == nullptr) goto exit; result = devinfo->configure(); if (result != 0) { osd_printf_error("Failed to configure DI Joystick device. Error 0x%x\n", static_cast(result)); } exit: return DIENUM_CONTINUE; } }; //============================================================ // dinput_joystick_pov_get_state //============================================================ static int32_t dinput_joystick_pov_get_state(void *device_internal, void *item_internal) { dinput_joystick_device *devinfo = static_cast(device_internal); int povnum = reinterpret_cast(item_internal) / 4; int povdir = reinterpret_cast(item_internal) % 4; int32_t result = 0; DWORD pov; // get the current state devinfo->module().poll_if_necessary(devinfo->machine()); pov = devinfo->joystick.state.rgdwPOV[povnum]; // if invalid, return 0 if ((pov & 0xffff) == 0xffff) return result; // 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; } return result; } #else 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)