Răsfoiți Sursa

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

rdb 9 ani în urmă
părinte
comite
fd16e34660

+ 71 - 20
panda/src/device/evdevInputDevice.cxx

@@ -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()));
-        }
       }
     }
   }

+ 18 - 2
panda/src/device/inputDevice.cxx

@@ -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;
 

+ 12 - 0
panda/src/device/inputDevice.h

@@ -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;

+ 28 - 4
panda/src/device/linuxJoystickDevice.cxx

@@ -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:

+ 95 - 28
panda/src/device/xInputDevice.cxx

@@ -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,11 +241,79 @@ 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;
+    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 {
-    _device_class = DC_unknown;
+    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;
@@ -254,7 +321,7 @@ init_device(const XINPUT_CAPABILITIES &caps, const XINPUT_STATE &state) {
 
   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) {