input: joystick buttons, use yaw/pitch/roll for axes, more devices

Also adds format_* functions to get string from enum value.
This commit is contained in:
rdb 2018-01-07 14:50:54 +01:00
parent 27bd7ae4ac
commit 4f739e88cf
9 changed files with 256 additions and 148 deletions

View File

@ -37,6 +37,9 @@ enum QuirkBits {
// Only consider the device "connected" if all axes are non-zero. // Only consider the device "connected" if all axes are non-zero.
QB_connect_if_nonzero = 8, QB_connect_if_nonzero = 8,
// ABS_THROTTLE maps to rudder
QB_rudder_from_throttle = 16,
}; };
static const struct DeviceMapping { static const struct DeviceMapping {
@ -48,9 +51,25 @@ static const struct DeviceMapping {
// NVIDIA Shield Controller // NVIDIA Shield Controller
{0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z}, {0x0955, 0x7214, InputDevice::DC_gamepad, QB_rstick_from_z},
// T.Flight Hotas X // T.Flight Hotas X
{0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle}, {0x044f, 0xb108, InputDevice::DC_flight_stick, QB_centered_throttle | QB_reversed_throttle | QB_rudder_from_throttle},
// Xbox 360 Wireless Controller // Xbox 360 Wireless Controller
{0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero}, {0x045e, 0x0719, InputDevice::DC_gamepad, QB_connect_if_nonzero},
// Jess Tech Colour Rumble Pad
{0x0f30, 0x0111, InputDevice::DC_gamepad, 0},
// 3Dconnexion Space Traveller 3D Mouse
{0x046d, 0xc623, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion Space Pilot 3D Mouse
{0x046d, 0xc625, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion Space Navigator 3D Mouse
{0x046d, 0xc626, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion Space Explorer 3D Mouse
{0x046d, 0xc627, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion Space Navigator for Notebooks
{0x046d, 0xc628, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion SpacePilot Pro 3D Mouse
{0x046d, 0xc629, InputDevice::DC_3d_mouse, 0},
// 3Dconnexion Space Mouse Pro
{0x046d, 0xc62b, InputDevice::DC_3d_mouse, 0},
{0}, {0},
}; };
@ -329,10 +348,6 @@ init_device() {
//cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n"; //cerr << "Found highscore class " << _device_class << " with this score: " << highest_score << "\n";
} }
if (_device_class != DC_gamepad) {
emulate_dpad = false;
}
if (has_keys) { if (has_keys) {
// Also check whether the buttons are currently pressed. // Also check whether the buttons are currently pressed.
uint8_t states[(KEY_CNT + 7) >> 3]; uint8_t states[(KEY_CNT + 7) >> 3];
@ -342,7 +357,7 @@ init_device() {
for (int i = 0; i < KEY_CNT; ++i) { for (int i = 0; i < KEY_CNT; ++i) {
if (test_bit(i, keys)) { if (test_bit(i, keys)) {
ButtonState button; ButtonState button;
button.handle = map_button(i); button.handle = map_button(i, _device_class);
int button_index = (int)_buttons.size(); int button_index = (int)_buttons.size();
if (button.handle == ButtonHandle::none()) { if (button.handle == ButtonHandle::none()) {
@ -378,10 +393,22 @@ init_device() {
ControlAxis axis = C_none; ControlAxis axis = C_none;
switch (i) { switch (i) {
case ABS_X: case ABS_X:
if (_device_class == DC_gamepad) {
axis = InputDevice::C_left_x; axis = InputDevice::C_left_x;
} else if (_device_class == DC_flight_stick) {
axis = InputDevice::C_roll;
} else {
axis = InputDevice::C_x;
}
break; break;
case ABS_Y: case ABS_Y:
if (_device_class == DC_gamepad) {
axis = InputDevice::C_left_y; axis = InputDevice::C_left_y;
} else if (_device_class == DC_flight_stick) {
axis = InputDevice::C_pitch;
} else {
axis = InputDevice::C_y;
}
break; break;
case ABS_Z: case ABS_Z:
if (quirks & QB_rstick_from_z) { if (quirks & QB_rstick_from_z) {
@ -408,11 +435,15 @@ init_device() {
} else if (_device_class == DC_gamepad) { } else if (_device_class == DC_gamepad) {
axis = InputDevice::C_right_trigger; axis = InputDevice::C_right_trigger;
} else { } else {
axis = InputDevice::C_twist; axis = InputDevice::C_yaw;
} }
break; break;
case ABS_THROTTLE: case ABS_THROTTLE:
if (quirks & QB_rudder_from_throttle) {
axis = InputDevice::C_rudder; axis = InputDevice::C_rudder;
} else {
axis = InputDevice::C_throttle;
}
break; break;
case ABS_RUDDER: case ABS_RUDDER:
axis = InputDevice::C_rudder; axis = InputDevice::C_rudder;
@ -438,20 +469,26 @@ init_device() {
if (emulate_dpad) { if (emulate_dpad) {
_dpad_x_axis = i; _dpad_x_axis = i;
_dpad_left_button = (int)_buttons.size(); _dpad_left_button = (int)_buttons.size();
if (_device_class == DC_gamepad) {
_buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_left()));
_buttons.push_back(ButtonState(GamepadButton::dpad_right())); _buttons.push_back(ButtonState(GamepadButton::dpad_right()));
} else { } else {
axis = C_hat_x; _buttons.push_back(ButtonState(GamepadButton::hat_left()));
_buttons.push_back(ButtonState(GamepadButton::hat_right()));
}
} }
break; break;
case ABS_HAT0Y: case ABS_HAT0Y:
if (emulate_dpad) { if (emulate_dpad) {
_dpad_y_axis = i; _dpad_y_axis = i;
_dpad_up_button = (int)_buttons.size(); _dpad_up_button = (int)_buttons.size();
if (_device_class == DC_gamepad) {
_buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down())); _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
} else { } else {
axis = C_hat_y; _buttons.push_back(ButtonState(GamepadButton::hat_up()));
_buttons.push_back(ButtonState(GamepadButton::hat_down()));
}
} }
break; break;
} }
@ -461,8 +498,9 @@ init_device() {
if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) { if (ioctl(_fd, EVIOCGABS(i), &absinfo) >= 0) {
int index; int index;
// We'd like to reverse the Y axis to match the XInput behavior. // We'd like to reverse the Y axis to match the XInput behavior.
// Also reverse the yaw axis to match right-hand coordinate system.
// Also T.Flight Hotas X throttle is reversed and can go backwards. // Also T.Flight Hotas X throttle is reversed and can go backwards.
if (axis == C_y || axis == C_left_y || axis == C_right_y || if (axis == C_yaw || axis == C_left_y || axis == C_right_y ||
(axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) { (axis == C_throttle && (quirks & QB_reversed_throttle) != 0)) {
swap(absinfo.maximum, absinfo.minimum); swap(absinfo.maximum, absinfo.minimum);
} }
@ -647,7 +685,7 @@ process_events() {
* Static function to map an evdev code to a ButtonHandle. * Static function to map an evdev code to a ButtonHandle.
*/ */
ButtonHandle EvdevInputDevice:: ButtonHandle EvdevInputDevice::
map_button(int code) { map_button(int code, DeviceClass device_class) {
if (code >= 0 && code < 0x80) { if (code >= 0 && code < 0x80) {
// See linux/input.h for the source of this mapping. // See linux/input.h for the source of this mapping.
static const ButtonHandle keyboard_map[] = { static const ButtonHandle keyboard_map[] = {
@ -802,12 +840,33 @@ map_button(int code) {
} else { } else {
return MouseButton::button(code - BTN_MOUSE); return MouseButton::button(code - BTN_MOUSE);
} }
} else if ((code & 0xfff0) == BTN_JOYSTICK) {
if (device_class == DC_gamepad) {
// Based on "Jess Tech Colour Rumble Pad"
static const ButtonHandle mapping[] = {
GamepadButton::action_x(),
GamepadButton::action_y(),
GamepadButton::action_a(),
GamepadButton::action_b(),
GamepadButton::lshoulder(),
GamepadButton::ltrigger(),
GamepadButton::rshoulder(),
GamepadButton::rtrigger(),
GamepadButton::back(),
GamepadButton::start(),
GamepadButton::lstick(),
GamepadButton::rstick(),
};
if ((code & 0xf) < 12) {
return mapping[code & 0xf];
}
} else {
return GamepadButton::joystick(code & 0xf);
}
} }
switch (code) { switch (code) {
case BTN_TRIGGER:
return GamepadButton::trigger();
case BTN_A: case BTN_A:
return GamepadButton::action_a(); return GamepadButton::action_a();

View File

@ -54,7 +54,7 @@ private:
int _dpad_up_button; int _dpad_up_button;
public: public:
static ButtonHandle map_button(int code); static ButtonHandle map_button(int code, DeviceClass device_class = DC_unknown);
public: public:
static TypeHandle get_class_type() { static TypeHandle get_class_type() {

View File

@ -143,14 +143,17 @@ add_control(ControlAxis axis, int minimum, int maximum, bool centered) {
int InputDevice:: int InputDevice::
add_control(ControlAxis axis, int minimum, int maximum) { add_control(ControlAxis axis, int minimum, int maximum) {
bool centered = (minimum < 0) bool centered = (minimum < 0)
|| axis == C_x
|| axis == C_y
|| axis == C_z
|| axis == C_yaw
|| axis == C_pitch
|| axis == C_roll
|| axis == C_left_x || axis == C_left_x
|| axis == C_left_y || axis == C_left_y
|| axis == C_right_x || axis == C_right_x
|| axis == C_right_y || axis == C_right_y
|| axis == C_x
|| axis == C_y
|| axis == C_wheel || axis == C_wheel
|| axis == C_twist
|| axis == C_rudder; || axis == C_rudder;
return add_control(axis, minimum, maximum, centered); return add_control(axis, minimum, maximum, centered);
} }
@ -499,130 +502,125 @@ void InputDevice::
do_poll() { do_poll() {
} }
ostream & /**
operator << (ostream &out, InputDevice::DeviceClass dc) { * Returns a string describing the given device class enumerant.
*/
string InputDevice::
format_device_class(DeviceClass dc) {
switch (dc) { switch (dc) {
case InputDevice::DC_unknown: case InputDevice::DC_unknown:
out << "unknown"; return "unknown";
break;
case InputDevice::DC_virtual: case InputDevice::DC_virtual:
out << "virtual"; return "virtual";
break;
case InputDevice::DC_keyboard: case InputDevice::DC_keyboard:
out << "keyboard"; return "keyboard";
break;
case InputDevice::DC_mouse: case InputDevice::DC_mouse:
out << "mouse"; return "mouse";
break;
case InputDevice::DC_touch: case InputDevice::DC_touch:
out << "touch"; return "touch";
break;
case InputDevice::DC_gamepad: case InputDevice::DC_gamepad:
out << "gamepad"; return "gamepad";
break;
case InputDevice::DC_flight_stick: case InputDevice::DC_flight_stick:
out << "flight_stick"; return "flight_stick";
break;
case InputDevice::DC_steering_wheel: case InputDevice::DC_steering_wheel:
out << "steering_wheel"; return "steering_wheel";
break;
case InputDevice::DC_dance_pad: case InputDevice::DC_dance_pad:
out << "dance_pad"; return "dance_pad";
break;
case InputDevice::DC_hmd: case InputDevice::DC_hmd:
out << "hmd"; return "hmd";
break;
case InputDevice::DC_3d_mouse:
return "3d_mouse";
case InputDevice::DC_COUNT: case InputDevice::DC_COUNT:
break; break;
} }
return "**invalid**";
}
/**
* Returns a string describing the given axis enumerant.
*/
string InputDevice::
format_axis(ControlAxis axis) {
switch (axis) {
case InputDevice::C_none:
return "none";
case InputDevice::C_x:
return "x";
case InputDevice::C_y:
return "y";
case InputDevice::C_z:
return "z";
case InputDevice::C_yaw:
return "yaw";
case InputDevice::C_pitch:
return "pitch";
case InputDevice::C_roll:
return "roll";
case InputDevice::C_left_x:
return "left_x";
case InputDevice::C_left_y:
return "left_y";
case InputDevice::C_left_trigger:
return "left_trigger";
case InputDevice::C_right_x:
return "right_x";
case InputDevice::C_right_y:
return "right_y";
case InputDevice::C_right_trigger:
return "right_trigger";
//case InputDevice::C_trigger:
// return "trigger";
case InputDevice::C_throttle:
return "throttle";
case InputDevice::C_rudder:
return "rudder";
case InputDevice::C_wheel:
return "wheel";
case InputDevice::C_accelerator:
return "accelerator";
case InputDevice::C_brake:
return "brake";
}
return "**invalid**";
}
ostream &
operator << (ostream &out, InputDevice::DeviceClass dc) {
out << InputDevice::format_device_class(dc);
return out; return out;
} }
ostream & ostream &
operator << (ostream &out, InputDevice::ControlAxis axis) { operator << (ostream &out, InputDevice::ControlAxis axis) {
switch (axis) { out << InputDevice::format_axis(axis);
case InputDevice::C_none:
out << "none";
break;
case InputDevice::C_left_x:
out << "left_x";
break;
case InputDevice::C_left_y:
out << "left_y";
break;
case InputDevice::C_left_trigger:
out << "left_trigger";
break;
case InputDevice::C_right_x:
out << "right_x";
break;
case InputDevice::C_right_y:
out << "right_y";
break;
case InputDevice::C_right_trigger:
out << "right_trigger";
break;
case InputDevice::C_x:
out << "x";
break;
case InputDevice::C_y:
out << "y";
break;
case InputDevice::C_trigger:
out << "trigger";
break;
case InputDevice::C_throttle:
out << "throttle";
break;
case InputDevice::C_twist:
out << "twist";
break;
case InputDevice::C_rudder:
out << "rudder";
break;
case InputDevice::C_hat_x:
out << "hat_x";
break;
case InputDevice::C_hat_y:
out << "hat_y";
break;
case InputDevice::C_wheel:
out << "wheel";
break;
case InputDevice::C_accelerator:
out << "accelerator";
break;
case InputDevice::C_brake:
out << "brake";
break;
}
return out; return out;
} }

View File

@ -78,6 +78,9 @@ PUBLISHED:
// Head-mounted display. // Head-mounted display.
DC_hmd, DC_hmd,
// 3D mouse, such as produced by 3Dconnexion.
DC_3d_mouse,
// Count of this enum, used for loops // Count of this enum, used for loops
DC_COUNT, DC_COUNT,
}; };
@ -95,6 +98,16 @@ PUBLISHED:
enum ControlAxis { enum ControlAxis {
C_none, C_none,
// Generic translational axes
C_x,
C_y,
C_z,
// Generic rotational axes, used by joysticks and 3D mice
C_yaw,
C_pitch,
C_roll,
// Gamepad // Gamepad
C_left_x, C_left_x,
C_left_y, C_left_y,
@ -103,15 +116,9 @@ PUBLISHED:
C_right_y, C_right_y,
C_right_trigger, C_right_trigger,
// Flight stick // Flight stick specific
C_x,
C_y,
C_trigger,
C_throttle, C_throttle,
C_twist, C_rudder, // When available separately from yaw
C_rudder,
C_hat_x,
C_hat_y,
// Steering wheel / pedals // Steering wheel / pedals
C_wheel, C_wheel,
@ -183,6 +190,8 @@ PUBLISHED:
PT(PointerEventList) get_pointer_events(); PT(PointerEventList) get_pointer_events();
virtual void output(ostream &out) const; virtual void output(ostream &out) const;
static string format_device_class(DeviceClass dc);
static string format_axis(ControlAxis axis);
protected: protected:
// Called during the constructor to add new controls or buttons // Called during the constructor to add new controls or buttons

View File

@ -251,7 +251,7 @@ open_device() {
if (_device_class == DC_gamepad) { if (_device_class == DC_gamepad) {
axis = C_left_trigger; axis = C_left_trigger;
} else { } else {
axis = C_trigger; //axis = C_trigger;
} }
break; break;
@ -288,28 +288,34 @@ open_device() {
break; break;
case ABS_HAT0X: case ABS_HAT0X:
if (_dpad_left_button == -1 && _device_class == DC_gamepad) { if (_dpad_left_button == -1) {
// Emulate D-Pad. // Emulate D-Pad or hat switch.
_dpad_x_axis = i; _dpad_x_axis = i;
_dpad_left_button = (int)_buttons.size(); _dpad_left_button = (int)_buttons.size();
if (_device_class == DC_gamepad) {
_buttons.push_back(ButtonState(GamepadButton::dpad_left())); _buttons.push_back(ButtonState(GamepadButton::dpad_left()));
_buttons.push_back(ButtonState(GamepadButton::dpad_right())); _buttons.push_back(ButtonState(GamepadButton::dpad_right()));
axis = C_none;
} else { } else {
axis = C_hat_x; _buttons.push_back(ButtonState(GamepadButton::hat_left()));
_buttons.push_back(ButtonState(GamepadButton::hat_right()));
}
axis = C_none;
} }
break; break;
case ABS_HAT0Y: case ABS_HAT0Y:
if (_dpad_up_button == -1 && _device_class == DC_gamepad) { if (_dpad_up_button == -1) {
// Emulate D-Pad. // Emulate D-Pad.
_dpad_y_axis = i; _dpad_y_axis = i;
_dpad_up_button = (int)_buttons.size(); _dpad_up_button = (int)_buttons.size();
if (_device_class == DC_gamepad) {
_buttons.push_back(ButtonState(GamepadButton::dpad_up())); _buttons.push_back(ButtonState(GamepadButton::dpad_up()));
_buttons.push_back(ButtonState(GamepadButton::dpad_down())); _buttons.push_back(ButtonState(GamepadButton::dpad_down()));
axis = C_none;
} else { } else {
axis = C_hat_y; _buttons.push_back(ButtonState(GamepadButton::hat_up()));
_buttons.push_back(ButtonState(GamepadButton::hat_down()));
}
axis = C_none;
} }
break; break;
@ -323,7 +329,7 @@ open_device() {
} }
_controls[i].axis = axis; _controls[i].axis = axis;
if (axis == C_left_trigger || axis == C_right_trigger || axis == C_trigger) { if (axis == C_left_trigger || axis == C_right_trigger) {
// We'd like to use 0.0 to indicate the resting position. // We'd like to use 0.0 to indicate the resting position.
_controls[i]._scale = 1.0 / 65534.0; _controls[i]._scale = 1.0 / 65534.0;
_controls[i]._bias = 0.5; _controls[i]._bias = 0.5;

View File

@ -39,7 +39,7 @@ private:
int _fd; int _fd;
int _index; int _index;
// These are used for D-pad emulation. // These are used for D-pad / hat switch emulation.
int _dpad_x_axis; int _dpad_x_axis;
int _dpad_y_axis; int _dpad_y_axis;
int _dpad_left_button; int _dpad_left_button;

View File

@ -271,10 +271,10 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
case XINPUT_DEVSUBTYPE_FLIGHT_STICK: case XINPUT_DEVSUBTYPE_FLIGHT_STICK:
_device_class = DC_flight_stick; _device_class = DC_flight_stick;
set_control_map(0, C_rudder); set_control_map(0, C_yaw);
set_control_map(1, C_throttle); set_control_map(1, C_throttle);
set_control_map(2, C_x); set_control_map(2, C_roll);
set_control_map(3, C_y); set_control_map(3, C_pitch);
set_control_map(4, C_hat_x); set_control_map(4, C_hat_x);
set_control_map(5, C_hat_y); set_control_map(5, C_hat_y);
break; break;

View File

@ -48,6 +48,32 @@ DEFINE_GAMEPAD_BUTTON_HANDLE(action_1)
DEFINE_GAMEPAD_BUTTON_HANDLE(action_2) DEFINE_GAMEPAD_BUTTON_HANDLE(action_2)
DEFINE_GAMEPAD_BUTTON_HANDLE(trigger) DEFINE_GAMEPAD_BUTTON_HANDLE(trigger)
DEFINE_GAMEPAD_BUTTON_HANDLE(hat_up)
DEFINE_GAMEPAD_BUTTON_HANDLE(hat_down)
DEFINE_GAMEPAD_BUTTON_HANDLE(hat_left)
DEFINE_GAMEPAD_BUTTON_HANDLE(hat_right)
/**
* Returns the ButtonHandle associated with the particular numbered joystick
* button (zero-based), if there is one, or ButtonHandle::none() if there is
* not.
*/
ButtonHandle GamepadButton::
joystick(int button_number) {
if (button_number >= 0) {
// "button1" does not exist, it is called "trigger" instead
static pvector<ButtonHandle> buttons(1, _trigger);
while (button_number >= buttons.size()) {
char numstr[20];
sprintf(numstr, "joystick%d", (int)buttons.size() + 1);
ButtonHandle handle;
ButtonRegistry::ptr()->register_button(handle, numstr);
buttons.push_back(handle);
}
return buttons[button_number];
}
return ButtonHandle::none();
}
/** /**
* This is intended to be called only once, by the static initialization * This is intended to be called only once, by the static initialization
@ -85,4 +111,8 @@ init_gamepad_buttons() {
ButtonRegistry::ptr()->register_button(_action_2, "action_2"); ButtonRegistry::ptr()->register_button(_action_2, "action_2");
ButtonRegistry::ptr()->register_button(_trigger, "trigger"); ButtonRegistry::ptr()->register_button(_trigger, "trigger");
ButtonRegistry::ptr()->register_button(_hat_up, "hat_up");
ButtonRegistry::ptr()->register_button(_hat_down, "hat_down");
ButtonRegistry::ptr()->register_button(_hat_left, "hat_left");
ButtonRegistry::ptr()->register_button(_hat_right, "hat_right");
} }

View File

@ -53,7 +53,13 @@ PUBLISHED:
static ButtonHandle action_1(); static ButtonHandle action_1();
static ButtonHandle action_2(); static ButtonHandle action_2();
// Flight stick buttons, takes zero-based index. First is always trigger.
static ButtonHandle trigger(); static ButtonHandle trigger();
static ButtonHandle joystick(int button_number);
static ButtonHandle hat_up();
static ButtonHandle hat_down();
static ButtonHandle hat_left();
static ButtonHandle hat_right();
public: public:
static void init_gamepad_buttons(); static void init_gamepad_buttons();