Browse Source

[Core] Split support for pointing devices. (#15304)

* Draft implementation

* formatting

* fix combined buttons

* remove pimoroni throttle

* sync pointing on a throttle loop with checksum

* no longer used

* doh

Co-authored-by: Drashna Jaelre <drashna@live.com>

* switch pimoroni to a cpi equivalent

* add cpi support

* allow user modification of seperate mouse reports

* a little tidy up

* add *_RIGHT defines.

* docs

* doxygen comments

* basic changelog

* clean up pimoroni

* small doc fixes

* Update docs/feature_pointing_device.md

Co-authored-by: Drashna Jaelre <drashna@live.com>

* performance tweak if side has usb

* Don't run init funtions on wrong side

* renamed some variables for consistency

* fix pimoroni typos

* Clamp instead of OR

* Promote combined values to uint16_t

* Update pointing_device.c

Co-authored-by: Drashna Jaelre <drashna@live.com>
Co-authored-by: Nick Brassel <nick@tzarc.org>
Dasky 3 years ago
parent
commit
7f7364c559

+ 13 - 0
docs/ChangeLog/20220226/PR15304.md

@@ -0,0 +1,13 @@
+### Split Common core now supports Pointing Devices ([#15304](https://github.com/qmk/qmk_firmware/pull/15304))
+
+Pointing devices can now be shared across a split keyboard with support for a single pointing device or a pointing device on each side.
+
+This feature can be enabled with `#define SPLIT_POINTING_ENABLE` and one of the following options:
+
+| Setting                   | Description                        |
+|---------------------------|------------------------------------|
+|`POINTING_DEVICE_LEFT`     | Pointing device on the left side   |
+|`POINTING_DEVICE_RIGHT`    | Pointing device on the right side  |
+|`POINTING_DEVICE_COMBINED` | Pointing device on both sides      |
+
+See the [Pointing Device](../feature_pointing_device.md) documentation for further configuration options.

+ 105 - 11
docs/feature_pointing_device.md

@@ -127,11 +127,10 @@ The Pimoroni Trackball module is a I2C based breakout board with an RGB enable t
 | Setting                             | Description                                                                        | Default |
 |-------------------------------------|------------------------------------------------------------------------------------|---------|
 |`PIMORONI_TRACKBALL_ADDRESS`         | (Required) Sets the I2C Address for the Pimoroni Trackball.                        | `0x0A`  |
-|`PIMORONI_TRACKBALL_TIMEOUT`         | (Optional) The timeout for i2c communication with the trackpad in milliseconds.    | `100`   |
-|`PIMORONI_TRACKBALL_INTERVAL_MS`     | (Optional) The update/read interval for the sensor in milliseconds.                | `8`     |
+|`PIMORONI_TRACKBALL_TIMEOUT`         | (Optional) The timeout for i2c communication with the trackball in milliseconds.   | `100`   |
 |`PIMORONI_TRACKBALL_SCALE`           | (Optional) The multiplier used to generate reports from the sensor.                | `5`     |
 |`PIMORONI_TRACKBALL_DEBOUNCE_CYCLES` | (Optional) The number of scan cycles used for debouncing on the ball press.        | `20`    |
-|`PIMORONI_TRACKBALL_ERROR_COUNT`     | (Optional) Specifies the number of read/write errors until the sensor is disabled. | `10`  |
+|`PIMORONI_TRACKBALL_ERROR_COUNT`     | (Optional) Specifies the number of read/write errors until the sensor is disabled. | `10`    |
 
 ### PMW 3360 Sensor
 
@@ -171,14 +170,35 @@ void           pointing_device_driver_set_cpi(uint16_t cpi) {}
 
 ## Common Configuration
 
-| Setting                       | Description                                                           | Default       |
-|-------------------------------|-----------------------------------------------------------------------|---------------|
-|`POINTING_DEVICE_ROTATION_90`  | (Optional) Rotates the X and Y data by  90 degrees.                   | _not defined_ |
-|`POINTING_DEVICE_ROTATION_180` | (Optional) Rotates the X and Y data by 180 degrees.                   | _not defined_ |
-|`POINTING_DEVICE_ROTATION_270` | (Optional) Rotates the X and Y data by 270 degrees.                   | _not defined_ |
-|`POINTING_DEVICE_INVERT_X`     | (Optional) Inverts the X axis report.                                 | _not defined_ |
-|`POINTING_DEVICE_INVERT_Y`     | (Optional) Inverts the Y axis report.                                 | _not defined_ |
-|`POINTING_DEVICE_MOTION_PIN`   | (Optional) If supported, will only read from sensor if pin is active. | _not defined_ |
+| Setting                          | Description                                                           | Default           |
+|----------------------------------|-----------------------------------------------------------------------|-------------------|
+|`POINTING_DEVICE_ROTATION_90`     | (Optional) Rotates the X and Y data by  90 degrees.                   | _not defined_     |
+|`POINTING_DEVICE_ROTATION_180`    | (Optional) Rotates the X and Y data by 180 degrees.                   | _not defined_     |
+|`POINTING_DEVICE_ROTATION_270`    | (Optional) Rotates the X and Y data by 270 degrees.                   | _not defined_     |
+|`POINTING_DEVICE_INVERT_X`        | (Optional) Inverts the X axis report.                                 | _not defined_     |
+|`POINTING_DEVICE_INVERT_Y`        | (Optional) Inverts the Y axis report.                                 | _not defined_     |
+|`POINTING_DEVICE_MOTION_PIN`      | (Optional) If supported, will only read from sensor if pin is active. | _not defined_     |
+|`POINTING_DEVICE_TASK_THROTTLE_MS`      | (Optional) Limits the frequency that the sensor is polled for motion. | _not defined_     |
+
+!> When using `SPLIT_POINTING_ENABLE` the `POINTING_DEVICE_MOTION_PIN` functionality is not supported and would recommend `POINTING_DEVICE_TASK_THROTTLE_MS` be set to `1`. Increasing this value will increase transport performance at the cost of possible mouse responsiveness.
+
+
+## Split Keyboard Configuration
+
+The following configuration options are only available when using `SPLIT_POINTING_ENABLE` see [data sync options](feature_split_keyboard.md?id=data-sync-options). The rotation and invert `*_RIGHT` options are only used with `POINTING_DEVICE_COMBINED`. If using `POINTING_DEVICE_LEFT` or `POINTING_DEVICE_RIGHT` use the common configuration above to configure your pointing device.
+
+| Setting                                | Description                                                           | Default       |
+|----------------------------------------|-----------------------------------------------------------------------|---------------|
+|`POINTING_DEVICE_LEFT`                  | Pointing device on the left side (Required - pick one only)           | _not defined_ |
+|`POINTING_DEVICE_RIGHT`                 | Pointing device on the right side (Required - pick one only)          | _not defined_ |
+|`POINTING_DEVICE_COMBINED`              | Pointing device on both sides (Required - pick one only)              | _not defined_ |
+|`POINTING_DEVICE_ROTATION_90_RIGHT`     | (Optional) Rotates the X and Y data by  90 degrees.                   | _not defined_ |
+|`POINTING_DEVICE_ROTATION_180_RIGHT`    | (Optional) Rotates the X and Y data by 180 degrees.                   | _not defined_ |
+|`POINTING_DEVICE_ROTATION_270_RIGHT`    | (Optional) Rotates the X and Y data by 270 degrees.                   | _not defined_ |
+|`POINTING_DEVICE_INVERT_X_RIGHT`        | (Optional) Inverts the X axis report.                                 | _not defined_ |
+|`POINTING_DEVICE_INVERT_Y_RIGHT`        | (Optional) Inverts the Y axis report.                                 | _not defined_ |
+
+!> If there is a `_RIGHT` configuration option or callback, the [common configuration](feature_pointing_device.md?id=common-configuration) option will work for the left. For correct left/right detection you should setup a [handedness option](feature_split_keyboard?id=setting-handedness), `EE_HANDS` is usually a good option for an existing board that doesn't do handedness by hardware.
 
 
 ## Callbacks and Functions 
@@ -196,6 +216,21 @@ void           pointing_device_driver_set_cpi(uint16_t cpi) {}
 | `pointing_device_set_report(mouse_report)`                 | Sets the mouse report to the assigned `mouse_report_t` data structured passed to the function.                | 
 | `pointing_device_send(void)`                               | Sends the current mouse report to the host system.  Function can be replaced.                                 | 
 | `has_mouse_report_changed(old, new)`                       | Compares the old and new `mouse_report_t` data and returns true only if it has changed.                       |
+| `pointing_device_adjust_by_defines(mouse_report)`          | Applies rotations and invert configurations to a raw mouse report.                                             |
+
+
+## Split Keyboard Callbacks and Functions
+
+The combined functions below are only available when using `SPLIT_POINTING_ENABLE` and `POINTING_DEVICE_COMBINED`. The 2 callbacks `pointing_device_task_combined_*` replace the single sided equivalents above. See the [combined pointing devices example](feature_pointing_device.md?id=combined-pointing-devices)
+
+| Function                                                        | Description                                                                                                              |
+|-----------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
+| `pointing_device_set_shared_report(mouse_report)`               | Sets the shared mouse report to the assigned `mouse_report_t` data structured passed to the function.                    |
+| `pointing_device_set_cpi_on_side(bool, uint16_t)`               | Sets the CPI/DPI of one side, if supported. Passing `true` will set the left and `false` the right`                      |
+| `pointing_device_combine_reports(left_report, right_report)`    | Returns a combined mouse_report of left_report and right_report (as a `mouse_report_t` data structure)                   |
+| `pointing_device_task_combined_kb(left_report, right_report)`   | Callback, so keyboard code can intercept and modify the data. Returns a combined mouse report.                           |
+| `pointing_device_task_combined_user(left_report, right_report)` | Callback, so user code can intercept and modify. Returns a combined mouse report using `pointing_device_combine_reports` |
+| `pointing_device_adjust_by_defines_right(mouse_report)`         | Applies right side rotations and invert configurations to a raw mouse report.                                            |
 
 
 # Manipulating Mouse Reports
@@ -242,3 +277,62 @@ case MS_SPECIAL:
 ```
 
 Recall that the mouse report is set to zero (except the buttons) whenever it is sent, so the scrolling would only occur once in each case.
+
+## Split Examples
+
+The following examples make use the `SPLIT_POINTING_ENABLE` functionality and show how to manipulate the mouse report for a scrolling mode.
+
+### Single Pointing Device
+
+The following example will work with either `POINTING_DEVICE_LEFT` or `POINTING_DEVICE_RIGHT` and enables scrolling mode while on a particular layer.
+
+```c
+
+static bool scrolling_mode = false;
+
+layer_state_t layer_state_set_user(layer_state_t state) {
+    switch (get_highest_layer(state)) {
+        case _RAISE:  // If we're on the _RAISE layer enable scrolling mode
+            scrolling_mode = true;
+            pointing_device_set_cpi(2000);
+            break;
+        default:
+            if (scrolling_mode) {  // check if we were scrolling before and set disable if so
+                scrolling_mode = false;
+                pointing_device_set_cpi(8000);
+            }
+            break;
+    }
+    return state;
+}
+
+report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) {
+    if (scrolling_mode) {
+        mouse_report.h = mouse_report.x;
+        mouse_report.v = mouse_report.y;
+        mouse_report.x = 0;
+        mouse_report.y = 0;
+    }
+    return mouse_report;
+}
+
+```
+
+### Combined Pointing Devices
+
+The following example requires `POINTING_DEVICE_COMBINED` and sets the left side pointing device to scroll only.
+
+```c
+void keyboard_post_init_user(void) {
+    pointing_device_set_cpi_on_side(true, 1000); //Set cpi on left side to a low value for slower scrolling.
+    pointing_device_set_cpi_on_side(false, 8000); //Set cpi on right side to a reasonable value for mousing.
+}
+
+report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report) {
+    left_report.h = left_report.x;
+    left_report.v = left_report.y;
+    left_report.x = 0;
+    left_report.y = 0;
+    return pointing_device_combine_reports(left_report, right_report);
+}
+```

+ 8 - 0
docs/feature_split_keyboard.md

@@ -266,6 +266,14 @@ This enables transmitting the current OLED on/off status to the slave side of th
 
 This enables transmitting the current ST7565 on/off status to the slave side of the split keyboard. The purpose of this feature is to support state (on/off state only) syncing.
 
+```c
+#define SPLIT_POINTING_ENABLE
+```
+
+This enables transmitting the pointing device status to the master side of the split keyboard. The purpose of this feature is to enable use pointing devices on the slave side. 
+
+!> There is additional required configuration for `SPLIT_POINTING_ENABLE` outlined in the [pointing device documentation](feature_pointing_device.md?id=split-keyboard-configuration).
+
 ### Custom data sync between sides :id=custom-data-sync
 
 QMK's split transport allows for arbitrary data transactions at both the keyboard and user levels. This is modelled on a remote procedure call, with the master invoking a function on the slave side, with the ability to send data from master to slave, process it slave side, and send data back from slave to master.

+ 19 - 3
drivers/sensors/pimoroni_trackball.c

@@ -33,8 +33,24 @@
 
 static uint16_t precision = 128;
 
-float pimoroni_trackball_get_precision(void) { return ((float)precision / 128); }
-void  pimoroni_trackball_set_precision(float floatprecision) { precision = (floatprecision * 128); }
+uint16_t pimoroni_trackball_get_cpi(void) { return (precision * 125); }
+/**
+ * @brief Sets the scaling value for pimoroni trackball
+ *
+ * Sets a scaling value for pimoroni trackball to allow runtime adjustment. This isn't used by the sensor and is an
+ * approximation so the functions are consistent across drivers.
+ *
+ * NOTE: This rounds down to the nearest number divisable by 125 that's a positive integer, values below 125 are clamped to 125.
+ *
+ * @param cpi uint16_t
+ */
+void pimoroni_trackball_set_cpi(uint16_t cpi) {
+    if (cpi < 249) {
+        precision = 1;
+    } else {
+        precision = (cpi - (cpi % 125)) / 125;
+    }
+}
 
 void pimoroni_trackball_set_rgbw(uint8_t r, uint8_t g, uint8_t b, uint8_t w) {
     uint8_t                              data[4] = {r, g, b, w};
@@ -60,7 +76,7 @@ i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data) {
     return status;
 }
 
-__attribute__((weak)) void pimironi_trackball_device_init(void) {
+__attribute__((weak)) void pimoroni_trackball_device_init(void) {
     i2c_init();
     pimoroni_trackball_set_rgbw(0x00, 0x00, 0x00, 0x00);
 }

+ 3 - 6
drivers/sensors/pimoroni_trackball.h

@@ -23,9 +23,6 @@
 #ifndef PIMORONI_TRACKBALL_ADDRESS
 #    define PIMORONI_TRACKBALL_ADDRESS 0x0A
 #endif
-#ifndef PIMORONI_TRACKBALL_INTERVAL_MS
-#    define PIMORONI_TRACKBALL_INTERVAL_MS 8
-#endif
 #ifndef PIMORONI_TRACKBALL_SCALE
 #    define PIMORONI_TRACKBALL_SCALE 5
 #endif
@@ -52,10 +49,10 @@ typedef struct {
     uint8_t click;
 } pimoroni_data_t;
 
-void         pimironi_trackball_device_init(void);
+void         pimoroni_trackball_device_init(void);
 void         pimoroni_trackball_set_rgbw(uint8_t red, uint8_t green, uint8_t blue, uint8_t white);
 int16_t      pimoroni_trackball_get_offsets(uint8_t negative_dir, uint8_t positive_dir, uint8_t scale);
 void         pimoroni_trackball_adapt_values(int8_t* mouse, int16_t* offset);
-float        pimoroni_trackball_get_precision(void);
-void         pimoroni_trackball_set_precision(float precision);
+uint16_t     pimoroni_trackball_get_cpi(void);
+void         pimoroni_trackball_set_cpi(uint16_t cpi);
 i2c_status_t read_pimoroni_trackball(pimoroni_data_t* data);

+ 356 - 33
quantum/pointing_device.c

@@ -18,24 +18,105 @@
 
 #include "pointing_device.h"
 #include <string.h>
+#include "timer.h"
 #ifdef MOUSEKEY_ENABLE
 #    include "mousekey.h"
 #endif
 #if (defined(POINTING_DEVICE_ROTATION_90) + defined(POINTING_DEVICE_ROTATION_180) + defined(POINTING_DEVICE_ROTATION_270)) > 1
 #    error More than one rotation selected.  This is not supported.
 #endif
+#if defined(SPLIT_POINTING_ENABLE)
+#    include "transactions.h"
+#    include "keyboard.h"
 
-static report_mouse_t mouseReport = {};
+report_mouse_t shared_mouse_report = {};
+uint16_t       shared_cpi          = 0;
+
+/**
+ * @brief Sets the shared mouse report used be pointing device task
+ *
+ * NOTE : Only available when using SPLIT_POINTING_ENABLE
+ *
+ * @param[in] new_mouse_report report_mouse_t
+ */
+void pointing_device_set_shared_report(report_mouse_t new_mouse_report) { shared_mouse_report = new_mouse_report; }
+
+/**
+ * @brief Gets current pointing device CPI if supported
+ *
+ * Gets current cpi of the shared report and returns it as uint16_t
+ *
+ * NOTE : Only available when using SPLIT_POINTING_ENABLE
+ *
+ * @return cpi value as uint16_t
+ */
+uint16_t pointing_device_get_shared_cpi(void) { return shared_cpi; }
+
+#    if defined(POINTING_DEVICE_LEFT)
+#        define POINTING_DEVICE_THIS_SIDE is_keyboard_left()
+#    elif defined(POINTING_DEVICE_RIGHT)
+#        define POINTING_DEVICE_THIS_SIDE !is_keyboard_left()
+#    elif defined(POINTING_DEVICE_COMBINED)
+#        define POINTING_DEVICE_THIS_SIDE true
+#    endif
+
+#endif  // defined(SPLIT_POINTING_ENABLE)
+
+static report_mouse_t local_mouse_report = {};
 
 extern const pointing_device_driver_t pointing_device_driver;
 
+/**
+ * @brief Compares 2 mouse reports for difference and returns result
+ *
+ * @param[in] new report_mouse_t
+ * @param[in] old report_mouse_t
+ * @return bool result
+ */
 __attribute__((weak)) bool has_mouse_report_changed(report_mouse_t new, report_mouse_t old) { return memcmp(&new, &old, sizeof(new)); }
 
-__attribute__((weak)) void           pointing_device_init_kb(void) {}
-__attribute__((weak)) void           pointing_device_init_user(void) {}
+/**
+ * @brief Keyboard level code pointing device initialisation
+ *
+ */
+__attribute__((weak)) void pointing_device_init_kb(void) {}
+
+/**
+ * @brief User level code pointing device initialisation
+ *
+ */
+__attribute__((weak)) void pointing_device_init_user(void) {}
+
+/**
+ * @brief Weak function allowing for keyboard level mouse report modification
+ *
+ * Takes report_mouse_t struct allowing modification at keyboard level then returns report_mouse_t.
+ *
+ * @param[in] mouse_report report_mouse_t
+ * @return report_mouse_t
+ */
 __attribute__((weak)) report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report) { return pointing_device_task_user(mouse_report); }
+
+/**
+ * @brief Weak function allowing for user level mouse report modification
+ *
+ * Takes report_mouse_t struct allowing modification at user level then returns report_mouse_t.
+ *
+ * @param[in] mouse_report report_mouse_t
+ * @return report_mouse_t
+ */
 __attribute__((weak)) report_mouse_t pointing_device_task_user(report_mouse_t mouse_report) { return mouse_report; }
 
+/**
+ * @brief Handles pointing device buttons
+ *
+ * Returns modified button bitmask using bool pressed and selected pointing_device_buttons_t button in uint8_t buttons bitmask.
+ *
+ * @param buttons[in] uint8_t bitmask
+ * @param pressed[in] bool
+ * @param button[in] pointing_device_buttons_t value
+ * @return Modified uint8_t bitmask buttons
+ */
 __attribute__((weak)) uint8_t pointing_device_handle_buttons(uint8_t buttons, bool pressed, pointing_device_buttons_t button) {
     if (pressed) {
         buttons |= 1 << (button);
@@ -45,7 +126,17 @@ __attribute__((weak)) uint8_t pointing_device_handle_buttons(uint8_t buttons, bo
     return buttons;
 }
 
+/**
+ * @brief Initialises pointing device
+ *
+ * Initialises pointing device, perform driver init and optional keyboard/user level code.
+ */
 __attribute__((weak)) void pointing_device_init(void) {
+#if defined(SPLIT_POINTING_ENABLE)
+    if (!(POINTING_DEVICE_THIS_SIDE)) {
+        return;
+    }
+#endif
     pointing_device_driver.init();
 #ifdef POINTING_DEVICE_MOTION_PIN
     setPinInputHigh(POINTING_DEVICE_MOTION_PIN);
@@ -54,67 +145,299 @@ __attribute__((weak)) void pointing_device_init(void) {
     pointing_device_init_user();
 }
 
+/**
+ * @brief Sends processed mouse report to host
+ *
+ * This sends the mouse report generated by pointing_device_task if changed since the last report. Once send zeros mouse report except buttons.
+ *
+ */
 __attribute__((weak)) void pointing_device_send(void) {
     static report_mouse_t old_report = {};
 
     // If you need to do other things, like debugging, this is the place to do it.
-    if (has_mouse_report_changed(mouseReport, old_report)) {
-        host_mouse_send(&mouseReport);
+    if (has_mouse_report_changed(local_mouse_report, old_report)) {
+        host_mouse_send(&local_mouse_report);
     }
     // send it and 0 it out except for buttons, so those stay until they are explicity over-ridden using update_pointing_device
-    mouseReport.x = 0;
-    mouseReport.y = 0;
-    mouseReport.v = 0;
-    mouseReport.h = 0;
+    local_mouse_report.x = 0;
+    local_mouse_report.y = 0;
+    local_mouse_report.v = 0;
+    local_mouse_report.h = 0;
 
-    memcpy(&old_report, &mouseReport, sizeof(mouseReport));
+    memcpy(&old_report, &local_mouse_report, sizeof(local_mouse_report));
 }
 
-__attribute__((weak)) void pointing_device_task(void) {
-    // Gather report info
-#ifdef POINTING_DEVICE_MOTION_PIN
-    if (!readPin(POINTING_DEVICE_MOTION_PIN))
-#endif
-        mouseReport = pointing_device_driver.get_report(mouseReport);
-
-        // Support rotation of the sensor data
+/**
+ * @brief Adjust mouse report by any optional common pointing configuration defines
+ *
+ * This applies rotation or inversion to the mouse report as selected by the pointing device common configuration defines.
+ *
+ * @param mouse_report[in] takes a report_mouse_t to be adjusted
+ * @return report_mouse_t with adjusted values
+ */
+report_mouse_t pointing_device_adjust_by_defines(report_mouse_t mouse_report) {
+    // Support rotation of the sensor data
 #if defined(POINTING_DEVICE_ROTATION_90) || defined(POINTING_DEVICE_ROTATION_180) || defined(POINTING_DEVICE_ROTATION_270)
-    int8_t x = mouseReport.x, y = mouseReport.y;
+    int8_t x = mouse_report.x, y = mouse_report.y;
 #    if defined(POINTING_DEVICE_ROTATION_90)
-    mouseReport.x = y;
-    mouseReport.y = -x;
+    mouse_report.x = y;
+    mouse_report.y = -x;
 #    elif defined(POINTING_DEVICE_ROTATION_180)
-    mouseReport.x = -x;
-    mouseReport.y = -y;
+    mouse_report.x = -x;
+    mouse_report.y = -y;
 #    elif defined(POINTING_DEVICE_ROTATION_270)
-    mouseReport.x = -y;
-    mouseReport.y = x;
+    mouse_report.x = -y;
+    mouse_report.y = x;
 #    else
 #        error "How the heck did you get here?!"
 #    endif
 #endif
     // Support Inverting the X and Y Axises
 #if defined(POINTING_DEVICE_INVERT_X)
-    mouseReport.x = -mouseReport.x;
+    mouse_report.x = -mouse_report.x;
 #endif
 #if defined(POINTING_DEVICE_INVERT_Y)
-    mouseReport.y = -mouseReport.y;
+    mouse_report.y = -mouse_report.y;
+#endif
+    return mouse_report;
+}
+
+/**
+ * @brief Retrieves and processes pointing device data.
+ *
+ * This function is part of the keyboard loop and retrieves the mouse report from the pointing device driver.
+ * It applies any optional configuration e.g. rotation or axis inversion and then initiates a send.
+ *
+ */
+__attribute__((weak)) void pointing_device_task(void) {
+#if defined(SPLIT_POINTING_ENABLE)
+    // Don't poll the target side pointing device.
+    if (!is_keyboard_master()) {
+        return;
+    };
+#endif
+
+#if defined(POINTING_DEVICE_TASK_THROTTLE_MS)
+    static uint32_t last_exec = 0;
+    if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
+        return;
+    }
+    last_exec = timer_read32();
+#else
+#    if defined(SPLIT_POINTING_ENABLE)
+#        pragma message("It's recommended you enable a throttle when sharing pointing devices.")
+#    endif
+#endif
+
+    // Gather report info
+#ifdef POINTING_DEVICE_MOTION_PIN
+#    if defined(SPLIT_POINTING_ENABLE)
+#        error POINTING_DEVICE_MOTION_PIN not supported when sharing the pointing device report between sides.
+#    endif
+    if (!readPin(POINTING_DEVICE_MOTION_PIN))
 #endif
 
+#if defined(SPLIT_POINTING_ENABLE)
+#    if defined(POINTING_DEVICE_COMBINED)
+        static uint8_t old_buttons = 0;
+    local_mouse_report.buttons = old_buttons;
+    local_mouse_report         = pointing_device_driver.get_report(local_mouse_report);
+    old_buttons                = local_mouse_report.buttons;
+#    elif defined(POINTING_DEVICE_LEFT) || defined(POINTING_DEVICE_RIGHT)
+        local_mouse_report = POINTING_DEVICE_THIS_SIDE ? pointing_device_driver.get_report(local_mouse_report) : shared_mouse_report;
+#    else
+#        error "You need to define the side(s) the pointing device is on. POINTING_DEVICE_COMBINED / POINTING_DEVICE_LEFT / POINTING_DEVICE_RIGHT"
+#    endif
+#else
+    local_mouse_report = pointing_device_driver.get_report(local_mouse_report);
+#endif  // defined(SPLIT_POINTING_ENABLE)
+
     // allow kb to intercept and modify report
-    mouseReport = pointing_device_task_kb(mouseReport);
+#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
+    if (is_keyboard_left()) {
+        local_mouse_report  = pointing_device_adjust_by_defines(local_mouse_report);
+        shared_mouse_report = pointing_device_adjust_by_defines_right(shared_mouse_report);
+    } else {
+        local_mouse_report  = pointing_device_adjust_by_defines_right(local_mouse_report);
+        shared_mouse_report = pointing_device_adjust_by_defines(shared_mouse_report);
+    }
+    local_mouse_report = is_keyboard_left() ? pointing_device_task_combined_kb(local_mouse_report, shared_mouse_report) : pointing_device_task_combined_kb(shared_mouse_report, local_mouse_report);
+#else
+    local_mouse_report = pointing_device_adjust_by_defines(local_mouse_report);
+    local_mouse_report = pointing_device_task_kb(local_mouse_report);
+#endif
     // combine with mouse report to ensure that the combined is sent correctly
 #ifdef MOUSEKEY_ENABLE
     report_mouse_t mousekey_report = mousekey_get_report();
-    mouseReport.buttons            = mouseReport.buttons | mousekey_report.buttons;
+    local_mouse_report.buttons     = local_mouse_report.buttons | mousekey_report.buttons;
 #endif
     pointing_device_send();
 }
 
-report_mouse_t pointing_device_get_report(void) { return mouseReport; }
+/**
+ * @brief Gets current mouse report used by pointing device task
+ *
+ * @return report_mouse_t
+ */
+report_mouse_t pointing_device_get_report(void) { return local_mouse_report; }
+
+/**
+ * @brief Sets mouse report used be pointing device task
+ *
+ * @param[in] new_mouse_report
+ */
+void pointing_device_set_report(report_mouse_t new_mouse_report) { local_mouse_report = new_mouse_report; }
+
+/**
+ * @brief Gets current pointing device CPI if supported
+ *
+ * Gets current cpi from pointing device driver if supported and returns it as uint16_t
+ *
+ * @return cpi value as uint16_t
+ */
+uint16_t pointing_device_get_cpi(void) {
+#if defined(SPLIT_POINTING_ENABLE)
+    return POINTING_DEVICE_THIS_SIDE ? pointing_device_driver.get_cpi() : shared_cpi;
+#else
+    return pointing_device_driver.get_cpi();
+#endif
+}
 
-void pointing_device_set_report(report_mouse_t newMouseReport) { mouseReport = newMouseReport; }
+/**
+ * @brief Set pointing device CPI if supported
+ *
+ * Takes a uint16_t value to set pointing device cpi if supported by driver.
+ *
+ * @param[in] cpi uint16_t value.
+ */
+void pointing_device_set_cpi(uint16_t cpi) {
+#if defined(SPLIT_POINTING_ENABLE)
+    if (POINTING_DEVICE_THIS_SIDE) {
+        pointing_device_driver.set_cpi(cpi);
+    } else {
+        shared_cpi = cpi;
+    }
+#else
+    pointing_device_driver.set_cpi(cpi);
+#endif
+}
 
-uint16_t pointing_device_get_cpi(void) { return pointing_device_driver.get_cpi(); }
+#if defined(SPLIT_POINTING_ENABLE) && defined(POINTING_DEVICE_COMBINED)
+/**
+ * @brief Set pointing device CPI if supported
+ *
+ * Takes a bool and uint16_t and allows setting cpi for a single side when using 2 pointing devices with a split keyboard.
+ *
+ * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
+ *
+ * @param[in] left true = left, false = right.
+ * @param[in] cpi uint16_t value.
+ */
+void pointing_device_set_cpi_on_side(bool left, uint16_t cpi) {
+    bool local = (is_keyboard_left() & left) ? true : false;
+    if (local) {
+        pointing_device_driver.set_cpi(cpi);
+    } else {
+        shared_cpi = cpi;
+    }
+}
 
-void pointing_device_set_cpi(uint16_t cpi) { pointing_device_driver.set_cpi(cpi); }
+/**
+ * @brief clamps int16_t to int8_t
+ *
+ * @param[in] int16_t value
+ * @return int8_t clamped value
+ */
+static inline int8_t pointing_device_movement_clamp(int16_t value) {
+    if (value < INT8_MIN) {
+        return INT8_MIN;
+    } else if (value > INT8_MAX) {
+        return INT8_MAX;
+    } else {
+        return value;
+    }
+}
+
+/**
+ * @brief combines 2 mouse reports and returns 2
+ *
+ * Combines 2 report_mouse_t structs, clamping movement values to int8_t and ignores report_id then returns the resulting report_mouse_t struct.
+ *
+ * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
+ *
+ * @param[in] left_report left report_mouse_t
+ * @param[in] right_report right report_mouse_t
+ * @return combined report_mouse_t of left_report and right_report
+ */
+report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, report_mouse_t right_report) {
+    left_report.x = pointing_device_movement_clamp((int16_t)left_report.x + right_report.x);
+    left_report.y = pointing_device_movement_clamp((int16_t)left_report.y + right_report.y);
+    left_report.h = pointing_device_movement_clamp((int16_t)left_report.h + right_report.h);
+    left_report.v = pointing_device_movement_clamp((int16_t)left_report.v + right_report.v);
+    left_report.buttons |= right_report.buttons;
+    return left_report;
+}
+
+/**
+ * @brief Adjust mouse report by any optional right pointing configuration defines
+ *
+ * This applies rotation or inversion to the mouse report as selected by the pointing device common configuration defines.
+ *
+ * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
+ *
+ * @param[in] mouse_report report_mouse_t to be adjusted
+ * @return report_mouse_t with adjusted values
+ */
+report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_report) {
+    // Support rotation of the sensor data
+#    if defined(POINTING_DEVICE_ROTATION_90_RIGHT) || defined(POINTING_DEVICE_ROTATION_RIGHT) || defined(POINTING_DEVICE_ROTATION_RIGHT)
+    int8_t x = mouse_report.x, y = mouse_report.y;
+#        if defined(POINTING_DEVICE_ROTATION_90_RIGHT)
+    mouse_report.x = y;
+    mouse_report.y = -x;
+#        elif defined(POINTING_DEVICE_ROTATION_180_RIGHT)
+    mouse_report.x = -x;
+    mouse_report.y = -y;
+#        elif defined(POINTING_DEVICE_ROTATION_270_RIGHT)
+    mouse_report.x = -y;
+    mouse_report.y = x;
+#        else
+#            error "How the heck did you get here?!"
+#        endif
+#    endif
+    // Support Inverting the X and Y Axises
+#    if defined(POINTING_DEVICE_INVERT_X_RIGHT)
+    mouse_report.x = -mouse_report.x;
+#    endif
+#    if defined(POINTING_DEVICE_INVERT_Y_RIGHT)
+    mouse_report.y = -mouse_report.y;
+#    endif
+    return mouse_report;
+}
+
+/**
+ * @brief Weak function allowing for keyboard level mouse report modification
+ *
+ * Takes 2 report_mouse_t structs allowing individual modification of sides at keyboard level then returns pointing_device_task_combined_user.
+ *
+ * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
+ *
+ * @param[in] left_report report_mouse_t
+ * @param[in] right_report report_mouse_t
+ * @return pointing_device_task_combined_user(left_report, right_report) by default
+ */
+__attribute__((weak)) report_mouse_t pointing_device_task_combined_kb(report_mouse_t left_report, report_mouse_t right_report) { return pointing_device_task_combined_user(left_report, right_report); }
+
+/**
+ * @brief Weak function allowing for user level mouse report modification
+ *
+ * Takes 2 report_mouse_t structs allowing individual modification of sides at user level then returns pointing_device_combine_reports.
+ *
+ * NOTE: Only available when using SPLIT_POINTING_ENABLE and POINTING_DEVICE_COMBINED
+ *
+ * @param[in] left_report report_mouse_t
+ * @param[in] right_report report_mouse_t
+ * @return pointing_device_combine_reports(left_report, right_report) by default
+ */
+__attribute__((weak)) report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report) { return pointing_device_combine_reports(left_report, right_report); }
+#endif

+ 13 - 0
quantum/pointing_device.h

@@ -86,3 +86,16 @@ void           pointing_device_init_user(void);
 report_mouse_t pointing_device_task_kb(report_mouse_t mouse_report);
 report_mouse_t pointing_device_task_user(report_mouse_t mouse_report);
 uint8_t        pointing_device_handle_buttons(uint8_t buttons, bool pressed, pointing_device_buttons_t button);
+report_mouse_t pointing_device_adjust_by_defines(report_mouse_t mouse_report);
+
+#if defined(SPLIT_POINTING_ENABLE)
+void     pointing_device_set_shared_report(report_mouse_t report);
+uint16_t pointing_device_get_shared_cpi(void);
+#    if defined(POINTING_DEVICE_COMBINED)
+void           pointing_device_set_cpi_on_side(bool left, uint16_t cpi);
+report_mouse_t pointing_device_combine_reports(report_mouse_t left_report, report_mouse_t right_report);
+report_mouse_t pointing_device_task_combined_kb(report_mouse_t left_report, report_mouse_t right_report);
+report_mouse_t pointing_device_task_combined_user(report_mouse_t left_report, report_mouse_t right_report);
+report_mouse_t pointing_device_adjust_by_defines_right(report_mouse_t mouse_report);
+#    endif //defined(POINTING_DEVICE_COMBINED)
+#endif //defined(SPLIT_POINTING_ENABLE)

+ 11 - 13
quantum/pointing_device_drivers.c

@@ -165,14 +165,13 @@ const pointing_device_driver_t pointing_device_driver = {
 // clang-format on
 
 #elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
-report_mouse_t pimorono_trackball_get_report(report_mouse_t mouse_report) {
-    static fast_timer_t throttle      = 0;
-    static uint16_t     debounce      = 0;
-    static uint8_t      error_count   = 0;
-    pimoroni_data_t     pimoroni_data = {0};
-    static int16_t      x_offset = 0, y_offset = 0;
-
-    if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT && timer_elapsed_fast(throttle) >= PIMORONI_TRACKBALL_INTERVAL_MS) {
+report_mouse_t pimoroni_trackball_get_report(report_mouse_t mouse_report) {
+    static uint16_t debounce      = 0;
+    static uint8_t  error_count   = 0;
+    pimoroni_data_t pimoroni_data = {0};
+    static int16_t  x_offset = 0, y_offset = 0;
+
+    if (error_count < PIMORONI_TRACKBALL_ERROR_COUNT) {
         i2c_status_t status = read_pimoroni_trackball(&pimoroni_data);
 
         if (status == I2C_STATUS_SUCCESS) {
@@ -195,17 +194,16 @@ report_mouse_t pimorono_trackball_get_report(report_mouse_t mouse_report) {
         } else {
             error_count++;
         }
-        throttle = timer_read_fast();
     }
     return mouse_report;
 }
 
 // clang-format off
 const pointing_device_driver_t pointing_device_driver = {
-    .init       = pimironi_trackball_device_init,
-    .get_report = pimorono_trackball_get_report,
-    .set_cpi    = NULL,
-    .get_cpi    = NULL
+    .init       = pimoroni_trackball_device_init,
+    .get_report = pimoroni_trackball_get_report,
+    .set_cpi    = pimoroni_trackball_set_cpi,
+    .get_cpi    = pimoroni_trackball_get_cpi
 };
 // clang-format on
 #elif defined(POINTING_DEVICE_DRIVER_pmw3360)

+ 6 - 0
quantum/split_common/transaction_id_define.h

@@ -78,6 +78,12 @@ enum serial_transaction_id {
     PUT_ST7565,
 #endif  // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
 
+#if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+    GET_POINTING_CHECKSUM,
+    GET_POINTING_DATA,
+    PUT_POINTING_CPI,
+#endif  // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
     PUT_RPC_INFO,
     PUT_RPC_REQ_DATA,

+ 79 - 0
quantum/split_common/transactions.c

@@ -578,6 +578,82 @@ static void st7565_handlers_slave(matrix_row_t master_matrix[], matrix_row_t sla
 
 #endif  // defined(ST7565_ENABLE) && defined(SPLIT_ST7565_ENABLE)
 
+////////////////////////////////////////////////////
+// POINTING
+
+#if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
+static bool pointing_handlers_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+#    if defined(POINTING_DEVICE_LEFT)
+    if (is_keyboard_left()) {
+        return true;
+    }
+#    elif defined(POINTING_DEVICE_RIGHT)
+    if (!is_keyboard_left()) {
+        return true;
+    }
+#    endif
+    static uint32_t last_update = 0;
+    static uint16_t last_cpi    = 0;
+    report_mouse_t  temp_state;
+    uint16_t        temp_cpi;
+    bool            okay = read_if_checksum_mismatch(GET_POINTING_CHECKSUM, GET_POINTING_DATA, &last_update, &temp_state, &split_shmem->pointing.report, sizeof(temp_state));
+    if (okay) pointing_device_set_shared_report(temp_state);
+    temp_cpi = pointing_device_get_shared_cpi();
+    if (temp_cpi && memcmp(&last_cpi, &temp_cpi, sizeof(temp_cpi)) != 0) {
+        memcpy(&split_shmem->pointing.cpi, &temp_cpi, sizeof(temp_cpi));
+        okay = transport_write(PUT_POINTING_CPI, &split_shmem->pointing.cpi, sizeof(split_shmem->pointing.cpi));
+        if (okay) {
+            last_cpi = temp_cpi;
+        }
+    }
+    return okay;
+}
+
+extern const pointing_device_driver_t pointing_device_driver;
+
+static void pointing_handlers_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
+#    if defined(POINTING_DEVICE_LEFT)
+    if (!is_keyboard_left()) {
+        return;
+    }
+#    elif defined(POINTING_DEVICE_RIGHT)
+    if (is_keyboard_left()) {
+        return;
+    }
+#    endif
+    report_mouse_t temp_report;
+    uint16_t       temp_cpi;
+#    ifdef POINTING_DEVICE_TASK_THROTTLE_MS
+    static uint32_t last_exec = 0;
+    if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
+        return;
+    }
+    last_exec = timer_read32();
+#    endif
+    temp_cpi = pointing_device_driver.get_cpi();
+    if (split_shmem->pointing.cpi && memcmp(&split_shmem->pointing.cpi, &temp_cpi, sizeof(temp_cpi)) != 0) {
+        pointing_device_driver.set_cpi(split_shmem->pointing.cpi);
+    }
+    memset(&temp_report, 0, sizeof(temp_report));
+    temp_report = pointing_device_driver.get_report(temp_report);
+    memcpy(&split_shmem->pointing.report, &temp_report, sizeof(temp_report));
+    // Now update the checksum given that the pointing has been written to
+    split_shmem->pointing.checksum = crc8(&temp_report, sizeof(temp_report));
+}
+
+#    define TRANSACTIONS_POINTING_MASTER() TRANSACTION_HANDLER_MASTER(pointing)
+#    define TRANSACTIONS_POINTING_SLAVE() TRANSACTION_HANDLER_SLAVE(pointing)
+#    define TRANSACTIONS_POINTING_REGISTRATIONS [GET_POINTING_CHECKSUM] = trans_target2initiator_initializer(pointing.checksum), [GET_POINTING_DATA] = trans_target2initiator_initializer(pointing.report), [PUT_POINTING_CPI] = trans_initiator2target_initializer(pointing.cpi),
+
+#else  // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
+#    define TRANSACTIONS_POINTING_MASTER()
+#    define TRANSACTIONS_POINTING_SLAVE()
+#    define TRANSACTIONS_POINTING_REGISTRATIONS
+
+#endif  // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
 ////////////////////////////////////////////////////
 
 uint8_t                  dummy;
@@ -604,6 +680,7 @@ split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = {
     TRANSACTIONS_WPM_REGISTRATIONS
     TRANSACTIONS_OLED_REGISTRATIONS
     TRANSACTIONS_ST7565_REGISTRATIONS
+    TRANSACTIONS_POINTING_REGISTRATIONS
 // clang-format on
 
 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
@@ -629,6 +706,7 @@ bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix
     TRANSACTIONS_WPM_MASTER();
     TRANSACTIONS_OLED_MASTER();
     TRANSACTIONS_ST7565_MASTER();
+    TRANSACTIONS_POINTING_MASTER();
     return true;
 }
 
@@ -647,6 +725,7 @@ void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[
     TRANSACTIONS_WPM_SLAVE();
     TRANSACTIONS_OLED_SLAVE();
     TRANSACTIONS_ST7565_SLAVE();
+    TRANSACTIONS_POINTING_SLAVE();
 }
 
 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)

+ 13 - 0
quantum/split_common/transport.h

@@ -106,6 +106,15 @@ typedef struct _split_mods_sync_t {
 } split_mods_sync_t;
 #endif  // SPLIT_MODS_ENABLE
 
+#if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+#    include "pointing_device.h"
+typedef struct _split_slave_pointing_sync_t {
+    uint8_t        checksum;
+    report_mouse_t report;
+    uint16_t       cpi;
+} split_slave_pointing_sync_t;
+#endif  // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
 typedef struct _rpc_sync_info_t {
     int8_t  transaction_id;
@@ -173,6 +182,10 @@ typedef struct _split_shared_memory_t {
     uint8_t current_st7565_state;
 #endif  // ST7565_ENABLE(OLED_ENABLE) && defined(SPLIT_ST7565_ENABLE)
 
+#if defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+    split_slave_pointing_sync_t pointing;
+#endif  // defined(POINTING_DEVICE_ENABLE) && defined(SPLIT_POINTING_ENABLE)
+
 #if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER)
     rpc_sync_info_t rpc_info;
     uint8_t         rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE];