Fix Xbox One controller on Windows, add untested support for flight sticks and steering wheels

This commit is contained in:
rdb 2016-06-24 13:30:57 +02:00
parent 01e57e1bdd
commit fd16e34660
5 changed files with 225 additions and 55 deletions

View File

@ -23,11 +23,54 @@
#include <fcntl.h>
#include <linux/input.h>
#define test_bit(bit, array) ((array)[(bit)/8] & (1<<((bit)&7)))
#define test_bit(bit, array) ((array)[(bit)>>3] & (1<<((bit)&7)))
static InputDevice::ControlAxis axis_map[] = {
InputDevice::C_left_x, InputDevice::C_left_y, InputDevice::C_left_trigger,
InputDevice::C_right_x, InputDevice::C_right_y, InputDevice::C_right_trigger
// ABS_X = 0x00
InputDevice::C_left_x,
// ABS_Y = 0x01
InputDevice::C_left_y,
// ABS_Z = 0x02
InputDevice::C_left_trigger,
// ABS_RX = 0x03
InputDevice::C_right_x,
// ABS_RY = 0x04
InputDevice::C_right_y,
// ABS_RZ = 0x05
InputDevice::C_right_trigger,
// ABS_THROTTLE = 0x06
InputDevice::C_throttle,
// ABS_RUDDER = 0x07
InputDevice::C_rudder,
// ABS_WHEEL = 0x08
InputDevice::C_wheel,
// ABS_GAS = 0x09
InputDevice::C_accelerator,
// ABS_BRAKE = 0x0a
InputDevice::C_brake,
InputDevice::C_none,
InputDevice::C_none,
InputDevice::C_none,
InputDevice::C_none,
InputDevice::C_none,
// ABS_HAT0X = 0x10
InputDevice::C_hat_x,
// ABS_HAT0Y = 0x11
InputDevice::C_hat_y,
// ABS_HAT1X = 0x12
// ABS_HAT1Y = 0x13
// ABS_HAT2X = 0x14
// ABS_HAT2Y = 0x15
// ABS_HAT3X = 0x16
// ABS_HAT3Y = 0x17
// ABS_PRESSURE = 0x18
// ABS_DISTANCE = 0x19
// ABS_TILT_X = 0x1a
// ABS_TILT_Y = 0x1b
// ABS_TOOL_WIDTH = 0x1c
};
TypeHandle EvdevInputDevice::_type_handle;
@ -189,7 +232,7 @@ init_device() {
}
bool all_values_zero = true;
bool have_dpad_buttons = false;
bool emulate_dpad = true;
if (test_bit(EV_KEY, evtypes)) {
// Check which buttons are on the device.
@ -213,7 +256,7 @@ init_device() {
_buttons[bi]._state = S_up;
}
if (_buttons[bi]._handle == GamepadButton::dpad_left()) {
have_dpad_buttons = true;
emulate_dpad = false;
}
++bi;
}
@ -241,6 +284,10 @@ init_device() {
}
}
if (_device_class != DC_gamepad) {
emulate_dpad = false;
}
if (test_bit(EV_ABS, evtypes)) {
// Check which axes are on the device.
uint8_t axes[(ABS_CNT + 7) >> 3];
@ -254,7 +301,25 @@ init_device() {
int num_bits = ioctl(_fd, EVIOCGBIT(EV_ABS, sizeof(axes)), axes) << 3;
for (int i = 0; i < num_bits; ++i) {
if (test_bit(i, axes)) {
set_control_map(i, axis_map[i]);
// Emulate D-Pad buttons if necessary.
if (i > ABS_HAT0Y) {
set_control_map(i, C_none);
} else if (i == ABS_HAT0X && emulate_dpad) {
_dpad_x_axis = i;
_dpad_left_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_left()));
_buttons.push_back(ButtonState(GamepadButton::dpad_right()));
} else if (i == ABS_HAT0Y && emulate_dpad) {
_dpad_y_axis = i;
_dpad_up_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down()));
} else {
set_control_map(i, axis_map[i]);
}
// Check the initial value and ranges.
struct input_absinfo absinfo;
@ -284,20 +349,6 @@ init_device() {
all_values_zero = false;
}
}
// Emulate D-Pad buttons if necessary.
if (i == ABS_HAT0X && !have_dpad_buttons) {
_dpad_x_axis = i;
_dpad_left_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_left()));
_buttons.push_back(ButtonState(GamepadButton::dpad_right()));
}
if (i == ABS_HAT0Y && !have_dpad_buttons) {
_dpad_y_axis = i;
_dpad_up_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down()));
}
}
}
}

View File

