diff --git a/rootfs/usr/share/inputplumber/devices/50-anbernic_win600.yaml b/rootfs/usr/share/inputplumber/devices/50-anbernic_win600.yaml index 59f0072..0601412 100644 --- a/rootfs/usr/share/inputplumber/devices/50-anbernic_win600.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-anbernic_win600.yaml @@ -30,7 +30,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-aokzoe_a1.yaml b/rootfs/usr/share/inputplumber/devices/50-aokzoe_a1.yaml index bdab4d4..c806325 100644 --- a/rootfs/usr/share/inputplumber/devices/50-aokzoe_a1.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-aokzoe_a1.yaml @@ -40,7 +40,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2.yaml index 7dba4ad..cbfecb8 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2.yaml @@ -44,7 +44,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2021.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2021.yaml index 29eeb16..d21e196 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2021.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2021.yaml @@ -49,7 +49,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2s.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2s.yaml index 15670da..1036577 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_2s.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_2s.yaml @@ -44,7 +44,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air.yaml index affe730..e7f045f 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air.yaml @@ -40,7 +40,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_1s.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_1s.yaml index fa1ea86..6bab9a8 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_1s.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_1s.yaml @@ -40,7 +40,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus.yaml index 246b08b..df6ae16 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus.yaml @@ -50,7 +50,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus_mendo.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus_mendo.yaml index 7760a46..4ce4035 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus_mendo.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_air_plus_mendo.yaml @@ -42,7 +42,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_flip.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_flip.yaml index bec35e2..bceb04d 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_flip.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_flip.yaml @@ -44,7 +44,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_kun.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_kun.yaml index 94dc023..d8a4f90 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_kun.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_kun.yaml @@ -37,7 +37,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_next.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_next.yaml index 10e1882..9e02e4d 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_next.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_next.yaml @@ -55,7 +55,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayaneo_slide.yaml b/rootfs/usr/share/inputplumber/devices/50-ayaneo_slide.yaml index 2b49458..da16871 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayaneo_slide.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayaneo_slide.yaml @@ -37,7 +37,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-ayn_loki.yaml b/rootfs/usr/share/inputplumber/devices/50-ayn_loki.yaml index 9a3f1eb..c060036 100644 --- a/rootfs/usr/share/inputplumber/devices/50-ayn_loki.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-ayn_loki.yaml @@ -51,7 +51,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_win3.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_win3.yaml index 5738a27..969d496 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_win3.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_win3.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_win4.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_win4.yaml index 1a06343..abf5bf1 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_win4.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_win4.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_winmax2.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_winmax2.yaml index ebf3dd5..644d032 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_winmax2.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_winmax2.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-gpd_winmini.yaml b/rootfs/usr/share/inputplumber/devices/50-gpd_winmini.yaml index fb644da..4e9e871 100644 --- a/rootfs/usr/share/inputplumber/devices/50-gpd_winmini.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-gpd_winmini.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml index 7cdac1d..aa7261f 100644 --- a/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-legion_go.yaml @@ -139,6 +139,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_2.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_2.yaml index f4d722c..ddefe3a 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_2.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_2.yaml @@ -43,7 +43,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_amd.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_amd.yaml index d720ed1..5bc04c5 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_amd.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_amd.yaml @@ -54,7 +54,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_intel.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_intel.yaml index 0673c73..be976ee 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_intel.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_intel.yaml @@ -35,7 +35,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_a07.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_a07.yaml index b99f361..35ca120 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_a07.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_a07.yaml @@ -34,7 +34,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_pro.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_pro.yaml index 2999b34..7ab7980 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_pro.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_mini_pro.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-onexplayer_onexfly.yaml b/rootfs/usr/share/inputplumber/devices/50-onexplayer_onexfly.yaml index b73c47d..c9976b9 100644 --- a/rootfs/usr/share/inputplumber/devices/50-onexplayer_onexfly.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-onexplayer_onexfly.yaml @@ -33,7 +33,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-orangepi_neo.yaml b/rootfs/usr/share/inputplumber/devices/50-orangepi_neo.yaml index 132e7d6..4f1a080 100644 --- a/rootfs/usr/share/inputplumber/devices/50-orangepi_neo.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-orangepi_neo.yaml @@ -56,7 +56,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5 + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-rog_ally.yaml b/rootfs/usr/share/inputplumber/devices/50-rog_ally.yaml index b26d44e..9e0a45a 100644 --- a/rootfs/usr/share/inputplumber/devices/50-rog_ally.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-rog_ally.yaml @@ -38,7 +38,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - xbox-elite - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/50-steam_deck.yaml b/rootfs/usr/share/inputplumber/devices/50-steam_deck.yaml index c4cb309..30927ff 100644 --- a/rootfs/usr/share/inputplumber/devices/50-steam_deck.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-steam_deck.yaml @@ -47,7 +47,7 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xbox-elite - mouse - keyboard - touchscreen-fts3528 diff --git a/rootfs/usr/share/inputplumber/devices/50-switch_pro.yaml b/rootfs/usr/share/inputplumber/devices/50-switch_pro.yaml index 5360de4..44c9964 100644 --- a/rootfs/usr/share/inputplumber/devices/50-switch_pro.yaml +++ b/rootfs/usr/share/inputplumber/devices/50-switch_pro.yaml @@ -25,6 +25,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - gamepad #TODO: Nintendo controller target - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/60-ps4_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/60-ps4_gamepad.yaml index 1280b4d..40485ab 100644 --- a/rootfs/usr/share/inputplumber/devices/60-ps4_gamepad.yaml +++ b/rootfs/usr/share/inputplumber/devices/60-ps4_gamepad.yaml @@ -32,6 +32,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - ds5-edge + - ds5 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/60-xbox_360_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/60-xbox_360_gamepad.yaml index d4ce553..139618c 100644 --- a/rootfs/usr/share/inputplumber/devices/60-xbox_360_gamepad.yaml +++ b/rootfs/usr/share/inputplumber/devices/60-xbox_360_gamepad.yaml @@ -6,7 +6,7 @@ version: 1 kind: CompositeDevice # Name of the composite device mapping -name: Xbox 360 Gamepad +name: Microsoft X-Box 360 pad # Only use this profile if *any* of the given matches matches. If this list is # empty then the source devices will *always* be checked. @@ -177,6 +177,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/60-xbox_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/60-xbox_gamepad.yaml index 277b2c6..1c2ad68 100644 --- a/rootfs/usr/share/inputplumber/devices/60-xbox_gamepad.yaml +++ b/rootfs/usr/share/inputplumber/devices/60-xbox_gamepad.yaml @@ -6,7 +6,7 @@ version: 1 kind: CompositeDevice # Name of the composite device mapping -name: Xbox Gamepad +name: Microsoft X-Box pad # Only use this profile if *any* of the given matches matches. If this list is # empty,then the source devices will *always* be checked. @@ -104,6 +104,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/60-xbox_one_elite_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/60-xbox_one_elite_gamepad.yaml new file mode 100644 index 0000000..8c8ebb4 --- /dev/null +++ b/rootfs/usr/share/inputplumber/devices/60-xbox_one_elite_gamepad.yaml @@ -0,0 +1,28 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/ShadowBlip/InputPlumber/main/rootfs/usr/share/inputplumber/schema/composite_device_v1.json +# Schema version number +version: 1 + +# The type of configuration schema +kind: CompositeDevice + +# Name of the composite device mapping +name: Microsoft X-Box One Elite pad + +# Only use this profile if *any* of the given matches matches. If this list is +# empty,then the source devices will *always* be checked. +# /sys/class/dmi/id/product_name +matches: [] + +# One or more source devices to combine into a single virtual device. The events +# from these devices will be watched and translated according to the key map. +source_devices: + - group: gamepad + evdev: + vendor_id: "045e" + product_id: "{02e3,0b00}" + +# The target input device(s) that the virtual device profile can use +target_devices: + - xbox-elite + - mouse + - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/60-xbox_one_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/60-xbox_one_gamepad.yaml index cd4f9f2..01389b0 100644 --- a/rootfs/usr/share/inputplumber/devices/60-xbox_one_gamepad.yaml +++ b/rootfs/usr/share/inputplumber/devices/60-xbox_one_gamepad.yaml @@ -6,7 +6,7 @@ version: 1 kind: CompositeDevice # Name of the composite device mapping -name: Xbox One Gamepad +name: Microsoft X-Box One pad # Only use this profile if *any* of the given matches matches. If this list is # empty,then the source devices will *always* be checked. @@ -23,7 +23,7 @@ source_devices: - group: gamepad evdev: vendor_id: "045e" - product_id: "{02d1,02dd,02e3,0b00,02ea,0b12}" + product_id: "{02d1,02dd,02ea,0b12}" - group: gamepad evdev: @@ -87,6 +87,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/devices/70-generic_gamepad.yaml b/rootfs/usr/share/inputplumber/devices/70-generic_gamepad.yaml index 703956a..17b5077 100644 --- a/rootfs/usr/share/inputplumber/devices/70-generic_gamepad.yaml +++ b/rootfs/usr/share/inputplumber/devices/70-generic_gamepad.yaml @@ -22,6 +22,6 @@ source_devices: # The target input device(s) that the virtual device profile can use target_devices: - - gamepad + - xb360 - mouse - keyboard diff --git a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json index 2efbdae..ce03fc7 100644 --- a/rootfs/usr/share/inputplumber/schema/composite_device_v1.json +++ b/rootfs/usr/share/inputplumber/schema/composite_device_v1.json @@ -47,6 +47,7 @@ "keyboard", "gamepad", "xb360", + "xbox-elite", "deck", "ds5", "ds5-usb", diff --git a/src/input/event/evdev.rs b/src/input/event/evdev.rs index 03e2ff3..bf8a1bb 100644 --- a/src/input/event/evdev.rs +++ b/src/input/event/evdev.rs @@ -582,37 +582,37 @@ fn event_codes_from_capability(capability: Capability) -> Vec { Capability::DBus(_) => vec![], Capability::Gamepad(gamepad) => match gamepad { Gamepad::Button(btn) => match btn { - GamepadButton::South => vec![KeyCode::BTN_SOUTH.0], + GamepadButton::DPadDown => vec![AbsoluteAxisCode::ABS_HAT0Y.0], + GamepadButton::DPadLeft => vec![AbsoluteAxisCode::ABS_HAT0X.0], + GamepadButton::DPadRight => vec![AbsoluteAxisCode::ABS_HAT0X.0], + GamepadButton::DPadUp => vec![AbsoluteAxisCode::ABS_HAT0Y.0], GamepadButton::East => vec![KeyCode::BTN_EAST.0], - GamepadButton::North => vec![KeyCode::BTN_NORTH.0], - GamepadButton::West => vec![KeyCode::BTN_WEST.0], + GamepadButton::Guide => vec![KeyCode::BTN_MODE.0], + GamepadButton::Keyboard => vec![], GamepadButton::LeftBumper => vec![KeyCode::BTN_TL.0], - GamepadButton::RightBumper => vec![KeyCode::BTN_TR.0], + GamepadButton::LeftPaddle1 => vec![KeyCode::BTN_TRIGGER_HAPPY7.0], + GamepadButton::LeftPaddle2 => vec![KeyCode::BTN_TRIGGER_HAPPY8.0], + GamepadButton::LeftPaddle3 => vec![KeyCode::BTN_TRIGGER_HAPPY10.0], + GamepadButton::LeftStick => vec![KeyCode::BTN_THUMBL.0], + GamepadButton::LeftStickTouch => vec![], GamepadButton::LeftTop => vec![], - GamepadButton::RightTop => vec![], - GamepadButton::Start => vec![KeyCode::BTN_START.0], - GamepadButton::Select => vec![KeyCode::BTN_SELECT.0], - GamepadButton::Guide => vec![KeyCode::BTN_MODE.0], + GamepadButton::LeftTrigger => vec![KeyCode::BTN_TL2.0], + GamepadButton::North => vec![KeyCode::BTN_NORTH.0], GamepadButton::QuickAccess => vec![], GamepadButton::QuickAccess2 => vec![], - GamepadButton::Keyboard => vec![], - GamepadButton::Screenshot => vec![], - GamepadButton::LeftStick => vec![KeyCode::BTN_THUMBL.0], + GamepadButton::RightBumper => vec![KeyCode::BTN_TR.0], + GamepadButton::RightPaddle1 => vec![KeyCode::BTN_TRIGGER_HAPPY5.0], + GamepadButton::RightPaddle2 => vec![KeyCode::BTN_TRIGGER_HAPPY6.0], + GamepadButton::RightPaddle3 => vec![KeyCode::BTN_TRIGGER_HAPPY9.0], GamepadButton::RightStick => vec![KeyCode::BTN_THUMBR.0], - GamepadButton::DPadUp => vec![AbsoluteAxisCode::ABS_HAT0Y.0], - GamepadButton::DPadDown => vec![AbsoluteAxisCode::ABS_HAT0Y.0], - GamepadButton::DPadLeft => vec![AbsoluteAxisCode::ABS_HAT0X.0], - GamepadButton::DPadRight => vec![AbsoluteAxisCode::ABS_HAT0X.0], - GamepadButton::LeftTrigger => vec![KeyCode::BTN_TL2.0], - GamepadButton::LeftPaddle1 => vec![], - GamepadButton::LeftPaddle2 => vec![], - GamepadButton::LeftStickTouch => vec![], - GamepadButton::RightTrigger => vec![KeyCode::BTN_TR2.0], - GamepadButton::RightPaddle1 => vec![], - GamepadButton::RightPaddle2 => vec![], GamepadButton::RightStickTouch => vec![], - GamepadButton::LeftPaddle3 => vec![], - GamepadButton::RightPaddle3 => vec![], + GamepadButton::RightTop => vec![], + GamepadButton::RightTrigger => vec![KeyCode::BTN_TR2.0], + GamepadButton::Screenshot => vec![], + GamepadButton::Select => vec![KeyCode::BTN_SELECT.0], + GamepadButton::South => vec![KeyCode::BTN_SOUTH.0], + GamepadButton::Start => vec![KeyCode::BTN_START.0], + GamepadButton::West => vec![KeyCode::BTN_WEST.0], }, Gamepad::Axis(axis) => match axis { GamepadAxis::LeftStick => { diff --git a/src/input/manager.rs b/src/input/manager.rs index 530274d..0a0fb6f 100644 --- a/src/input/manager.rs +++ b/src/input/manager.rs @@ -31,12 +31,12 @@ use crate::input::target::dbus::DBusDevice; use crate::input::target::dualsense; use crate::input::target::dualsense::DualSenseDevice; use crate::input::target::dualsense::DualSenseHardware; -use crate::input::target::gamepad::GenericGamepad; use crate::input::target::keyboard::KeyboardDevice; use crate::input::target::mouse::MouseDevice; use crate::input::target::steam_deck::SteamDeckDevice; use crate::input::target::touchscreen_fts3528::Fts3528TouchscreenDevice; use crate::input::target::xb360::XBox360Controller; +use crate::input::target::xbox_elite::XboxEliteController; use crate::input::target::TargetDeviceType; use crate::procfs; use crate::udev; @@ -401,7 +401,7 @@ impl Manager { log::debug!("Creating target device: {kind}"); // Create the target device to emulate based on the kind let device = match kind { - "gamepad" => TargetDeviceType::GenericGamepad(GenericGamepad::new(self.dbus.clone())), + "dbus" => TargetDeviceType::DBus(DBusDevice::new(self.dbus.clone())), "deck" => TargetDeviceType::SteamDeck(SteamDeckDevice::new(self.dbus.clone())), "ds5" | "ds5-usb" | "ds5-bt" | "ds5-edge" | "ds5-edge-usb" | "ds5-edge-bt" => { let hw = match kind { @@ -424,13 +424,17 @@ impl Manager { }; TargetDeviceType::DualSense(DualSenseDevice::new(self.dbus.clone(), hw)) } - "xb360" => TargetDeviceType::XBox360(XBox360Controller::new()), - "dbus" => TargetDeviceType::DBus(DBusDevice::new(self.dbus.clone())), - "mouse" => TargetDeviceType::Mouse(MouseDevice::new(self.dbus.clone())), + // Deprecated, retained for backwards compatibility + "gamepad" => TargetDeviceType::Xbox360(XBox360Controller::new(self.dbus.clone())), "keyboard" => TargetDeviceType::Keyboard(KeyboardDevice::new(self.dbus.clone())), + "mouse" => TargetDeviceType::Mouse(MouseDevice::new(self.dbus.clone())), "touchscreen-fts3528" => { TargetDeviceType::Touchscreen(Fts3528TouchscreenDevice::new(self.dbus.clone())) } + "xb360" => TargetDeviceType::Xbox360(XBox360Controller::new(self.dbus.clone())), + "xbox-elite" => { + TargetDeviceType::XBoxElite(XboxEliteController::new(self.dbus.clone())) + } _ => TargetDeviceType::Null, }; log::debug!("Created target input device: {kind}"); @@ -447,6 +451,32 @@ impl Manager { for target in targets { match target { TargetDeviceType::Null => (), + TargetDeviceType::DBus(mut device) => { + let path = self.next_target_path("dbus")?; + let event_tx = device.transmitter(); + target_devices.insert(path.clone(), event_tx.clone()); + self.target_devices.insert(path.clone(), event_tx.clone()); + device.listen_on_dbus(path.clone()).await?; + tokio::spawn(async move { + if let Err(e) = device.run().await { + log::error!("Failed to run target dbus device: {:?}", e); + } + log::debug!("Target dbus device closed at: {}", path); + }); + } + TargetDeviceType::DualSense(mut device) => { + let path = self.next_target_path("gamepad")?; + let event_tx = device.transmitter(); + target_devices.insert(path.clone(), event_tx.clone()); + self.target_devices.insert(path.clone(), event_tx.clone()); + device.listen_on_dbus(path.clone()).await?; + tokio::spawn(async move { + if let Err(e) = device.run().await { + log::error!("Failed to run target dualsense device: {:?}", e); + } + log::debug!("Target dualsense device closed at: {}", path); + }); + } TargetDeviceType::Keyboard(mut device) => { let path = self.next_target_path("keyboard")?; let event_tx = device.transmitter(); @@ -473,70 +503,56 @@ impl Manager { log::debug!("Target mouse device closed at: {}", path); }); } - TargetDeviceType::GenericGamepad(mut gamepad) => { + TargetDeviceType::SteamDeck(mut device) => { let path = self.next_target_path("gamepad")?; - let event_tx = gamepad.transmitter(); - target_devices.insert(path.clone(), event_tx.clone()); - self.target_devices.insert(path.clone(), event_tx.clone()); - gamepad.listen_on_dbus(path.clone()).await?; - tokio::spawn(async move { - if let Err(e) = gamepad.run().await { - log::error!("Failed to run target gamepad: {:?}", e); - } - log::debug!("Target gamepad device closed at: {}", path); - }); - } - TargetDeviceType::DBus(mut device) => { - let path = self.next_target_path("dbus")?; let event_tx = device.transmitter(); target_devices.insert(path.clone(), event_tx.clone()); self.target_devices.insert(path.clone(), event_tx.clone()); device.listen_on_dbus(path.clone()).await?; tokio::spawn(async move { if let Err(e) = device.run().await { - log::error!("Failed to run target dbus device: {:?}", e); + log::error!("Failed to run target steam deck device: {:?}", e); } - log::debug!("Target dbus device closed at: {}", path); + log::debug!("Target steam deck device closed at: {}", path); }); } - TargetDeviceType::SteamDeck(mut device) => { - let path = self.next_target_path("gamepad")?; + TargetDeviceType::Touchscreen(mut device) => { + let path = self.next_target_path("touchscreen")?; let event_tx = device.transmitter(); target_devices.insert(path.clone(), event_tx.clone()); self.target_devices.insert(path.clone(), event_tx.clone()); device.listen_on_dbus(path.clone()).await?; tokio::spawn(async move { if let Err(e) = device.run().await { - log::error!("Failed to run target steam deck device: {:?}", e); + log::error!("Failed to run target touchscreen device: {:?}", e); } - log::debug!("Target steam deck device closed at: {}", path); + log::debug!("Target touchscreen device closed at: {}", path); }); } - TargetDeviceType::DualSense(mut device) => { + TargetDeviceType::Xbox360(mut gamepad) => { let path = self.next_target_path("gamepad")?; - let event_tx = device.transmitter(); + let event_tx = gamepad.transmitter(); target_devices.insert(path.clone(), event_tx.clone()); self.target_devices.insert(path.clone(), event_tx.clone()); - device.listen_on_dbus(path.clone()).await?; + gamepad.listen_on_dbus(path.clone()).await?; tokio::spawn(async move { - if let Err(e) = device.run().await { - log::error!("Failed to run target dualsense device: {:?}", e); + if let Err(e) = gamepad.run().await { + log::error!("Failed to run target gamepad: {:?}", e); } - log::debug!("Target dualsense device closed at: {}", path); + log::debug!("Target gamepad device closed at: {}", path); }); } - TargetDeviceType::XBox360(_) => todo!(), - TargetDeviceType::Touchscreen(mut device) => { - let path = self.next_target_path("touchscreen")?; - let event_tx = device.transmitter(); + TargetDeviceType::XBoxElite(mut gamepad) => { + let path = self.next_target_path("gamepad")?; + let event_tx = gamepad.transmitter(); target_devices.insert(path.clone(), event_tx.clone()); self.target_devices.insert(path.clone(), event_tx.clone()); - device.listen_on_dbus(path.clone()).await?; + gamepad.listen_on_dbus(path.clone()).await?; tokio::spawn(async move { - if let Err(e) = device.run().await { - log::error!("Failed to run target touchscreen device: {:?}", e); + if let Err(e) = gamepad.run().await { + log::error!("Failed to run target gamepad: {:?}", e); } - log::debug!("Target touchscreen device closed at: {}", path); + log::debug!("Target gamepad device closed at: {}", path); }); } } diff --git a/src/input/target/mod.rs b/src/input/target/mod.rs index d646f5e..c8cc142 100644 --- a/src/input/target/mod.rs +++ b/src/input/target/mod.rs @@ -7,12 +7,12 @@ use super::{ pub mod dbus; pub mod dualsense; -pub mod gamepad; pub mod keyboard; pub mod mouse; pub mod steam_deck; pub mod touchscreen_fts3528; pub mod xb360; +pub mod xbox_elite; /// A [TargetDevice] is any virtual input device that emits input events #[derive(Debug)] @@ -21,8 +21,8 @@ pub enum TargetDeviceType { DBus(dbus::DBusDevice), Keyboard(keyboard::KeyboardDevice), Mouse(mouse::MouseDevice), - GenericGamepad(gamepad::GenericGamepad), - XBox360(xb360::XBox360Controller), + Xbox360(xb360::XBox360Controller), + XBoxElite(xbox_elite::XboxEliteController), SteamDeck(steam_deck::SteamDeckDevice), DualSense(dualsense::DualSenseDevice), Touchscreen(touchscreen_fts3528::Fts3528TouchscreenDevice), diff --git a/src/input/target/xb360.rs b/src/input/target/xb360.rs index 946f7e6..7883b30 100644 --- a/src/input/target/xb360.rs +++ b/src/input/target/xb360.rs @@ -1,25 +1,198 @@ -use std::error::Error; +use std::{ + collections::HashMap, + error::Error, + ops::DerefMut, + os::fd::AsRawFd, + sync::{Arc, Mutex}, + thread, +}; use evdev::{ - uinput::VirtualDeviceBuilder, AbsInfo, AbsoluteAxisCode, AttributeSet, FFEffectCode, KeyCode, + uinput::{VirtualDevice, VirtualDeviceBuilder}, + AbsInfo, AbsoluteAxisCode, AttributeSet, BusType, EventSummary, FFEffectCode, FFStatusCode, + InputEvent, InputId, KeyCode, SynchronizationCode, SynchronizationEvent, UInputCode, UinputAbsSetup, }; +use nix::fcntl::{FcntlArg, OFlag}; +use tokio::{sync::mpsc, time::Duration}; +use zbus::Connection; + +use crate::{ + dbus::interface::target::gamepad::TargetGamepadInterface, + input::{ + capability::{Capability, Gamepad, GamepadAxis, GamepadButton, GamepadTrigger}, + composite_device::client::CompositeDeviceClient, + event::{evdev::EvdevEvent, native::NativeEvent}, + output_event::{OutputEvent, UinputOutputEvent}, + }, +}; + +use super::TargetCommand; -use crate::input::composite_device::client::CompositeDeviceClient; +/// Size of the [TargetCommand] buffer for receiving input events +const BUFFER_SIZE: usize = 2048; +/// How long to sleep before polling for events. +const POLL_RATE: Duration = Duration::from_micros(1666); #[derive(Debug)] pub struct XBox360Controller { - _composite_tx: Option, + conn: Connection, + dbus_path: Option, + tx: mpsc::Sender, + rx: mpsc::Receiver, + composite_device: Option, } impl XBox360Controller { - pub fn new() -> Self { + pub fn new(conn: Connection) -> Self { + let (tx, rx) = mpsc::channel(BUFFER_SIZE); Self { - _composite_tx: None, + conn, + dbus_path: None, + tx, + rx, + composite_device: None, } } - pub fn run() -> Result<(), Box> { + /// Returns the DBus path of this device + pub fn _get_dbus_path(&self) -> Option { + self.dbus_path.clone() + } + + /// Returns a transmitter channel that can be used to send events to this device + pub fn transmitter(&self) -> mpsc::Sender { + self.tx.clone() + } + + /// Configures the device to send output events to the given composite device + /// channel. + pub fn set_composite_device(&mut self, composite_device: CompositeDeviceClient) { + self.composite_device = Some(composite_device); + } + + /// Creates a new instance of the dbus device interface on DBus. + pub async fn listen_on_dbus(&mut self, path: String) -> Result<(), Box> { + log::debug!("Starting dbus interface on {path}"); + let conn = self.conn.clone(); + self.dbus_path = Some(path.clone()); + tokio::spawn(async move { + log::debug!("Starting dbus interface: {path}"); + let iface = TargetGamepadInterface::new("Gamepad".into()); + if let Err(e) = conn.object_server().at(path.clone(), iface).await { + log::debug!("Failed to start dbus interface {path}: {e:?}"); + } else { + log::debug!("Started dbus interface on {path}"); + } + }); + Ok(()) + } + + /// Creates and runs the target device + pub async fn run(&mut self) -> Result<(), Box> { + log::debug!("Creating virtual gamepad"); + let device = self.create_virtual_device()?; + + // Put the device behind an Arc Mutex so it can be shared between the + // read and write threads + let device = Arc::new(Mutex::new(device)); + + // Query information about the device to get the absolute ranges + let axes_map = self.get_abs_info(); + + // Listen for events from source devices + log::debug!("Started listening for events"); + while let Some(command) = self.rx.recv().await { + match command { + TargetCommand::SetCompositeDevice(composite_device) => { + self.set_composite_device(composite_device.clone()); + + // Spawn a thread to listen for force feedback events + let ff_device = device.clone(); + XBox360Controller::spawn_ff_thread(ff_device, composite_device); + } + TargetCommand::WriteEvent(event) => { + log::trace!("Got event to emit: {:?}", event); + let evdev_events = self.translate_event(event, axes_map.clone()); + if let Ok(mut dev) = device.lock() { + dev.emit(evdev_events.as_slice())?; + dev.emit(&[ + SynchronizationEvent::new(SynchronizationCode::SYN_REPORT, 0).into(), + ])?; + } + } + TargetCommand::GetCapabilities(tx) => { + let caps = self.get_capabilities(); + if let Err(e) = tx.send(caps).await { + log::error!("Failed to send target capabilities: {e:?}"); + } + } + TargetCommand::Stop => break, + } + } + + log::debug!( + "Stopping device {}", + self.dbus_path.clone().unwrap_or_default() + ); + + // Remove the DBus interface + if let Some(path) = self.dbus_path.clone() { + let conn = self.conn.clone(); + let path = path.clone(); + tokio::task::spawn(async move { + log::debug!("Stopping dbus interface for {path}"); + let result = conn + .object_server() + .remove::(path.clone()) + .await; + if let Err(e) = result { + log::error!("Failed to stop dbus interface {path}: {e:?}"); + } else { + log::debug!("Stopped dbus interface for {path}"); + } + }); + } + + Ok(()) + } + + /// Translate the given native event into an evdev event + fn translate_event( + &self, + event: NativeEvent, + axis_map: HashMap, + ) -> Vec { + EvdevEvent::from_native_event(event, axis_map) + .into_iter() + .map(|event| event.as_input_event()) + .collect() + } + + /// Return a hashmap of ABS information for this virtual device. This information + /// is used to denormalize input event values. + fn get_abs_info(&self) -> HashMap { + let mut axes_info = HashMap::new(); + + let joystick_setup = AbsInfo::new(0, -32768, 32767, 16, 128, 1); + axes_info.insert(AbsoluteAxisCode::ABS_X, joystick_setup); + axes_info.insert(AbsoluteAxisCode::ABS_Y, joystick_setup); + axes_info.insert(AbsoluteAxisCode::ABS_RX, joystick_setup); + axes_info.insert(AbsoluteAxisCode::ABS_RY, joystick_setup); + + let triggers_setup = AbsInfo::new(0, 0, 255, 0, 0, 1); + axes_info.insert(AbsoluteAxisCode::ABS_Z, triggers_setup); + axes_info.insert(AbsoluteAxisCode::ABS_RZ, triggers_setup); + + let dpad_setup = AbsInfo::new(0, -1, 1, 0, 0, 1); + axes_info.insert(AbsoluteAxisCode::ABS_HAT0X, dpad_setup); + axes_info.insert(AbsoluteAxisCode::ABS_HAT0Y, dpad_setup); + + axes_info + } + + /// Create the virtual device to emulate + fn create_virtual_device(&self) -> Result> { // Setup Key inputs let mut keys = AttributeSet::::new(); keys.insert(KeyCode::BTN_SOUTH); @@ -60,9 +233,13 @@ impl XBox360Controller { ff.insert(FFEffectCode::FF_SINE); ff.insert(FFEffectCode::FF_GAIN); + // Identify to the kernel as an Xbox One Elite + let id = InputId::new(BusType(3), 0x045e, 0x028e, 0x0001); + // Build the device - let _device = VirtualDeviceBuilder::new()? - .name("Xbox 360 Wireless Receiver (XBOX)") + let device = VirtualDeviceBuilder::new()? + .name("Microsoft X-Box 360 pad") + .input_id(id) .with_keys(&keys)? .with_absolute_axis(&abs_x)? .with_absolute_axis(&abs_y)? @@ -73,8 +250,175 @@ impl XBox360Controller { .with_absolute_axis(&abs_hat0x)? .with_absolute_axis(&abs_hat0y)? .with_ff(&ff)? + .with_ff_effects_max(16) .build()?; + // Set the device to do non-blocking reads + // TODO: use epoll to wake up when data is available + // https://github.com/emberian/evdev/blob/main/examples/evtest_nonblocking.rs + let raw_fd = device.as_raw_fd(); + nix::fcntl::fcntl(raw_fd, FcntlArg::F_SETFL(OFlag::O_NONBLOCK))?; + + Ok(device) + } + + /// Spawns the force-feedback handler thread + fn spawn_ff_thread( + ff_device: Arc>, + composite_device: CompositeDeviceClient, + ) { + tokio::task::spawn_blocking(move || { + loop { + // Check to see if the main input thread still has a reference + // to the virtual device. If it does not, it means the device + // has stopped. + let num_refs = Arc::strong_count(&ff_device); + if num_refs == 1 { + log::debug!("Virtual device stopped. Stopping FF handler thread."); + break; + } + + // Read any events + if let Err(e) = XBox360Controller::process_ff(&ff_device, &composite_device) { + log::warn!("Error processing FF events: {:?}", e); + } + + // Sleep for the poll rate interval + thread::sleep(POLL_RATE); + } + }); + } + + /// Process force feedback events from the given device + fn process_ff( + device: &Arc>, + composite_device: &CompositeDeviceClient, + ) -> Result<(), Box> { + // Listen for events (Force Feedback Events) + let events = match device.lock() { + Ok(mut dev) => { + let res = dev.deref_mut().fetch_events(); + match res { + Ok(events) => events.collect(), + Err(err) => match err.kind() { + // Do nothing if this would block + std::io::ErrorKind::WouldBlock => vec![], + _ => { + log::trace!("Failed to fetch events: {:?}", err); + return Err(err.into()); + } + }, + } + } + Err(err) => { + log::trace!("Failed to lock device mutex: {:?}", err); + return Err(err.to_string().into()); + } + }; + + const STOPPED: i32 = FFStatusCode::FF_STATUS_STOPPED.0 as i32; + const PLAYING: i32 = FFStatusCode::FF_STATUS_PLAYING.0 as i32; + + // Process the events + for event in events { + match event.destructure() { + EventSummary::UInput(event, UInputCode::UI_FF_UPLOAD, ..) => { + log::debug!("Got FF upload event"); + // Claim ownership of the FF upload and convert it to a FF_UPLOAD + // event + let mut event = device + .lock() + .map_err(|e| e.to_string())? + .process_ff_upload(event)?; + let effect_id = event.effect_id(); + + log::debug!("Upload effect: {:?} with id {}", event.effect(), effect_id); + + // Send the effect data to be uploaded to the device and wait + // for an effect ID to be generated. + let (tx, rx) = std::sync::mpsc::channel::>(); + let upload = OutputEvent::Uinput(UinputOutputEvent::FFUpload( + effect_id, + event.effect(), + tx, + )); + if let Err(e) = composite_device.blocking_process_output_event(upload) { + event.set_retval(-1); + return Err(e.into()); + } + let effect_id = match rx.recv_timeout(Duration::from_secs(1)) { + Ok(id) => id, + Err(e) => { + event.set_retval(-1); + return Err(e.into()); + } + }; + + // Set the effect ID for the FF effect + if let Some(id) = effect_id { + event.set_effect_id(id); + event.set_retval(0); + } else { + log::warn!("Failed to get effect ID to upload FF effect"); + event.set_retval(-1); + } + } + EventSummary::UInput(event, UInputCode::UI_FF_ERASE, ..) => { + log::debug!("Got FF erase event"); + // Claim ownership of the FF erase event and convert it to a FF_ERASE + // event. + let event = device + .lock() + .map_err(|e| e.to_string())? + .process_ff_erase(event)?; + log::debug!("Erase effect: {:?}", event.effect_id()); + + let erase = OutputEvent::Uinput(UinputOutputEvent::FFErase(event.effect_id())); + composite_device.blocking_process_output_event(erase)?; + } + EventSummary::ForceFeedback(.., effect_id, STOPPED) => { + log::debug!("Stopped effect ID: {}", effect_id.0); + log::debug!("Stopping event: {:?}", event); + composite_device.blocking_process_output_event(OutputEvent::Evdev(event))?; + } + EventSummary::ForceFeedback(.., effect_id, PLAYING) => { + log::debug!("Playing effect ID: {}", effect_id.0); + log::debug!("Playing event: {:?}", event); + composite_device.blocking_process_output_event(OutputEvent::Evdev(event))?; + } + _ => { + log::debug!("Unhandled event: {:?}", event); + } + } + } + Ok(()) } + + /// Returns capabilities of the target device + fn get_capabilities(&self) -> Vec { + vec![ + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), + Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), + ] + } } diff --git a/src/input/target/gamepad.rs b/src/input/target/xbox_elite.rs similarity index 93% rename from src/input/target/gamepad.rs rename to src/input/target/xbox_elite.rs index 436ed84..e715fa6 100644 --- a/src/input/target/gamepad.rs +++ b/src/input/target/xbox_elite.rs @@ -1,5 +1,3 @@ -//! The GenericGamepad target provides a simple generic virtual gamepad based -//! on the XBox 360 gamepad. use std::{ collections::HashMap, error::Error, @@ -11,8 +9,9 @@ use std::{ use evdev::{ uinput::{VirtualDevice, VirtualDeviceBuilder}, - AbsInfo, AbsoluteAxisCode, AttributeSet, EventSummary, FFEffectCode, FFStatusCode, InputEvent, - KeyCode, SynchronizationCode, SynchronizationEvent, UInputCode, UinputAbsSetup, + AbsInfo, AbsoluteAxisCode, AttributeSet, BusType, EventSummary, FFEffectCode, FFStatusCode, + InputEvent, InputId, KeyCode, SynchronizationCode, SynchronizationEvent, UInputCode, + UinputAbsSetup, }; use nix::fcntl::{FcntlArg, OFlag}; use tokio::{sync::mpsc, time::Duration}; @@ -36,7 +35,7 @@ const BUFFER_SIZE: usize = 2048; const POLL_RATE: Duration = Duration::from_micros(1666); #[derive(Debug)] -pub struct GenericGamepad { +pub struct XboxEliteController { conn: Connection, dbus_path: Option, tx: mpsc::Sender, @@ -44,7 +43,7 @@ pub struct GenericGamepad { composite_device: Option, } -impl GenericGamepad { +impl XboxEliteController { pub fn new(conn: Connection) -> Self { let (tx, rx) = mpsc::channel(BUFFER_SIZE); Self { @@ -57,7 +56,7 @@ impl GenericGamepad { } /// Returns the DBus path of this device - pub fn get_dbus_path(&self) -> Option { + pub fn _get_dbus_path(&self) -> Option { self.dbus_path.clone() } @@ -91,7 +90,7 @@ impl GenericGamepad { /// Creates and runs the target device pub async fn run(&mut self) -> Result<(), Box> { - log::debug!("Creating virtual gamepad"); + log::debug!("Creating virtual Xbox Elite gamepad"); let device = self.create_virtual_device()?; // Put the device behind an Arc Mutex so it can be shared between the @@ -110,7 +109,7 @@ impl GenericGamepad { // Spawn a thread to listen for force feedback events let ff_device = device.clone(); - GenericGamepad::spawn_ff_thread(ff_device, composite_device); + XboxEliteController::spawn_ff_thread(ff_device, composite_device); } TargetCommand::WriteEvent(event) => { log::trace!("Got event to emit: {:?}", event); @@ -211,6 +210,10 @@ impl GenericGamepad { keys.insert(KeyCode::BTN_TRIGGER_HAPPY2); keys.insert(KeyCode::BTN_TRIGGER_HAPPY3); keys.insert(KeyCode::BTN_TRIGGER_HAPPY4); + keys.insert(KeyCode::BTN_TRIGGER_HAPPY5); + keys.insert(KeyCode::BTN_TRIGGER_HAPPY6); + keys.insert(KeyCode::BTN_TRIGGER_HAPPY7); + keys.insert(KeyCode::BTN_TRIGGER_HAPPY8); // Setup ABS inputs let joystick_setup = AbsInfo::new(0, -32768, 32767, 16, 128, 1); @@ -234,9 +237,13 @@ impl GenericGamepad { ff.insert(FFEffectCode::FF_SINE); ff.insert(FFEffectCode::FF_GAIN); + // Identify to the kernel as an Xbox One Elite + let id = InputId::new(BusType(3), 0x045e, 0x02e3, 0x0001); + // Build the device let device = VirtualDeviceBuilder::new()? - .name("InputPlumber Gamepad") + .name("Microsoft X-Box One Elite pad") + .input_id(id) .with_keys(&keys)? .with_absolute_axis(&abs_x)? .with_absolute_axis(&abs_y)? @@ -276,7 +283,7 @@ impl GenericGamepad { } // Read any events - if let Err(e) = GenericGamepad::process_ff(&ff_device, &composite_device) { + if let Err(e) = XboxEliteController::process_ff(&ff_device, &composite_device) { log::warn!("Error processing FF events: {:?}", e); } @@ -395,25 +402,29 @@ impl GenericGamepad { /// Returns capabilities of the target device fn get_capabilities(&self) -> Vec { vec![ - Capability::Gamepad(Gamepad::Button(GamepadButton::South)), - Capability::Gamepad(Gamepad::Button(GamepadButton::North)), - Capability::Gamepad(Gamepad::Button(GamepadButton::East)), - Capability::Gamepad(Gamepad::Button(GamepadButton::West)), - Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), - Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), - Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), + Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), Capability::Gamepad(Gamepad::Button(GamepadButton::DPadDown)), - Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), Capability::Gamepad(Gamepad::Button(GamepadButton::DPadLeft)), Capability::Gamepad(Gamepad::Button(GamepadButton::DPadRight)), + Capability::Gamepad(Gamepad::Button(GamepadButton::DPadUp)), + Capability::Gamepad(Gamepad::Button(GamepadButton::East)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Guide)), Capability::Gamepad(Gamepad::Button(GamepadButton::LeftBumper)), - Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftPaddle2)), Capability::Gamepad(Gamepad::Button(GamepadButton::LeftStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::LeftTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::North)), Capability::Gamepad(Gamepad::Button(GamepadButton::RightBumper)), - Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle1)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightPaddle2)), Capability::Gamepad(Gamepad::Button(GamepadButton::RightStick)), - Capability::Gamepad(Gamepad::Axis(GamepadAxis::LeftStick)), - Capability::Gamepad(Gamepad::Axis(GamepadAxis::RightStick)), + Capability::Gamepad(Gamepad::Button(GamepadButton::RightTrigger)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Select)), + Capability::Gamepad(Gamepad::Button(GamepadButton::South)), + Capability::Gamepad(Gamepad::Button(GamepadButton::Start)), + Capability::Gamepad(Gamepad::Button(GamepadButton::West)), Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::LeftTrigger)), Capability::Gamepad(Gamepad::Trigger(GamepadTrigger::RightTrigger)), ]