diff --git a/docs/README.md b/docs/README.md index b27debeb..1e84f56b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -91,6 +91,7 @@ PID 0x0B22 while the other models identify with PID 0x0B13. This has some known * Supports customization through profiles (work in progress) * Optional high-precision mode for Wine/Proton users (disables dead zones so games don't apply an additional one) * Share button support on supported controllers +* Works as a mouse if you're are in couch-mode (press Guide+Select) ## Unavailable Features @@ -218,6 +219,24 @@ or Y while holding down the Xbox logo button. However, the following caveats app - If you hold the button for too long, the controller will turn off - we cannot prevent that. +### Mouse Profile Support + +The driver can switch to emulating a mouse (and limited keyboard) on all supported controllers. Press +Guide+Select to switch to mouse mode or back to controller mode: + +- Left stick moves the mouse pointer +- Right stick can be used as a scrolling wheel/ball +- Triggers for left and right mouse button +- Shoulder buttons for back and forward button +- D-pad for cursor movement +- Menu to show on-screen keyboard (FIXME possible? KEY_KEYBOARD) +- A for Enter +- B for Escape + +**Important:** The mouse profile won't work if you disabled the shift-mode of the Xbox logo button (module parameter +`disable_shift_mode`). + + ## Getting Started ### Distribution Packages diff --git a/hid-xpadneo/src/Makefile b/hid-xpadneo/src/Makefile index cd0ea486..b00f1520 100644 --- a/hid-xpadneo/src/Makefile +++ b/hid-xpadneo/src/Makefile @@ -9,4 +9,4 @@ hid-xpadneo-y += xpadneo.o $(obj)/xpadneo.c: $(src)/hid-xpadneo.c cp $< $@ -hid-xpadneo-y += xpadneo/core.o xpadneo/consumer.o xpadneo/keyboard.o +hid-xpadneo-y += xpadneo/core.o xpadneo/consumer.o xpadneo/keyboard.o xpadneo/mouse.o diff --git a/hid-xpadneo/src/hid-xpadneo.c b/hid-xpadneo/src/hid-xpadneo.c index bede173a..1823860f 100644 --- a/hid-xpadneo/src/hid-xpadneo.c +++ b/hid-xpadneo/src/hid-xpadneo.c @@ -709,20 +709,6 @@ static u8 *xpadneo_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int return rdesc; } -static void xpadneo_toggle_mouse(struct xpadneo_devdata *xdata) -{ - if (xdata->mouse_mode) { - xdata->mouse_mode = false; - hid_info(xdata->hdev, "mouse mode disabled\n"); - } else { - xdata->mouse_mode = true; - hid_info(xdata->hdev, "mouse mode enabled\n"); - } - - /* Indicate that a request was made */ - xdata->profile_switched = true; -} - static void xpadneo_switch_profile(struct xpadneo_devdata *xdata, const u8 profile, const bool emulated) { @@ -829,6 +815,9 @@ static int xpadneo_raw_event(struct hid_device *hdev, struct hid_report *report, } } + if (xpadneo_mouse_raw_event(xdata, report, data, reportsize)) + return -1; + return 0; } @@ -861,6 +850,7 @@ static int xpadneo_input_configured(struct hid_device *hdev, struct hid_input *h return 0; default: hid_warn(hdev, "unhandled input application 0x%x\n", hi->application); + return 0; } if (param_disable_deadzones) { @@ -910,6 +900,9 @@ static int xpadneo_event(struct hid_device *hdev, struct hid_field *field, struct input_dev *gamepad = xdata->gamepad; struct input_dev *keyboard = xdata->keyboard; + if (xpadneo_mouse_event(xdata, usage, value)) + goto stop_processing; + if ((usage->type == EV_KEY) && (usage->code == BTN_PADDLES(0))) { if (gamepad && xdata->profile == 0) { /* report the paddles individually */ @@ -1201,6 +1194,10 @@ static int xpadneo_probe(struct hid_device *hdev, const struct hid_device_id *id if (ret) return ret; + ret = xpadneo_init_mouse(xdata); + if (ret) + return ret; + ret = xpadneo_init_hw(hdev); if (ret) { hid_err(hdev, "hw init failed: %d\n", ret); @@ -1212,6 +1209,9 @@ static int xpadneo_probe(struct hid_device *hdev, const struct hid_device_id *id if (ret) hid_err(hdev, "could not initialize ff, continuing anyway\n"); + timer_setup(&xdata->mouse_timer, xpadneo_mouse_report, 0); + mod_timer(&xdata->mouse_timer, jiffies); + hid_info(hdev, "%s connected\n", xdata->battery.name); return 0; @@ -1247,6 +1247,7 @@ static void xpadneo_remove(struct hid_device *hdev) hdev->product = xdata->original_product; } + del_timer_sync(&xdata->mouse_timer); cancel_delayed_work_sync(&xdata->ff_worker); kfree(xdata->battery.name); diff --git a/hid-xpadneo/src/xpadneo.h b/hid-xpadneo/src/xpadneo.h index 727cbc5e..68ce02ba 100644 --- a/hid-xpadneo/src/xpadneo.h +++ b/hid-xpadneo/src/xpadneo.h @@ -12,6 +12,7 @@ #define XPADNEO_H_FILE #include +#include #include #include "hid-ids.h" @@ -138,8 +139,8 @@ struct xpadneo_devdata { /* logical device interfaces */ struct hid_device *hdev; - struct input_dev *consumer, *gamepad, *keyboard; - bool consumer_sync, gamepad_sync, keyboard_sync; + struct input_dev *consumer, *gamepad, *keyboard, *mouse; + bool consumer_sync, gamepad_sync, keyboard_sync, mouse_sync; short int missing_reported; /* revert fixups on removal */ @@ -156,6 +157,14 @@ struct xpadneo_devdata { /* mouse mode */ bool mouse_mode; + struct timer_list mouse_timer; + struct { + s32 rel_x, rel_y, wheel_x, wheel_y; + s32 rel_x_err, rel_y_err, wheel_x_err, wheel_y_err; + struct { + bool left, right; + } analog_button; + } mouse_state; /* trigger scale */ struct { @@ -193,7 +202,12 @@ struct xpadneo_devdata { extern int xpadneo_init_consumer(struct xpadneo_devdata *); extern int xpadneo_init_keyboard(struct xpadneo_devdata *); +extern int xpadneo_init_mouse(struct xpadneo_devdata *); extern int xpadneo_init_synthetic(struct xpadneo_devdata *, char *, struct input_dev **); +extern int xpadneo_mouse_event(struct xpadneo_devdata *, struct hid_usage *, __s32); +extern int xpadneo_mouse_raw_event(struct xpadneo_devdata *, struct hid_report *, u8 *, int); extern void xpadneo_report(struct hid_device *, struct hid_report *); +extern void xpadneo_mouse_report(struct timer_list *); +extern void xpadneo_toggle_mouse(struct xpadneo_devdata *); #endif diff --git a/hid-xpadneo/src/xpadneo/consumer.c b/hid-xpadneo/src/xpadneo/consumer.c index 95c1818c..05d623f0 100644 --- a/hid-xpadneo/src/xpadneo/consumer.c +++ b/hid-xpadneo/src/xpadneo/consumer.c @@ -18,6 +18,9 @@ extern int xpadneo_init_consumer(struct xpadneo_devdata *xdata) return ret; } + /* enable consumer events for mouse mode */ + input_set_capability(xdata->consumer, EV_KEY, KEY_ONSCREEN_KEYBOARD); + if (synth) { ret = input_register_device(xdata->consumer); if (ret) { diff --git a/hid-xpadneo/src/xpadneo/core.c b/hid-xpadneo/src/xpadneo/core.c index a338edc5..1c615541 100644 --- a/hid-xpadneo/src/xpadneo/core.c +++ b/hid-xpadneo/src/xpadneo/core.c @@ -54,4 +54,9 @@ extern void xpadneo_report(struct hid_device *hdev, struct hid_report *report) xdata->keyboard_sync = false; input_sync(xdata->keyboard); } + + if (xdata->mouse && xdata->mouse_sync) { + xdata->mouse_sync = false; + input_sync(xdata->mouse); + } } diff --git a/hid-xpadneo/src/xpadneo/keyboard.c b/hid-xpadneo/src/xpadneo/keyboard.c index d554143c..53067f0e 100644 --- a/hid-xpadneo/src/xpadneo/keyboard.c +++ b/hid-xpadneo/src/xpadneo/keyboard.c @@ -21,6 +21,14 @@ extern int xpadneo_init_keyboard(struct xpadneo_devdata *xdata) /* enable key events for keyboard */ input_set_capability(xdata->keyboard, EV_KEY, BTN_SHARE); + /* enable key events for mouse mode */ + input_set_capability(xdata->keyboard, EV_KEY, KEY_ESC); + input_set_capability(xdata->keyboard, EV_KEY, KEY_ENTER); + input_set_capability(xdata->keyboard, EV_KEY, KEY_UP); + input_set_capability(xdata->keyboard, EV_KEY, KEY_LEFT); + input_set_capability(xdata->keyboard, EV_KEY, KEY_RIGHT); + input_set_capability(xdata->keyboard, EV_KEY, KEY_DOWN); + if (synth) { ret = input_register_device(xdata->keyboard); if (ret) { diff --git a/hid-xpadneo/src/xpadneo/mouse.c b/hid-xpadneo/src/xpadneo/mouse.c new file mode 100644 index 00000000..54f03c1a --- /dev/null +++ b/hid-xpadneo/src/xpadneo/mouse.c @@ -0,0 +1,232 @@ +/* + * xpadneo mouse driver + * + * Copyright (c) 2021 Kai Krakow + */ + +#include "../xpadneo.h" + +extern void xpadneo_toggle_mouse(struct xpadneo_devdata *xdata) +{ + if (!xdata->mouse) { + xdata->mouse_mode = false; + hid_info(xdata->hdev, "mouse not available\n"); + } else if (xdata->mouse_mode) { + xdata->mouse_mode = false; + hid_info(xdata->hdev, "mouse mode disabled\n"); + } else { + xdata->mouse_mode = true; + hid_info(xdata->hdev, "mouse mode enabled\n"); + } + + /* Indicate that a request was made */ + xdata->profile_switched = true; +} + +#define mouse_report_rel(a,v) if((v)!=0)input_report_rel(mouse,(a),(v)) +extern void xpadneo_mouse_report(struct timer_list *t) +{ + __s32 value; + + struct xpadneo_devdata *xdata = from_timer(xdata, t, mouse_timer); + struct input_dev *mouse = xdata->mouse; + + mod_timer(&xdata->mouse_timer, jiffies + msecs_to_jiffies(10)); + + if (xdata->mouse_mode) { + value = xdata->mouse_state.rel_x + xdata->mouse_state.rel_x_err; + xdata->mouse_state.rel_x_err = value % 1024; + mouse_report_rel(REL_X, value / 1024); + + value = xdata->mouse_state.rel_y + xdata->mouse_state.rel_y_err; + xdata->mouse_state.rel_y_err = value % 1024; + mouse_report_rel(REL_Y, value / 1024); + + value = xdata->mouse_state.wheel_x + xdata->mouse_state.wheel_x_err; + xdata->mouse_state.wheel_x_err = value % 16384; + mouse_report_rel(REL_HWHEEL, value / 16384); + + value = xdata->mouse_state.wheel_y + xdata->mouse_state.wheel_y_err; + xdata->mouse_state.wheel_y_err = value % 16384; + mouse_report_rel(REL_WHEEL, value / 16384); + + input_sync(xdata->mouse); + } + +} + +extern int xpadneo_mouse_raw_event(struct xpadneo_devdata *xdata, struct hid_report *report, + u8 *data, int reportsize) +{ + if (!xdata->mouse_mode) + return 0; + + /* do nothing for now */ + return 0; +} + +#define rescale_axis(v,d) (((v)<(d)&&(v)>-(d))?0:(32768*((v)>0?(v)-(d):(v)+(d))/(32768-(d)))) +#define digipad(v,v1,v2,v3) (((v==(v1))||(v==(v2))||(v==(v3)))?1:0) +extern int xpadneo_mouse_event(struct xpadneo_devdata *xdata, struct hid_usage *usage, __s32 value) +{ + struct hid_device *hdev = xdata->hdev; + struct input_dev *consumer = xdata->consumer; + struct input_dev *keyboard = xdata->keyboard; + struct input_dev *mouse = xdata->mouse; + + if (!xdata->mouse_mode) + return 0; + + if (usage->type == EV_ABS) { + switch (usage->code) { + case ABS_X: + xdata->mouse_state.rel_x = rescale_axis(value - 32768, 3072); + return 1; + case ABS_Y: + xdata->mouse_state.rel_y = rescale_axis(value - 32768, 3072); + return 1; + case ABS_RX: + xdata->mouse_state.wheel_x = rescale_axis(value - 32768, 3072); + return 1; + case ABS_RY: + xdata->mouse_state.wheel_y = rescale_axis(value - 32768, 3072); + return 1; + case ABS_RZ: + if (xdata->mouse_state.analog_button.left && value < 384) { + xdata->mouse_state.analog_button.left = false; + input_report_key(mouse, BTN_LEFT, 0); + xdata->mouse_sync = true; + } else if (!xdata->mouse_state.analog_button.left && value > 640) { + xdata->mouse_state.analog_button.left = true; + input_report_key(mouse, BTN_LEFT, 1); + xdata->mouse_sync = true; + } + return 1; + case ABS_Z: + if (xdata->mouse_state.analog_button.right && value < 384) { + xdata->mouse_state.analog_button.right = false; + input_report_key(mouse, BTN_RIGHT, 0); + xdata->mouse_sync = true; + } else if (!xdata->mouse_state.analog_button.right && value > 640) { + xdata->mouse_state.analog_button.right = true; + input_report_key(mouse, BTN_RIGHT, 1); + xdata->mouse_sync = true; + } + return 1; + case ABS_HAT0X: + /* reports 8 directions: 1 = up, 3 = right, down = 5, left = 7 */ + if (xdata->keyboard) { + input_report_key(keyboard, KEY_UP, digipad(value, 8, 1, 2)); + input_report_key(keyboard, KEY_RIGHT, digipad(value, 2, 3, 4)); + input_report_key(keyboard, KEY_DOWN, digipad(value, 4, 5, 6)); + input_report_key(keyboard, KEY_LEFT, digipad(value, 6, 7, 8)); + xdata->keyboard_sync = true; + } + return 1; + } + } else if (usage->type == EV_KEY) { + switch (usage->code) { + case BTN_A: + if (!keyboard) + goto keyboard_missing; + input_report_key(keyboard, KEY_ENTER, value); + xdata->keyboard_sync = true; + return 1; + case BTN_B: + if (!keyboard) + goto keyboard_missing; + input_report_key(keyboard, KEY_ESC, value); + xdata->keyboard_sync = true; + return 1; + case BTN_X: + if (!consumer) + goto consumer_missing; + input_report_key(consumer, KEY_ONSCREEN_KEYBOARD, value); + xdata->consumer_sync = true; + return 1; + case BTN_Y: + input_report_key(mouse, BTN_MIDDLE, value); + xdata->mouse_sync = true; + return 1; + case BTN_TL: + input_report_key(mouse, BTN_SIDE, value); + xdata->mouse_sync = true; + return 1; + case BTN_TR: + input_report_key(mouse, BTN_EXTRA, value); + xdata->mouse_sync = true; + return 1; + case BTN_SELECT: + input_report_key(mouse, BTN_BACK, value); + xdata->mouse_sync = true; + return 1; + case BTN_START: + input_report_key(mouse, BTN_FORWARD, value); + xdata->mouse_sync = true; + return 1; + case BTN_SHARE: + input_report_key(mouse, BTN_TASK, value); + xdata->mouse_sync = true; + return 1; + } + } + + return 0; + +keyboard_missing: + if ((xdata->missing_reported && XPADNEO_MISSING_KEYBOARD) == 0) { + xdata->missing_reported |= XPADNEO_MISSING_KEYBOARD; + hid_err(hdev, "keyboard not detected\n"); + } + return 1; + +consumer_missing: + if ((xdata->missing_reported && XPADNEO_MISSING_CONSUMER) == 0) { + xdata->missing_reported |= XPADNEO_MISSING_CONSUMER; + hid_err(hdev, "consumer control not detected\n"); + } + return 1; +} + +extern int xpadneo_init_mouse(struct xpadneo_devdata *xdata) +{ + struct hid_device *hdev = xdata->hdev; + int ret, synth = 0; + + if (!xdata->mouse) { + synth = 1; + ret = xpadneo_init_synthetic(xdata, "Mouse", &xdata->mouse); + if (ret || !xdata->mouse) + return ret; + } + + /* enable relative events for mouse emulation */ + __set_bit(EV_REL, xdata->mouse->evbit); + __set_bit(REL_X, xdata->mouse->relbit); + __set_bit(REL_Y, xdata->mouse->relbit); + __set_bit(REL_HWHEEL, xdata->mouse->relbit); + __set_bit(REL_WHEEL, xdata->mouse->relbit); + + /* enable button events for mouse emulation */ + __set_bit(EV_KEY, xdata->mouse->evbit); + __set_bit(BTN_LEFT, xdata->mouse->keybit); + __set_bit(BTN_RIGHT, xdata->mouse->keybit); + __set_bit(BTN_MIDDLE, xdata->mouse->keybit); + __set_bit(BTN_SIDE, xdata->mouse->keybit); + __set_bit(BTN_EXTRA, xdata->mouse->keybit); + __set_bit(BTN_FORWARD, xdata->mouse->keybit); + __set_bit(BTN_BACK, xdata->mouse->keybit); + __set_bit(BTN_TASK, xdata->mouse->keybit); + + if (synth) { + ret = input_register_device(xdata->mouse); + if (ret) { + hid_err(hdev, "failed to register mouse\n"); + return ret; + } + + hid_info(hdev, "mouse added\n"); + } + + return 0; +}