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;
+}