Emulate d-pad from hat axes, fixes Xbox One d-pad buttons on Linux

This commit is contained in:
rdb 2016-06-24 10:54:03 +02:00
parent b98d3c06ee
commit 01e57e1bdd
6 changed files with 147 additions and 52 deletions

View File

@ -43,7 +43,11 @@ EvdevInputDevice(int index) :
_ff_id(-1),
_ff_playing(false),
_ff_strong(-1),
_ff_weak(-1) {
_ff_weak(-1),
_dpad_x_axis(-1),
_dpad_y_axis(-1),
_dpad_left_button(-1),
_dpad_up_button(-1) {
char path[64];
sprintf(path, "/dev/input/event%d", index);
@ -185,54 +189,7 @@ init_device() {
}
bool all_values_zero = true;
if (test_bit(EV_ABS, evtypes)) {
// Check which axes are on the device.
uint8_t axes[(ABS_CNT + 7) >> 3];
memset(axes, 0, sizeof(axes));
AxisRange range;
range._scale = 1.0;
range._bias = 0.0;
_axis_ranges.resize(ABS_CNT, range);
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)) {
if (i >= 0 && i < 6) {
set_control_map(i, axis_map[i]);
// Check the initial value and ranges.
struct input_absinfo absinfo;
if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) {
double factor, bias;
if (absinfo.minimum < 0) {
// Centered, eg. for sticks.
factor = 2.0 / (absinfo.maximum - absinfo.minimum);
bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum);
} else {
// 0-based, eg. for triggers.
factor = 1.0 / absinfo.maximum;
bias = 0.0;
}
// Flip Y axis to match Windows implementation.
if (i == ABS_Y || i == ABS_RY) {
factor = -factor;
bias = -bias;
}
_axis_ranges[i]._scale = factor;
_axis_ranges[i]._bias = bias;
_controls[i]._state = fma(absinfo.value, factor, bias);
if (absinfo.value != 0) {
all_values_zero = false;
}
}
}
}
}
}
bool have_dpad_buttons = false;
if (test_bit(EV_KEY, evtypes)) {
// Check which buttons are on the device.
@ -255,6 +212,9 @@ init_device() {
} else {
_buttons[bi]._state = S_up;
}
if (_buttons[bi]._handle == GamepadButton::dpad_left()) {
have_dpad_buttons = true;
}
++bi;
}
}
@ -281,6 +241,67 @@ init_device() {
}
}
if (test_bit(EV_ABS, evtypes)) {
// Check which axes are on the device.
uint8_t axes[(ABS_CNT + 7) >> 3];
memset(axes, 0, sizeof(axes));
AxisRange range;
range._scale = 1.0;
range._bias = 0.0;
_axis_ranges.resize(ABS_CNT, range);
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]);
// Check the initial value and ranges.
struct input_absinfo absinfo;
if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) {
double factor, bias;
if (absinfo.minimum < 0) {
// Centered, eg. for sticks.
factor = 2.0 / (absinfo.maximum - absinfo.minimum);
bias = (absinfo.maximum + absinfo.minimum) / (double)(absinfo.minimum - absinfo.maximum);
} else {
// 0-based, eg. for triggers.
factor = 1.0 / absinfo.maximum;
bias = 0.0;
}
// Flip Y axis to match Windows implementation.
if (i == ABS_Y || i == ABS_RY) {
factor = -factor;
bias = -bias;
}
_axis_ranges[i]._scale = factor;
_axis_ranges[i]._bias = bias;
_controls[i]._state = fma(absinfo.value, factor, bias);
if (absinfo.value != 0) {
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()));
}
}
}
}
if (test_bit(EV_REL, evtypes)) {
_flags |= IDF_has_pointer;
}
@ -403,6 +424,13 @@ process_events() {
break;
case EV_ABS:
if (code == _dpad_x_axis) {
set_button_state(_dpad_left_button, events[i].value < 0);
set_button_state(_dpad_left_button+1, events[i].value > 0);
} else if (code == _dpad_y_axis) {
set_button_state(_dpad_up_button, events[i].value < 0);
set_button_state(_dpad_up_button+1, events[i].value > 0);
}
set_control_state(code, fma(events[i].value, _axis_ranges[code]._scale, _axis_ranges[code]._bias));
break;

View File

@ -50,6 +50,12 @@ private:
};
pvector<AxisRange> _axis_ranges;
// These are used for D-pad emulation.
int _dpad_x_axis;
int _dpad_y_axis;
int _dpad_left_button;
int _dpad_up_button;
public:
static ButtonHandle map_button(int code);

