749 lines
24 KiB
C++
749 lines
24 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file winRawInputDevice.cxx
|
|
* @author rdb
|
|
* @date 2018-01-19
|
|
*/
|
|
|
|
#include "winRawInputDevice.h"
|
|
#include "gamepadButton.h"
|
|
#include "mouseButton.h"
|
|
#include "buttonRegistry.h"
|
|
#include "winInputDeviceManager.h"
|
|
|
|
#if defined(_WIN32) && !defined(CPPPARSER)
|
|
|
|
#include <cfgmgr32.h>
|
|
#include <devpkey.h>
|
|
#include "phidsdi.h"
|
|
|
|
enum QuirkBits : int {
|
|
// Has no trigger axes.
|
|
QB_no_analog_triggers = 1,
|
|
|
|
// Throttle goes from -1 to 1 rather than from 0 to 1.
|
|
QB_centered_throttle = 2,
|
|
|
|
// Throttle is reversed.
|
|
QB_reversed_throttle = 4,
|
|
|
|
// Right stick uses Z and Rz inputs.
|
|
QB_rstick_from_z = 8,
|
|
|
|
// Axes on the right stick are swapped, using x for y and vice versa.
|
|
QB_right_axes_swapped = 64,
|
|
};
|
|
|
|
// Some nonstandard gamepads have different button mappings.
|
|
static const struct DeviceMapping {
|
|
unsigned short vendor;
|
|
unsigned short product;
|
|
InputDevice::DeviceClass device_class;
|
|
int quirks;
|
|
const char *buttons[16];
|
|
} mapping_presets[] = {
|
|
// SNES-style USB gamepad, or cheap unbranded USB gamepad with no sticks
|
|
// ABXY are mapped based on their position, not based on their label.
|
|
{0x0810, 0xe501, InputDevice::DeviceClass::gamepad, QB_no_analog_triggers,
|
|
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start"}
|
|
},
|
|
// Unbranded generic cheap USB gamepad
|
|
{0x0810, 0x0001, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers | QB_right_axes_swapped,
|
|
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
|
|
},
|
|
// Trust GXT 24 / SPEED Link SL-6535-SBK-01
|
|
{0x0079, 0x0006, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers,
|
|
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
|
|
},
|
|
// T.Flight Hotas X
|
|
{0x044f, 0xb108, InputDevice::DeviceClass::flight_stick, QB_centered_throttle | QB_reversed_throttle,
|
|
{0}
|
|
},
|
|
// NVIDIA Shield Controller
|
|
{0x0955, 0x7214, InputDevice::DeviceClass::gamepad, 0,
|
|
{"face_a", "face_b", 0, "face_x", "face_y", "rshoulder", "lshoulder", "rshoulder", 0, 0, 0, "start", 0, "lstick", "rstick", 0}
|
|
},
|
|
// Dualshock (PS4)
|
|
{0x054c, 0x05c4, InputDevice::DeviceClass::gamepad, QB_rstick_from_z,
|
|
{"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", 0, 0, "back", "start", "lstick", "rstick", "guide", 0}
|
|
},
|
|
// Dualshock 2nd Gen (PS4 Slim)
|
|
{0x054c, 0x09cc, InputDevice::DeviceClass::gamepad, QB_rstick_from_z,
|
|
{"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", 0, 0, "back", "start", "lstick", "rstick", "guide", 0}
|
|
},
|
|
// Dualshock 2nd Gen (PS4 wireless adapter)
|
|
{0x054c, 0x0ba0, InputDevice::DeviceClass::gamepad, QB_rstick_from_z,
|
|
{"face_x", "face_a", "face_b", "face_y", "lshoulder", "rshoulder", 0, 0, "back", "start", "lstick", "rstick", "guide", 0}
|
|
},
|
|
// PS2 controller connected through a USB adapter
|
|
{0x2563, 0x0523, InputDevice::DeviceClass::gamepad, QB_rstick_from_z | QB_no_analog_triggers,
|
|
{"face_y", "face_b", "face_a", "face_x", "lshoulder", "rshoulder", "ltrigger", "rtrigger", "back", "start", "lstick", "rstick"}
|
|
},
|
|
// FrSky Simulator
|
|
{0x0483, 0x5720, InputDevice::DeviceClass::flight_stick, 0,
|
|
{0}
|
|
},
|
|
{0},
|
|
};
|
|
|
|
// This is our fallback button mapping, used with Xbox 360 and other devices.
|
|
static const char *default_gamepad_mapping[16] = {
|
|
"face_a", "face_b", "face_x", "face_y", "lshoulder", "rshoulder", "back", "start", "lstick", "rstick"
|
|
};
|
|
|
|
static pHidP_GetCaps _HidP_GetCaps = nullptr;
|
|
static pHidP_GetButtonCaps _HidP_GetButtonCaps = nullptr;
|
|
static pHidP_GetValueCaps _HidP_GetValueCaps = nullptr;
|
|
static pHidP_GetData _HidP_GetData = nullptr;
|
|
static pHidP_MaxDataListLength _HidP_MaxDataListLength = nullptr;
|
|
|
|
/**
|
|
* Static method to initialize the HID parser library. We load it dynamically
|
|
* because the Windows 7.1 SDK doesn't ship hid.lib.
|
|
*/
|
|
static bool init_hidp() {
|
|
HMODULE module = LoadLibraryA("hid.dll");
|
|
if (module) {
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Successfully loaded hid.dll\n";
|
|
}
|
|
|
|
_HidP_GetCaps = (pHidP_GetCaps)GetProcAddress(module, "HidP_GetCaps");
|
|
_HidP_GetButtonCaps = (pHidP_GetButtonCaps)GetProcAddress(module, "HidP_GetButtonCaps");
|
|
_HidP_GetValueCaps = (pHidP_GetValueCaps)GetProcAddress(module, "HidP_GetValueCaps");
|
|
_HidP_GetData = (pHidP_GetData)GetProcAddress(module, "HidP_GetData");
|
|
_HidP_MaxDataListLength = (pHidP_MaxDataListLength)GetProcAddress(module, "HidP_MaxDataListLength");
|
|
|
|
if (_HidP_GetCaps == nullptr || _HidP_GetButtonCaps == nullptr ||
|
|
_HidP_GetValueCaps == nullptr || _HidP_GetData == nullptr ||
|
|
_HidP_MaxDataListLength == nullptr) {
|
|
device_cat.error()
|
|
<< "Failed to locate function pointers in hid.dll\n";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
device_cat.error()
|
|
<< "Failed to load hid.dll.\n";
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Protected constructor. Given a raw device handle.
|
|
*/
|
|
WinRawInputDevice::
|
|
WinRawInputDevice(WinInputDeviceManager *manager, const char *path) :
|
|
_manager(manager),
|
|
_path(path),
|
|
_max_data_count(0),
|
|
_preparsed(nullptr) {
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
WinRawInputDevice::
|
|
~WinRawInputDevice() {
|
|
// Unregister the device from the manager.
|
|
LightMutexHolder holder(_lock);
|
|
if (_manager != nullptr) {
|
|
_manager->device_destroyed(this);
|
|
}
|
|
if (_preparsed != nullptr) {
|
|
free(_preparsed);
|
|
_preparsed = nullptr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called by InputDeviceManager when this device is connected. Returns true
|
|
* if the device was connected successfully.
|
|
*/
|
|
bool WinRawInputDevice::
|
|
on_arrival(HANDLE handle, const RID_DEVICE_INFO &info, std::string name) {
|
|
using std::hex;
|
|
using std::dec;
|
|
using std::swap;
|
|
|
|
LightMutexHolder holder(_lock);
|
|
|
|
_name = std::move(name);
|
|
|
|
int quirks = 0;
|
|
const char *const *gamepad_buttons = default_gamepad_mapping;
|
|
|
|
switch (info.dwType) {
|
|
case RIM_TYPEMOUSE:
|
|
_device_class = DeviceClass::mouse;
|
|
break;
|
|
|
|
case RIM_TYPEKEYBOARD:
|
|
_device_class = DeviceClass::keyboard;
|
|
break;
|
|
|
|
case RIM_TYPEHID:
|
|
_vendor_id = info.hid.dwVendorId;
|
|
_product_id = info.hid.dwProductId;
|
|
|
|
// Gamepads
|
|
if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
|
|
info.hid.usUsage == HID_USAGE_GENERIC_GAMEPAD) {
|
|
_device_class = DeviceClass::gamepad;
|
|
|
|
// Various game controllers, incl. flight sticks and some gamepads
|
|
} else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
|
|
info.hid.usUsage == HID_USAGE_GENERIC_JOYSTICK) {
|
|
_device_class = DeviceClass::flight_stick;
|
|
|
|
if (_name == "usb gamepad") {
|
|
// Well, it claims to be a gamepad...
|
|
_device_class = DeviceClass::gamepad;
|
|
}
|
|
|
|
// Mice
|
|
} else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
|
|
info.hid.usUsage == HID_USAGE_GENERIC_MOUSE) {
|
|
_device_class = DeviceClass::mouse;
|
|
|
|
// Keyboards
|
|
} else if (info.hid.usUsagePage == HID_USAGE_PAGE_GENERIC &&
|
|
info.hid.usUsage == HID_USAGE_GENERIC_KEYBOARD) {
|
|
_device_class = DeviceClass::keyboard;
|
|
|
|
// Digitizers
|
|
} else if (info.hid.usUsagePage == HID_USAGE_PAGE_DIGITIZER &&
|
|
info.hid.usUsage == 1) {
|
|
_device_class = DeviceClass::digitizer;
|
|
|
|
// 3Dconnexion SpaceNavigator and friends.
|
|
} else if (_vendor_id == 0x046d &&
|
|
(_product_id == 0xc623 ||
|
|
_product_id == 0xc625 ||
|
|
_product_id == 0xc626 ||
|
|
_product_id == 0xc627 ||
|
|
_product_id == 0xc628 ||
|
|
_product_id == 0xc629 ||
|
|
_product_id == 0xc62b)) {
|
|
_device_class = DeviceClass::spatial_mouse;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (_device_class == DeviceClass::gamepad ||
|
|
_device_class == DeviceClass::flight_stick) {
|
|
// Do we have a built-in mapping?
|
|
const DeviceMapping *mapping = mapping_presets;
|
|
while (mapping->vendor != 0) {
|
|
if (info.hid.dwVendorId == mapping->vendor &&
|
|
info.hid.dwProductId == mapping->product) {
|
|
_device_class = mapping->device_class;
|
|
gamepad_buttons = mapping->buttons;
|
|
quirks = mapping->quirks;
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Using preset mapping for " << mapping->device_class
|
|
<< " with VID=" << std::hex << mapping->vendor
|
|
<< " PID=" << mapping->product << std::dec << "\n";
|
|
}
|
|
break;
|
|
}
|
|
++mapping;
|
|
}
|
|
}
|
|
|
|
// Initialize hid.dll, which provides the HID parser functions.
|
|
static bool hid_initialized = false;
|
|
if (!hid_initialized) {
|
|
if (!init_hidp()) {
|
|
return false;
|
|
}
|
|
hid_initialized = true;
|
|
}
|
|
|
|
// Get the "preparsed data", which we can parse with the HID parser API.
|
|
UINT size = 0;
|
|
if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, nullptr, &size) < 0) {
|
|
return false;
|
|
}
|
|
|
|
PHIDP_PREPARSED_DATA buffer = (PHIDP_PREPARSED_DATA)malloc(size);
|
|
if (GetRawInputDeviceInfo(handle, RIDI_PREPARSEDDATA, buffer, &size) <= 0) {
|
|
return false;
|
|
}
|
|
_preparsed = buffer;
|
|
|
|
HIDP_CAPS caps;
|
|
if (_HidP_GetCaps(buffer, &caps) != HIDP_STATUS_SUCCESS) {
|
|
device_cat.warning()
|
|
<< "Failed to get capabilities from HID preparsed data.\n";
|
|
return false;
|
|
}
|
|
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Found " << _device_class << " device \"" << _name << "\" with "
|
|
<< caps.NumberInputDataIndices << " data indices, "
|
|
<< caps.NumberInputButtonCaps << " button caps, "
|
|
<< caps.NumberInputValueCaps << " value caps\n";
|
|
}
|
|
|
|
ButtonRegistry *registry = ButtonRegistry::ptr();
|
|
|
|
// Prepare a mapping of data indices to button/axis indices.
|
|
_indices.resize(caps.NumberInputDataIndices);
|
|
|
|
_buttons.clear();
|
|
_axes.clear();
|
|
|
|
USHORT num_button_caps = caps.NumberInputButtonCaps;
|
|
PHIDP_BUTTON_CAPS button_caps;
|
|
if (num_button_caps > 0u) {
|
|
button_caps = (PHIDP_BUTTON_CAPS)alloca(num_button_caps * sizeof(HIDP_BUTTON_CAPS));
|
|
_HidP_GetButtonCaps(HidP_Input, button_caps, &num_button_caps, buffer);
|
|
}
|
|
|
|
for (USHORT i = 0; i < num_button_caps; ++i) {
|
|
HIDP_BUTTON_CAPS &cap = button_caps[i];
|
|
int upper = 0;
|
|
if (cap.IsRange) {
|
|
upper = (cap.Range.UsageMax - cap.Range.UsageMin);
|
|
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Found button range: DataIndex=" << dec
|
|
<< cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
|
|
<< ", ReportID=" << (int)cap.ReportID
|
|
<< ", UsagePage=0x" << hex << cap.UsagePage
|
|
<< ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
|
|
<< dec << "\n";
|
|
}
|
|
} else {
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Found button: DataIndex=" << dec << cap.NotRange.DataIndex
|
|
<< ", ReportID=" << (int)cap.ReportID
|
|
<< ", UsagePage=0x" << hex << cap.UsagePage
|
|
<< ", Usage=0x" << cap.NotRange.Usage
|
|
<< dec << "\n";
|
|
}
|
|
}
|
|
|
|
nassertd(cap.Range.DataIndexMin + upper < (int)_indices.size()) continue;
|
|
|
|
// Windows will only tell us which buttons in a report are "on", so we
|
|
// need to keep track of which buttons exist in which report so that we
|
|
// can figure out which ones are off.
|
|
if (cap.ReportID >= _report_buttons.size()) {
|
|
_report_buttons.resize(cap.ReportID + 1);
|
|
}
|
|
for (int j = 0; j <= upper; ++j) {
|
|
USAGE usage = j + cap.Range.UsageMin;
|
|
USHORT data_index = j + cap.Range.DataIndexMin;
|
|
ButtonHandle handle = ButtonHandle::none();
|
|
switch (cap.UsagePage) {
|
|
case HID_USAGE_PAGE_BUTTON:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
if (usage > 0 && usage - 1 < _countof(default_gamepad_mapping)) {
|
|
if (gamepad_buttons[usage - 1] != nullptr) {
|
|
handle = registry->find_button(gamepad_buttons[usage - 1]);
|
|
}
|
|
}
|
|
} else if (_device_class == DeviceClass::flight_stick) {
|
|
if (usage > 0) {
|
|
handle = GamepadButton::joystick(usage - 1);
|
|
}
|
|
} else if (_device_class == DeviceClass::mouse) {
|
|
// In Panda, wheel and right button are flipped around...
|
|
int button = (usage == 2 || usage == 3) ? (4 - usage) : (usage - 1);
|
|
handle = MouseButton::button(button);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
continue;
|
|
}
|
|
|
|
int button_index = _buttons.size();
|
|
_report_buttons[cap.ReportID].set_bit(button_index);
|
|
_indices[data_index] = Index::button(button_index);
|
|
_buttons.push_back(ButtonState(handle));
|
|
}
|
|
}
|
|
|
|
USHORT num_value_caps = caps.NumberInputValueCaps;
|
|
PHIDP_VALUE_CAPS value_caps;
|
|
if (num_value_caps > 0u) {
|
|
value_caps = (PHIDP_VALUE_CAPS)alloca(num_value_caps * sizeof(HIDP_VALUE_CAPS));
|
|
_HidP_GetValueCaps(HidP_Input, value_caps, &num_value_caps, buffer);
|
|
}
|
|
|
|
_hat_data_index = -1;
|
|
|
|
for (USHORT i = 0; i < num_value_caps; ++i) {
|
|
HIDP_VALUE_CAPS &cap = value_caps[i];
|
|
int upper = 0;
|
|
if (cap.IsRange) {
|
|
upper = (cap.Range.UsageMax - cap.Range.UsageMin);
|
|
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Found value range: DataIndex=" << dec
|
|
<< cap.Range.DataIndexMin << ".." << cap.Range.DataIndexMax
|
|
<< ", ReportID=" << (int)cap.ReportID
|
|
<< ", UsagePage=0x" << hex << cap.UsagePage
|
|
<< ", Usage=0x" << cap.Range.UsageMin << "..0x" << cap.Range.UsageMax
|
|
<< dec << ", LogicalMin=" << cap.LogicalMin
|
|
<< ", LogicalMax=" << cap.LogicalMax
|
|
<< ", BitSize=" << cap.BitSize << "\n";
|
|
}
|
|
} else {
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Found value: DataIndex=" << dec << cap.NotRange.DataIndex
|
|
<< ", ReportID=" << (int)cap.ReportID
|
|
<< ", UsagePage=0x" << hex << cap.UsagePage
|
|
<< ", Usage=0x" << cap.NotRange.Usage
|
|
<< dec << ", LogicalMin=" << cap.LogicalMin
|
|
<< ", LogicalMax=" << cap.LogicalMax
|
|
<< ", BitSize=" << cap.BitSize << "\n";
|
|
}
|
|
}
|
|
|
|
nassertd(cap.Range.DataIndexMin + upper < (int)_indices.size()) continue;
|
|
|
|
for (int j = 0; j <= upper; ++j) {
|
|
USAGE usage = j + cap.Range.UsageMin;
|
|
USHORT data_index = j + cap.Range.DataIndexMin;
|
|
bool is_signed = true;
|
|
|
|
// My gamepads give this odd invalid range.
|
|
if (cap.LogicalMin == 0 && cap.LogicalMax == -1) {
|
|
cap.LogicalMax = (1 << cap.BitSize) - 1;
|
|
is_signed = false;
|
|
}
|
|
|
|
Axis axis = Axis::none;
|
|
switch (cap.UsagePage) {
|
|
case HID_USAGE_PAGE_GENERIC:
|
|
switch (usage) {
|
|
case HID_USAGE_GENERIC_X:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
axis = Axis::left_x;
|
|
} else if (_device_class == DeviceClass::flight_stick) {
|
|
axis = Axis::roll;
|
|
} else {
|
|
axis = Axis::x;
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_Y:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
axis = Axis::left_y;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
} else if (_device_class == DeviceClass::flight_stick) {
|
|
axis = Axis::pitch;
|
|
} else {
|
|
axis = Axis::y;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_Z:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
if (quirks & QB_rstick_from_z) {
|
|
if (quirks & QB_right_axes_swapped) {
|
|
axis = InputDevice::Axis::right_y;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
} else {
|
|
axis = InputDevice::Axis::right_x;
|
|
}
|
|
} else if ((quirks & QB_no_analog_triggers) == 0) {
|
|
axis = Axis::left_trigger;
|
|
}
|
|
} else if (_device_class == DeviceClass::flight_stick) {
|
|
axis = Axis::throttle;
|
|
if ((quirks & QB_reversed_throttle) != 0) {
|
|
std::swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
if ((quirks & QB_centered_throttle) != 0) {
|
|
is_signed = false;
|
|
}
|
|
} else {
|
|
axis = Axis::z;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_RX:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
if (quirks & QB_rstick_from_z) {
|
|
if ((quirks & QB_no_analog_triggers) == 0) {
|
|
axis = Axis::left_trigger;
|
|
}
|
|
} else {
|
|
axis = Axis::right_x;
|
|
}
|
|
} else {
|
|
axis = Axis::pitch;
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_RY:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
if (quirks & QB_rstick_from_z) {
|
|
if ((quirks & QB_no_analog_triggers) == 0) {
|
|
axis = Axis::right_trigger;
|
|
}
|
|
} else {
|
|
axis = Axis::right_y;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
} else {
|
|
axis = Axis::roll;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_RZ:
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
if (quirks & QB_rstick_from_z) {
|
|
if (quirks & QB_right_axes_swapped) {
|
|
axis = InputDevice::Axis::right_x;
|
|
} else {
|
|
axis = InputDevice::Axis::right_y;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
} else if ((quirks & QB_no_analog_triggers) == 0) {
|
|
axis = Axis::right_trigger;
|
|
}
|
|
} else {
|
|
// Flip to match Panda's convention for heading.
|
|
axis = Axis::yaw;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
break;
|
|
case HID_USAGE_GENERIC_SLIDER:
|
|
// Flip to match Panda's convention for heading.
|
|
axis = Axis::rudder;
|
|
swap(cap.LogicalMin, cap.LogicalMax);
|
|
break;
|
|
case HID_USAGE_GENERIC_WHEEL:
|
|
axis = Axis::wheel;
|
|
break;
|
|
case HID_USAGE_GENERIC_HATSWITCH:
|
|
// This is handled specially.
|
|
_hat_data_index = data_index;
|
|
_hat_data_minimum = cap.LogicalMin;
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
case HID_USAGE_PAGE_DIGITIZER:
|
|
switch (usage) {
|
|
case 0x30:
|
|
axis = Axis::pressure;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// If this axis already exists, don't double-map it, but take the first
|
|
// one. This is important for the Trust GXT 24 / SL-6535-SBK-01 which
|
|
// have a weird extra Z axis with DataIndex 2 that should be ignored.
|
|
for (size_t i = 0; i < _axes.size(); ++i) {
|
|
if (_axes[i].axis == axis) {
|
|
axis = Axis::none;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int sign_bit = 0;
|
|
if (cap.BitSize < 32) {
|
|
if (cap.LogicalMin < 0) {
|
|
sign_bit = 1 << (cap.BitSize - 1);
|
|
}
|
|
else if (is_signed) {
|
|
//XXX is this still necessary?
|
|
sign_bit = (1 << 15);
|
|
}
|
|
}
|
|
|
|
int axis_index;
|
|
if (!is_signed) {
|
|
// All axes on the weird XInput-style mappings go from -1 to 1
|
|
axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax, true);
|
|
} else {
|
|
axis_index = add_axis(axis, cap.LogicalMin, cap.LogicalMax);
|
|
}
|
|
_indices[data_index] = Index::axis(axis_index, sign_bit);
|
|
}
|
|
}
|
|
|
|
// Do we need to emulate a hat switch or directional pad?
|
|
if (_hat_data_index != -1) {
|
|
_hat_left_button = (int)_buttons.size();
|
|
if (_device_class == DeviceClass::gamepad) {
|
|
_buttons.push_back(ButtonState(GamepadButton::dpad_left()));
|
|
_buttons.push_back(ButtonState(GamepadButton::dpad_right()));
|
|
_buttons.push_back(ButtonState(GamepadButton::dpad_down()));
|
|
_buttons.push_back(ButtonState(GamepadButton::dpad_up()));
|
|
} else {
|
|
_buttons.push_back(ButtonState(GamepadButton::hat_left()));
|
|
_buttons.push_back(ButtonState(GamepadButton::hat_right()));
|
|
_buttons.push_back(ButtonState(GamepadButton::hat_down()));
|
|
_buttons.push_back(ButtonState(GamepadButton::hat_up()));
|
|
}
|
|
}
|
|
|
|
_max_data_count = _HidP_MaxDataListLength(HidP_Input, buffer);
|
|
|
|
nassertr_always(_max_data_count >= 0, false);
|
|
|
|
_handle = handle;
|
|
_is_connected = true;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Called by InputDeviceManager when this device is removed.
|
|
*/
|
|
void WinRawInputDevice::
|
|
on_removal() {
|
|
LightMutexHolder holder(_lock);
|
|
_is_connected = false;
|
|
_handle = nullptr;
|
|
if (_preparsed != nullptr) {
|
|
free(_preparsed);
|
|
_preparsed = nullptr;
|
|
}
|
|
_indices.clear();
|
|
_report_buttons.clear();
|
|
}
|
|
|
|
/**
|
|
* Called by InputDeviceManager when raw input is received for this device.
|
|
*/
|
|
void WinRawInputDevice::
|
|
on_input(PRAWINPUT input) {
|
|
nassertv(input != nullptr);
|
|
nassertv(_preparsed != nullptr);
|
|
|
|
if (_max_data_count == 0) {
|
|
return;
|
|
}
|
|
|
|
BYTE *ptr = input->data.hid.bRawData;
|
|
if (input->data.hid.dwSizeHid == 0) {
|
|
return;
|
|
}
|
|
|
|
LightMutexHolder holder(_lock);
|
|
|
|
if (device_cat.is_spam()) {
|
|
device_cat.spam()
|
|
<< _name << " received " << input->data.hid.dwCount << " reports of size "
|
|
<< input->data.hid.dwSizeHid << "\n";
|
|
}
|
|
|
|
for (DWORD i = 0; i < input->data.hid.dwCount; ++i) {
|
|
process_report((PCHAR)ptr, input->data.hid.dwSizeHid);
|
|
ptr += input->data.hid.dwSizeHid;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes a single HID report. Assumes the lock is held.
|
|
*/
|
|
void WinRawInputDevice::
|
|
process_report(PCHAR ptr, size_t size) {
|
|
// The first byte is the report identifier. We need it to figure out which
|
|
// buttons are off, since each report only contains the "on" buttons.
|
|
UCHAR report_id = ptr[0];
|
|
BitArray unset_buttons;
|
|
|
|
if (report_id < _report_buttons.size()) {
|
|
unset_buttons = _report_buttons[report_id];
|
|
}
|
|
|
|
PHIDP_DATA data = (PHIDP_DATA)alloca(sizeof(HIDP_DATA) * _max_data_count);
|
|
nassertv(data != nullptr);
|
|
|
|
ULONG count = _max_data_count;
|
|
NTSTATUS status = _HidP_GetData(HidP_Input, data, &count, (PHIDP_PREPARSED_DATA)_preparsed, ptr, size);
|
|
if (status == HIDP_STATUS_SUCCESS) {
|
|
for (ULONG di = 0; di < count; ++di) {
|
|
if (data[di].DataIndex != _hat_data_index) {
|
|
if (device_cat.is_spam()) {
|
|
device_cat.spam()
|
|
<< "Read RawValue " << data[di].RawValue
|
|
<< " for DataIndex " << data[di].DataIndex
|
|
<< " from raw device " << _path << "\n";
|
|
}
|
|
|
|
if (data[di].DataIndex >= _indices.size()) {
|
|
if (device_cat.is_debug()) {
|
|
device_cat.debug()
|
|
<< "Ignoring out of range DataIndex " << data[di].DataIndex
|
|
<< "from raw device " << _path << "\n";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const Index &idx = _indices[data[di].DataIndex];
|
|
if (idx._axis >= 0) {
|
|
if (idx._sign_bit != 0) {
|
|
// Sign extend.
|
|
int value = data[di].RawValue;
|
|
if (value & idx._sign_bit) {
|
|
value -= (idx._sign_bit << 1);
|
|
}
|
|
axis_changed(idx._axis, value);
|
|
} else {
|
|
axis_changed(idx._axis, data[di].RawValue);
|
|
}
|
|
}
|
|
if (idx._button >= 0) {
|
|
unset_buttons.clear_bit(idx._button);
|
|
button_changed(idx._button, (data[di].On != FALSE));
|
|
}
|
|
} else {
|
|
int value = (int)data[di].RawValue - _hat_data_minimum;
|
|
button_changed(_hat_left_button + 0, value >= 5 && value <= 7); // left
|
|
button_changed(_hat_left_button + 1, value >= 1 && value <= 3); // right
|
|
button_changed(_hat_left_button + 2, value >= 3 && value <= 5); // down
|
|
button_changed(_hat_left_button + 3, value == 7 || value == 0 || value == 1); // up
|
|
}
|
|
}
|
|
|
|
// Now unset the buttons in this report that aren't pressed.
|
|
int button_index = unset_buttons.get_lowest_on_bit();
|
|
while (button_index >= 0) {
|
|
button_changed(button_index, false);
|
|
unset_buttons.clear_bit(button_index);
|
|
button_index = unset_buttons.get_lowest_on_bit();
|
|
}
|
|
} else if (device_cat.is_spam()) {
|
|
device_cat.spam()
|
|
<< "Failed to get data from raw device " << _path
|
|
<< " (error 0x" << std::hex << (status & 0xffffffffu) << std::dec << ")\n";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Polls the input device for new activity, to ensure it contains the latest
|
|
* events. This will only have any effect for some types of input devices;
|
|
* others may be updated automatically, and this method will be a no-op.
|
|
*/
|
|
void WinRawInputDevice::
|
|
do_poll() {
|
|
}
|
|
|
|
#endif // _WIN32
|