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