@ -170,9 +170,25 @@ set_button_state(int index, bool down) {
_buttons.resize(index + 1, ButtonState());
}
_buttons[index]._state = down ? S_down : S_up;
State new_state = down ? S_down : S_up;
if (_buttons[index]._state == new_state) {
return;
}
_buttons[index]._state = new_state;
ButtonHandle handle = _buttons[index]._handle;
if (device_cat.is_spam()) {
device_cat.spam()
<< "Changed button " << index;
if (handle != ButtonHandle::none()) {
device_cat.spam(false) << " (" << handle << ")";
}
device_cat.spam(false) << " to " << (down ? "down" : "up") << "\n";
}
if (handle != ButtonHandle::none()) {
_button_events->add_event(ButtonEvent(handle, down ? ButtonEvent::T_down : ButtonEvent::T_up));
}
@ -192,7 +208,7 @@ set_control_state(int index, double state) {
_controls.resize(index + 1, AnalogState());
}
if (device_cat.is_spam()) {
if (device_cat.is_spam() && _controls[index]._state != state) {
device_cat.spam()
<< "Changed control " << index;

View File

@ -73,6 +73,10 @@ PUBLISHED:
DC_flight_stick,
DC_steering_wheel,
DC_dance_pad,
// Head-mounted display.
DC_hmd,
};
protected:
@ -101,6 +105,14 @@ PUBLISHED:
C_y,
C_trigger,
C_throttle,
C_rudder,
C_hat_x,
C_hat_y,
// Steering wheel / pedals
C_wheel,
C_accelerator,
C_brake,
};
INLINE string get_name() const;

View File

@ -257,26 +257,50 @@ open_device() {
axis = C_right_trigger;
break;
case ABS_THROTTLE:
axis = InputDevice::C_throttle;
break;
case ABS_RUDDER:
axis = InputDevice::C_rudder;
break;
case ABS_WHEEL:
axis = InputDevice::C_wheel;
break;
case ABS_GAS:
axis = InputDevice::C_accelerator;
break;
case ABS_BRAKE:
axis = InputDevice::C_brake;
break;
case ABS_HAT0X:
if (_dpad_left_button == -1) {
if (_dpad_left_button == -1 && _device_class == DC_gamepad) {
// Emulate D-Pad.
_dpad_x_axis = i;
_dpad_left_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_left()));
_buttons.push_back(ButtonState(GamepadButton::dpad_right()));
axis = C_none;
} else {
axis = C_hat_x;
}
axis = C_none;
break;
case ABS_HAT0Y:
if (_dpad_up_button == -1) {
if (_dpad_up_button == -1 && _device_class == DC_gamepad) {
// Emulate D-Pad.
_dpad_y_axis = i;
_dpad_up_button = (int)_buttons.size();
_buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down()));
axis = C_none;
} else {
axis = C_hat_y;
}
axis = C_none;
break;
default:

View File

