From 36afa54bad6d92b24ecc54441063a71e00bab556 Mon Sep 17 00:00:00 2001 From: Kai Krakow Date: Sun, 28 Mar 2021 00:37:13 +0100 Subject: [PATCH] hid-xpadneo: Allow the driver to operate in Xpad360 emulation mode This mode disables most advanced features and exposes the device as an original USB-connected Xbox 360 controller. Use module parameter `xpad_emulation=1`. This may be useful for applications like Retroarch or other programs that need the Guide button exposed as a joystick button. Essentially, it enables such applications to use the button as a macro modifier button, and the button will no longer be sent as a consumer control key (but still announced as being available). In this mode, joydev inserts the button before the left and right thumb buttons which may mess up some mappings, especially with joydev. Since we are pretending to be a USB Xbox360 controller, most programs will take care of it. For hidraw programs, we also scale the trigger range down from 0..1023 to 0..255 which makes the trigger slightly less precise. This parameter cannot be changed at runtime, you must reload the module with the parameter changed to apply the emulation correctly. Fixes: https://github.com/atar-axis/xpadneo/issues/237 Affects: https://github.com/atar-axis/xpadneo/issues/273 Fixes: https://github.com/atar-axis/xpadneo/issues/280 Signed-off-by: Kai Krakow --- hid-xpadneo/src/hid-xpadneo.c | 124 ++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 36 deletions(-) diff --git a/hid-xpadneo/src/hid-xpadneo.c b/hid-xpadneo/src/hid-xpadneo.c index e18a71e9..509768de 100644 --- a/hid-xpadneo/src/hid-xpadneo.c +++ b/hid-xpadneo/src/hid-xpadneo.c @@ -46,6 +46,12 @@ MODULE_PARM_DESC(disable_deadzones, "(bool) Disable dead zone handling for raw processing by Wine/Proton, confuses joydev. " "0: disable, 1: enable."); +static bool param_xpad_emulation = 0; +module_param_named(xpad_emulation, param_xpad_emulation, bool, 0444); +MODULE_PARM_DESC(xpad_emulation, + "(bool) Enable emulation of original Xpad360 USB controller, disables most advanced features. " + "0: disable, 1: enable."); + static struct { char *args[17]; unsigned int nargs; @@ -356,8 +362,13 @@ static int xpadneo_ff_play(struct input_dev *dev, void *data, struct ff_effect * break; case PARAM_TRIGGER_RUMBLE_PRESSURE: fraction_MAIN = percent_MAIN; - fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 511) / 1023; - fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 511) / 1023; + if (param_xpad_emulation) { + fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 127) / 255; + fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 127) / 255; + } else { + fraction_TL = (xdata->last_abs_z * percent_TRIGGERS + 511) / 1023; + fraction_TR = (xdata->last_abs_rz * percent_TRIGGERS + 511) / 1023; + } break; default: fraction_MAIN = percent_MAIN; @@ -674,17 +685,27 @@ static int xpadneo_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) { + struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); int i = 0; if (usage->hid == HID_DC_BATTERYSTRENGTH) { - struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); - xdata->battery.report_id = field->report->id; hid_info(hdev, "battery detected\n"); return MAP_IGNORE; } + /* force emulation of original USB Xpad360 */ + if (param_xpad_emulation) { + switch (usage->hid) { + case 0x10085: + case 0xC0223: + case 0x9000B: + hid_map_usage_clear(hi, usage, bit, max, EV_KEY, BTN_MODE); + return MAP_STATIC; + } + } + for (i = 0; i < ARRAY_SIZE(xpadneo_usage_maps); i++) { const struct usage_map *entry = &xpadneo_usage_maps[i]; @@ -806,6 +827,7 @@ static void xpadneo_switch_triggers(struct xpadneo_devdata *xdata, const u8 mode } } +#define AXIS(o) ((((u16)data[o+1])<<8)+data[o]) #define SWAP_BITS(v,b1,b2) \ (((v)>>(b1)&1)==((v)>>(b2)&1)?(v):(v^(1ULL<<(b1))^(1ULL<<(b2)))) static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report, @@ -852,6 +874,19 @@ static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report, data[16] = 0; } + /* trigger emulation of Xpad360 */ + if (param_xpad_emulation && reportsize >= 13) { + u16 axis; + /* left trigger */ + axis = AXIS(9) >> 2; + data[9] = (u8)(axis & 0xFF); + data[10] = (u8)(axis >> 8); + /* right trigger */ + axis = AXIS(11) >> 2; + data[11] = (u8)(axis & 0xFF); + data[12] = (u8)(axis >> 8); + } + /* swap button A with B and X with Y for Nintendo style controllers */ if ((xdata->quirks & XPADNEO_QUIRK_NINTENDO) && report->id == 1 && reportsize >= 15) { data[14] = SWAP_BITS(data[14], 0, 1); @@ -877,7 +912,7 @@ static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report, static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *hi) { struct xpadneo_devdata *xdata = hid_get_drvdata(hdev); - int deadzone = 3072, abs_min = 0, abs_max = 65535; + int deadzone = 3072, abs_min = 0, abs_max = 65535, abs_trigger = 1023, fuzz_trigger = 4; switch (hi->application) { case HID_GD_GAMEPAD: @@ -926,35 +961,43 @@ static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *h * still detect our driver as the correct model. Currently this * maps all controllers to the same model. */ - switch (xdata->gamepad->id.product) { - case 0x02E0: - xdata->gamepad->id.version = 0x00000903; - break; - case 0x02FD: - xdata->gamepad->id.product = 0x02E0; - break; - case 0x0B05: - case 0x0B13: - xdata->gamepad->id.product = 0x02E0; - xdata->gamepad->id.version = 0x00000903; - break; - } + if (param_xpad_emulation) { + xdata->gamepad->id.bustype = BUS_USB; + xdata->gamepad->id.product = 0x028E; + xdata->gamepad->id.version = 0x00000110; + hid_info(hdev, "emulating as Xpad360 USB device (0003:045E:028E:0110)"); + hid_warn(hdev, "most advanced features are disabled"); + } else { + switch (xdata->gamepad->id.product) { + case 0x02E0: + xdata->gamepad->id.version = 0x00000903; + break; + case 0x02FD: + xdata->gamepad->id.product = 0x02E0; + break; + case 0x0B05: + case 0x0B13: + xdata->gamepad->id.product = 0x02E0; + xdata->gamepad->id.version = 0x00000903; + break; + } - if (hdev->product != xdata->gamepad->id.product) - hid_info(hdev, - "pretending XB1S Windows wireless mode " - "(changed PID from 0x%04X to 0x%04X)\n", hdev->product, - (u16)xdata->gamepad->id.product); - - if (hdev->version != xdata->gamepad->id.version) - hid_info(hdev, - "working around wrong SDL2 mappings " - "(changed version from 0x%08X to 0x%08X)\n", hdev->version, - xdata->gamepad->id.version); - - if (param_disable_deadzones) { - hid_warn(hdev, "disabling dead zones\n"); - deadzone = 0; + if (hdev->product != xdata->gamepad->id.product) + hid_info(hdev, + "pretending XB1S Windows wireless mode " + "(changed PID from 0x%04X to 0x%04X)\n", hdev->product, + (u16)xdata->gamepad->id.product); + + if (hdev->version != xdata->gamepad->id.version) + hid_info(hdev, + "working around wrong SDL2 mappings " + "(changed version from 0x%08X to 0x%08X)\n", hdev->version, + xdata->gamepad->id.version); + + if (param_disable_deadzones) { + hid_warn(hdev, "disabling dead zones\n"); + deadzone = 0; + } } if (param_gamepad_compliance) { @@ -968,11 +1011,17 @@ static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *h input_set_abs_params(xdata->gamepad, ABS_RX, abs_min, abs_max, 32, deadzone); input_set_abs_params(xdata->gamepad, ABS_RY, abs_min, abs_max, 32, deadzone); - input_set_abs_params(xdata->gamepad, ABS_Z, 0, 1023, 4, 0); - input_set_abs_params(xdata->gamepad, ABS_RZ, 0, 1023, 4, 0); + /* Xpad360 emulation */ + if (param_xpad_emulation) { + abs_trigger = 255; + fuzz_trigger = 1; + } + + input_set_abs_params(xdata->gamepad, ABS_Z, 0, abs_trigger, fuzz_trigger, 0); + input_set_abs_params(xdata->gamepad, ABS_RZ, 0, abs_trigger, fuzz_trigger, 0); /* combine triggers to form a rudder, use ABS_MISC to order after dpad */ - input_set_abs_params(xdata->gamepad, ABS_MISC, -1023, 1023, 3, 63); + input_set_abs_params(xdata->gamepad, ABS_MISC, -abs_trigger, abs_trigger, fuzz_trigger, 0); /* do not report the consumer control buttons as part of the gamepad */ __clear_bit(BTN_XBOX, xdata->gamepad->keybit); @@ -1008,6 +1057,9 @@ static int xpadneo_event(struct hid_device *hdev, struct hid_field *field, xdata->last_abs_rz = value; goto combine_z_axes; } + } else if (param_xpad_emulation && (usage->type == EV_KEY) && (usage->code == BTN_MODE)) { + /* emulating Xpad360 */ + return 0; } else if ((usage->type == EV_KEY) && (usage->code == BTN_XBOX)) { /* * Handle the Xbox logo button: We want to cache the button