View File

@ -382,6 +382,16 @@ ButtonState() :
{
}
/**
*
*/
INLINE InputDevice::ButtonState::
ButtonState(ButtonHandle handle) :
_handle(handle),
_state(S_unknown)
{
}
/**
*
*/

View File

@ -238,6 +238,7 @@ public:
class ButtonState {
public:
INLINE ButtonState();
INLINE ButtonState(ButtonHandle handle);
ButtonHandle _handle;
State _state;

View File

@ -28,7 +28,11 @@ TypeHandle LinuxJoystickDevice::_type_handle;
LinuxJoystickDevice::
LinuxJoystickDevice(int index) :
_fd(-1),
_index(index)
_index(index),
_dpad_x_axis(-1),
_dpad_y_axis(-1),
_dpad_left_button(-1),
_dpad_up_button(-1)
{
LightMutexHolder holder(_lock);
if (!open_device()) {
@ -115,7 +119,7 @@ open_device() {
uint16_t btnmap[512];
ioctl(_fd, JSIOCGBTNMAP, btnmap);
for (char i = 0; i < num_buttons; ++i) {
for (uint8_t i = 0; i < num_buttons; ++i) {
ButtonHandle handle = ButtonHandle::none();
switch (btnmap[i]) {
case BTN_A:
@ -181,6 +185,7 @@ open_device() {
case BTN_TRIGGER_HAPPY1:
handle = GamepadButton::dpad_left();
_dpad_left_button = i;
break;
case BTN_TRIGGER_HAPPY2:
@ -189,6 +194,7 @@ open_device() {
case BTN_TRIGGER_HAPPY3:
handle = GamepadButton::dpad_up();
_dpad_up_button = i;
break;
case BTN_TRIGGER_HAPPY4:
@ -196,6 +202,10 @@ open_device() {
break;
default:
if (device_cat.is_debug()) {
device_cat.debug() << "Unmapped /dev/input/js" << _index
<< " button " << (int)i << ": 0x" << hex << btnmap[i] << "\n";
}
handle = ButtonHandle::none();
break;
}
@ -207,7 +217,7 @@ open_device() {
uint8_t axmap[512];
ioctl(_fd, JSIOCGAXMAP, axmap);
for (char i = 0; i < num_axes; ++i) {
for (uint8_t i = 0; i < num_axes; ++i) {
ControlAxis axis = C_none;
switch (axmap[i]) {
@ -247,7 +257,33 @@ open_device() {
axis = C_right_trigger;
break;
case ABS_HAT0X:
if (_dpad_left_button == -1) {
// 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;
break;
case ABS_HAT0Y:
if (_dpad_up_button == -1) {
// 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;
break;
default:
if (device_cat.is_debug()) {
device_cat.debug() << "Unmapped /dev/input/js" << _index
<< " axis " << (int)i << ": 0x" << hex << (int)axmap[i] << "\n";
}
axis = C_none;
break;
}
@ -362,6 +398,14 @@ process_events() {
set_button_state(index, (events[i].value != 0));
} else if (events[i].type & JS_EVENT_AXIS) {
if (index == _dpad_x_axis) {
set_button_state(_dpad_left_button, events[i].value < -1000);
set_button_state(_dpad_left_button+1, events[i].value > 1000);
} else if (index == _dpad_y_axis) {
set_button_state(_dpad_up_button, events[i].value < -1000);
set_button_state(_dpad_up_button+1, events[i].value > 1000);
}
ControlAxis axis = _controls[index]._axis;
if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) {

View File

@ -39,6 +39,12 @@ private:
int _fd;
int _index;
// These are used for D-pad emulation.
int _dpad_x_axis;
int _dpad_y_axis;
int _dpad_left_button;
int _dpad_up_button;
public:
static TypeHandle get_class_type() {
return _type_handle;