@ -28,11 +28,33 @@
#ifndef XINPUT_CAPS_FFB_SUPPORTED
#define XINPUT_CAPS_FFB_SUPPORTED 0x0001
#endif
#ifndef XINPUT_CAPS_NO_NAVIGATION
#define XINPUT_CAPS_NO_NAVIGATION 0x0010
#endif
#ifndef BATTERY_DEVTYPE_GAMEPAD
#define BATTERY_DEVTYPE_GAMEPAD 0x00
#endif
#ifndef XINPUT_DEVSUBTYPE_WHEEL
#define XINPUT_DEVSUBTYPE_WHEEL 0x02
#endif
#ifndef XINPUT_DEVSUBTYPE_ARCADE_STICK
#define XINPUT_DEVSUBTYPE_ARCADE_STICK 0x03
#endif
#ifndef XINPUT_DEVSUBTYPE_FLIGHT_STICK
#define XINPUT_DEVSUBTYPE_FLIGHT_STICK 0x04
#endif
#ifndef XINPUT_DEVSUBTYPE_DANCE_PAD
#define XINPUT_DEVSUBTYPE_DANCE_PAD 0x05
#endif
#ifndef XINPUT_DEVSUBTYPE_GUITAR
#define XINPUT_DEVSUBTYPE_GUITAR 0x06
#endif
#ifndef XINPUT_DEVSUBTYPE_DRUM_KIT
#define XINPUT_DEVSUBTYPE_DRUM_KIT 0x08
#endif
#ifndef BATTERY_TYPE_DISCONNECTED
#define BATTERY_TYPE_DISCONNECTED 0x00
#endif
@ -96,29 +118,6 @@ XInputDevice(DWORD user_index) :
_controls.resize(6);
_buttons.resize(16);
set_control_map(0, C_left_trigger);
set_control_map(1, C_right_trigger);
set_control_map(2, C_left_x);
set_control_map(3, C_left_y);
set_control_map(4, C_right_x);
set_control_map(5, C_right_y);
set_button_map(0, GamepadButton::dpad_up());
set_button_map(1, GamepadButton::dpad_down());
set_button_map(2, GamepadButton::dpad_left());
set_button_map(3, GamepadButton::dpad_right());
set_button_map(4, GamepadButton::start());
set_button_map(5, GamepadButton::back());
set_button_map(6, GamepadButton::lstick());
set_button_map(7, GamepadButton::rstick());
set_button_map(8, GamepadButton::lshoulder());
set_button_map(9, GamepadButton::rshoulder());
set_button_map(10, GamepadButton::guide());
set_button_map(11, GamepadButton::action_a());
set_button_map(12, GamepadButton::action_b());
set_button_map(13, GamepadButton::action_x());
set_button_map(14, GamepadButton::action_y());
// Check if the device is connected. If so, initialize it.
XINPUT_CAPABILITIES caps;
XINPUT_STATE state;
@ -242,19 +241,87 @@ init_xinput() {
*/
void XInputDevice::
init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
// It seems that the Xbox One controller is reported as having a DevType of
// zero, at least when I tested in with XInput 1.3 on Windows 7.
//if (caps.Type == XINPUT_DEVTYPE_GAMEPAD) {
// For subtypes and mappings, see this page:
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh405050.aspx
switch (caps.SubType) {
default:
case XINPUT_DEVSUBTYPE_GAMEPAD:
_device_class = DC_gamepad;
} else {
_device_class = DC_unknown;
set_control_map(0, C_left_trigger);
set_control_map(1, C_right_trigger);
set_control_map(2, C_left_x);
set_control_map(3, C_left_y);
set_control_map(4, C_right_x);
set_control_map(5, C_right_y);
break;
case XINPUT_DEVSUBTYPE_WHEEL:
_device_class = DC_steering_wheel;
set_control_map(0, C_brake);
set_control_map(1, C_accelerator);
set_control_map(2, C_wheel);
set_control_map(3, C_none);
set_control_map(4, C_none);
set_control_map(5, C_none);
break;
case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
_device_class = DC_flight_stick;
set_control_map(0, C_rudder);
set_control_map(1, C_throttle);
set_control_map(2, C_x);
set_control_map(3, C_y);
set_control_map(4, C_hat_x);
set_control_map(5, C_hat_y);
break;
case XINPUT_DEVSUBTYPE_DANCE_PAD:
_device_class = DC_dance_pad;
set_control_map(0, C_none);
set_control_map(1, C_none);
set_control_map(2, C_none);
set_control_map(3, C_none);
set_control_map(4, C_none);
set_control_map(5, C_none);
break;
}
if (caps.Flags & XINPUT_CAPS_NO_NAVIGATION) {
set_button_map(0, ButtonHandle::none());
set_button_map(1, ButtonHandle::none());
set_button_map(2, ButtonHandle::none());
set_button_map(3, ButtonHandle::none());
set_button_map(4, ButtonHandle::none());
set_button_map(5, ButtonHandle::none());
} else {
set_button_map(0, GamepadButton::dpad_up());
set_button_map(1, GamepadButton::dpad_down());
set_button_map(2, GamepadButton::dpad_left());
set_button_map(3, GamepadButton::dpad_right());
set_button_map(4, GamepadButton::start());
set_button_map(5, GamepadButton::back());
}
set_button_map(6, GamepadButton::lstick());
set_button_map(7, GamepadButton::rstick());
set_button_map(8, GamepadButton::lshoulder());
set_button_map(9, GamepadButton::rshoulder());
set_button_map(10, GamepadButton::guide());
set_button_map(11, GamepadButton::action_a());
set_button_map(12, GamepadButton::action_b());
set_button_map(13, GamepadButton::action_x());
set_button_map(14, GamepadButton::action_y());
if (caps.Flags & XINPUT_CAPS_FFB_SUPPORTED) {
_flags |= IDF_has_vibration;
}
if (get_battery_information != NULL) {
XINPUT_BATTERY_INFORMATION batt;
if (get_battery_information(_index, 0, &batt) == ERROR_SUCCESS) {
if (get_battery_information(_index, BATTERY_DEVTYPE_GAMEPAD, &batt) == ERROR_SUCCESS) {
if (batt.BatteryType != BATTERY_TYPE_DISCONNECTED &&
batt.BatteryType != BATTERY_TYPE_WIRED) {
// This device has a battery. Report the battery level.
@ -276,7 +343,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
{
// Reformat the serial number into its original hex string form.
char sn[10];
sprintf(sn, "%08X", businfo.InstanceID);
sprintf_s(sn, 10, "%08X", businfo.InstanceID);
_serial_number.assign(sn, 8);
}
@ -284,7 +351,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
// first need to construct the device path. Fortunately, we now have
// enough information to do so.
char path[32];
sprintf(path, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID);
sprintf_s(path, 32, "USB\\VID_%04X&PID_%04X\\%08X", businfo.VendorID, businfo.ProductID, businfo.InstanceID);
DEVINST inst;
if (CM_Locate_DevNodeA(&inst, path, 0) != 0) {