diff --git a/docs/README.md b/docs/README.md index a7a9a9be..3370b732 100644 --- a/docs/README.md +++ b/docs/README.md @@ -154,6 +154,21 @@ 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 + + ## Getting started ### Prerequisites diff --git a/hid-xpadneo/src/Makefile b/hid-xpadneo/src/Makefile index 475ffc79..e85ceaef 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 +hid-xpadneo-y += xpadneo/core.o xpadneo/consumer.o xpadneo/mouse.o diff --git a/hid-xpadneo/src/hid-xpadneo.c b/hid-xpadneo/src/hid-xpadneo.c index 3734059b..e61545ab 100644 --- a/hid-xpadneo/src/hid-xpadneo.c +++ b/hid-xpadneo/src/hid-xpadneo.c @@ -759,20 +759,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) { @@ -899,6 +885,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) { @@ -937,6 +924,9 @@ static int xpadneo_event(struct hid_device *hdev, struct hid_field *field, struct input_dev *gamepad = xdata->gamepad; struct input_dev *consumer = xdata->consumer; + if (xpadneo_mouse_event(xdata, usage, value)) + goto stop_processing; + if (usage->type == EV_ABS) { switch (usage->code) { case ABS_X: @@ -1207,6 +1197,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); @@ -1218,6 +1212,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; @@ -1237,6 +1234,7 @@ static void xpadneo_remove(struct hid_device *hdev) hid_hw_close(hdev); + 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 ff3788ed..def039f1 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" @@ -130,7 +131,7 @@ struct xpadneo_devdata { /* logical device interfaces */ struct hid_device *hdev; - struct input_dev *consumer, *gamepad, *keyboard; + struct input_dev *consumer, *gamepad, *keyboard, *mouse; short int missing_reported; /* quirk flags */ @@ -143,6 +144,13 @@ struct xpadneo_devdata { /* mouse mode */ bool mouse_mode; + struct timer_list mouse_timer; + struct { + s32 rel_x, rel_y, wheel_x, wheel_y; + struct { + bool left, right; + } analog_button; + } mouse_state; /* trigger scale */ struct { @@ -179,6 +187,10 @@ struct xpadneo_devdata { }; extern int xpadneo_init_consumer(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 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 b5658109..85c34029 100644 --- a/hid-xpadneo/src/xpadneo/consumer.c +++ b/hid-xpadneo/src/xpadneo/consumer.c @@ -21,6 +21,7 @@ extern int xpadneo_init_consumer(struct xpadneo_devdata *xdata) /* enable button events for mouse emulation */ input_set_capability(xdata->consumer, EV_KEY, BTN_XBOX); input_set_capability(xdata->consumer, EV_KEY, BTN_SHARE); + input_set_capability(xdata->consumer, EV_KEY, KEY_KEYBOARD); if (synth) { ret = input_register_device(xdata->consumer); diff --git a/hid-xpadneo/src/xpadneo/mouse.c b/hid-xpadneo/src/xpadneo/mouse.c new file mode 100644 index 00000000..eeef6896 --- /dev/null +++ b/hid-xpadneo/src/xpadneo/mouse.c @@ -0,0 +1,150 @@ +/* + * 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) +{ + 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) { + mouse_report_rel(REL_X, xdata->mouse_state.rel_x); + mouse_report_rel(REL_Y, xdata->mouse_state.rel_y); + mouse_report_rel(REL_HWHEEL, xdata->mouse_state.wheel_x); + mouse_report_rel(REL_WHEEL, xdata->mouse_state.wheel_y); + input_sync(xdata->mouse); + } + +} + +#define rescale_axis(v,d) (((v)<(d)&&(v)>-(d))?0:(32768*((v)>0?(v)-(d):(v)+(d))/(32768-(d)))) +extern int xpadneo_mouse_event(struct xpadneo_devdata *xdata, struct hid_usage *usage, __s32 value) +{ + 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) / 2048; + return 1; + case ABS_Y: + xdata->mouse_state.rel_y = rescale_axis(value - 32768, 3072) / 2048; + return 1; + case ABS_RX: + xdata->mouse_state.wheel_x = rescale_axis(value - 32768, 3072) / 8192; + return 1; + case ABS_RY: + xdata->mouse_state.wheel_y = rescale_axis(value - 32768, 3072) / 8192; + return 1; + case ABS_Z: + if (xdata->mouse_state.analog_button.left && value < 384) { + xdata->mouse_state.analog_button.left = false; + input_report_key(xdata->mouse, BTN_LEFT, 0); + input_sync(xdata->mouse); + } else if (!xdata->mouse_state.analog_button.left && value > 640) { + xdata->mouse_state.analog_button.left = true; + input_report_key(xdata->mouse, BTN_LEFT, 1); + input_sync(xdata->mouse); + } + return 1; + case ABS_RZ: + if (xdata->mouse_state.analog_button.right && value < 384) { + xdata->mouse_state.analog_button.right = false; + input_report_key(xdata->mouse, BTN_RIGHT, 0); + input_sync(xdata->mouse); + } else if (!xdata->mouse_state.analog_button.right && value > 640) { + xdata->mouse_state.analog_button.right = true; + input_report_key(xdata->mouse, BTN_RIGHT, 1); + input_sync(xdata->mouse); + } + return 1; + } + } else if (usage->type == EV_KEY) { + switch (usage->code) { + case BTN_TL: + input_report_key(xdata->mouse, BTN_SIDE, value); + input_sync(xdata->mouse); + return 1; + case BTN_TR: + input_report_key(xdata->mouse, BTN_EXTRA, value); + input_sync(xdata->mouse); + return 1; + case BTN_START: + if (xdata->consumer) { + input_report_key(xdata->consumer, KEY_KEYBOARD, value); + input_sync(xdata->consumer); + } + return 1; + } + } + + return 0; +} + +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; +}