summary refs log tree commit diff
path: root/quantum/pointing_device
diff options
context:
space:
mode:
authorDrashna Jaelre <drashna@live.com>2022-07-20 09:32:00 -0700
committerGitHub <noreply@github.com>2022-07-20 17:32:00 +0100
commitee17ffadea41241fc2b397e0bbea8903591dd21c (patch)
treecad8fb55bf28bdb6fa457a787a5392d75ec9377a /quantum/pointing_device
parentadfa36fee4a9c37977412522cddec78e6eecd977 (diff)
Move Pointing Device code to a subdirectory (#17684)
Diffstat (limited to 'quantum/pointing_device')
-rw-r--r--quantum/pointing_device/pointing_device.c470
-rw-r--r--quantum/pointing_device/pointing_device.h117
-rw-r--r--quantum/pointing_device/pointing_device_drivers.c345
-rw-r--r--quantum/pointing_device/pointing_device_gestures.c133
-rw-r--r--quantum/pointing_device/pointing_device_gestures.h58
5 files changed, 1123 insertions, 0 deletions
diff --git a/quantum/pointing_device/pointing_device.c b/quantum/pointing_device/pointing_device.c
new file mode 100644
index 0000000000..3aa4941687
--- /dev/null
+++ b/quantum/pointing_device/pointing_device.c
@@ -0,0 +1,470 @@
+/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
+ * Copyright 2020 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
+ * Copyright 2021 Dasky (@daskygit)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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(POINTING_DEVICE_LEFT) || defined(POINTING_DEVICE_RIGHT) || defined(POINTING_DEVICE_COMBINED)
+#    ifndef SPLIT_POINTING_ENABLE
+#        error "Using POINTING_DEVICE_LEFT or POINTING_DEVICE_RIGHT or POINTING_DEVICE_COMBINED, then SPLIT_POINTING_ENABLE is required but has not been defined"
+#    endif
+#endif
+
+#if defined(SPLIT_POINTING_ENABLE)
+#    include "transactions.h"
+#    include "keyboard.h"
+
+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 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);
+    } else {
+        buttons &= ~(1 << (button));
+    }
+    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);
+#endif
+    pointing_device_init_kb();
+    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(&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
+    local_mouse_report.x = 0;
+    local_mouse_report.y = 0;
+    local_mouse_report.v = 0;
+    local_mouse_report.h = 0;
+
+    memcpy(&old_report, &local_mouse_report, sizeof(local_mouse_report));
+}
+
+/**
+ * @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)
+    mouse_xy_report_t x = mouse_report.x;
+    mouse_xy_report_t y = mouse_report.y;
+#    if defined(POINTING_DEVICE_ROTATION_90)
+    mouse_report.x = y;
+    mouse_report.y = -x;
+#    elif defined(POINTING_DEVICE_ROTATION_180)
+    mouse_report.x = -x;
+    mouse_report.y = -y;
+#    elif defined(POINTING_DEVICE_ROTATION_270)
+    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)
+    mouse_report.x = -mouse_report.x;
+#endif
+#if defined(POINTING_DEVICE_INVERT_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 (POINTING_DEVICE_TASK_THROTTLE_MS > 0)
+    static uint32_t last_exec = 0;
+    if (timer_elapsed32(last_exec) < POINTING_DEVICE_TASK_THROTTLE_MS) {
+        return;
+    }
+    last_exec = timer_read32();
+#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
+#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();
+    local_mouse_report.buttons     = local_mouse_report.buttons | mousekey_report.buttons;
+#endif
+    pointing_device_send();
+}
+
+/**
+ * @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] mouse_report
+ */
+void pointing_device_set_report(report_mouse_t mouse_report) {
+    local_mouse_report = 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
+}
+
+/**
+ * @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
+}
+
+#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;
+    }
+}
+
+/**
+ * @brief clamps int16_t to int8_t
+ *
+ * @param[in] int16_t value
+ * @return int8_t clamped value
+ */
+static inline int8_t pointing_device_hv_clamp(int16_t value) {
+    if (value < INT8_MIN) {
+        return INT8_MIN;
+    } else if (value > INT8_MAX) {
+        return INT8_MAX;
+    } else {
+        return value;
+    }
+}
+
+/**
+ * @brief clamps int16_t to int8_t
+ *
+ * @param[in] clamp_range_t value
+ * @return mouse_xy_report_t clamped value
+ */
+static inline mouse_xy_report_t pointing_device_xy_clamp(clamp_range_t value) {
+    if (value < XY_REPORT_MIN) {
+        return XY_REPORT_MIN;
+    } else if (value > XY_REPORT_MAX) {
+        return XY_REPORT_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_xy_clamp((clamp_range_t)left_report.x + right_report.x);
+    left_report.y = pointing_device_xy_clamp((clamp_range_t)left_report.y + right_report.y);
+    left_report.h = pointing_device_hv_clamp((int16_t)left_report.h + right_report.h);
+    left_report.v = pointing_device_hv_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)
+    mouse_xy_report_t x = mouse_report.x;
+    mouse_xy_report_t 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
diff --git a/quantum/pointing_device/pointing_device.h b/quantum/pointing_device/pointing_device.h
new file mode 100644
index 0000000000..77db5471ea
--- /dev/null
+++ b/quantum/pointing_device/pointing_device.h
@@ -0,0 +1,117 @@
+/*
+Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdint.h>
+#include "host.h"
+#include "report.h"
+
+#if defined(POINTING_DEVICE_DRIVER_adns5050)
+#    include "drivers/sensors/adns5050.h"
+#elif defined(POINTING_DEVICE_DRIVER_adns9800)
+#    include "spi_master.h"
+#    include "drivers/sensors/adns9800.h"
+#elif defined(POINTING_DEVICE_DRIVER_analog_joystick)
+#    include "analog.h"
+#    include "drivers/sensors/analog_joystick.h"
+#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
+#    include "drivers/sensors/cirque_pinnacle.h"
+#    include "drivers/sensors/cirque_pinnacle_gestures.h"
+#    include "pointing_device_gestures.h"
+#elif defined(POINTING_DEVICE_DRIVER_paw3204)
+#    include "drivers/sensors/paw3204.h"
+#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
+#    include "i2c_master.h"
+#    include "drivers/sensors/pimoroni_trackball.h"
+// support for legacy pimoroni defines
+#    ifdef PIMORONI_TRACKBALL_INVERT_X
+#        define POINTING_DEVICE_INVERT_X
+#    endif
+#    ifdef PIMORONI_TRACKBALL_INVERT_Y
+#        define POINTING_DEVICE_INVERT_Y
+#    endif
+#    ifdef PIMORONI_TRACKBALL_ROTATE
+#        define POINTING_DEVICE_ROTATION_90
+#    endif
+#elif defined(POINTING_DEVICE_DRIVER_pmw3360) || defined(POINTING_DEVICE_DRIVER_pmw3389)
+#    include "spi_master.h"
+#    include "drivers/sensors/pmw33xx_common.h"
+#else
+void           pointing_device_driver_init(void);
+report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report);
+uint16_t       pointing_device_driver_get_cpi(void);
+void           pointing_device_driver_set_cpi(uint16_t cpi);
+#endif
+
+typedef struct {
+    void (*init)(void);
+    report_mouse_t (*get_report)(report_mouse_t mouse_report);
+    void (*set_cpi)(uint16_t);
+    uint16_t (*get_cpi)(void);
+} pointing_device_driver_t;
+
+typedef enum {
+    POINTING_DEVICE_BUTTON1,
+    POINTING_DEVICE_BUTTON2,
+    POINTING_DEVICE_BUTTON3,
+    POINTING_DEVICE_BUTTON4,
+    POINTING_DEVICE_BUTTON5,
+    POINTING_DEVICE_BUTTON6,
+    POINTING_DEVICE_BUTTON7,
+    POINTING_DEVICE_BUTTON8,
+} pointing_device_buttons_t;
+
+#ifdef MOUSE_EXTENDED_REPORT
+#    define XY_REPORT_MIN INT16_MIN
+#    define XY_REPORT_MAX INT16_MAX
+typedef int32_t clamp_range_t;
+#else
+#    define XY_REPORT_MIN INT8_MIN
+#    define XY_REPORT_MAX INT8_MAX
+typedef int16_t clamp_range_t;
+#endif
+
+void           pointing_device_init(void);
+void           pointing_device_task(void);
+void           pointing_device_send(void);
+report_mouse_t pointing_device_get_report(void);
+void           pointing_device_set_report(report_mouse_t mouse_report);
+uint16_t       pointing_device_get_cpi(void);
+void           pointing_device_set_cpi(uint16_t cpi);
+
+void           pointing_device_init_kb(void);
+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_TASK_THROTTLE_MS)
+#        define POINTING_DEVICE_TASK_THROTTLE_MS 1
+#    endif
+#    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)
diff --git a/quantum/pointing_device/pointing_device_drivers.c b/quantum/pointing_device/pointing_device_drivers.c
new file mode 100644
index 0000000000..d0b545d22d
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_drivers.c
@@ -0,0 +1,345 @@
+/* Copyright 2017 Joshua Broekhuijsen <snipeye+qmk@gmail.com>
+ * Copyright 2020 Christopher Courtney, aka Drashna Jael're  (@drashna) <drashna@live.com>
+ * Copyright 2021 Dasky (@daskygit)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pointing_device.h"
+#include "debug.h"
+#include "wait.h"
+#include "timer.h"
+#include <stddef.h>
+
+#define CONSTRAIN_HID(amt) ((amt) < INT8_MIN ? INT8_MIN : ((amt) > INT8_MAX ? INT8_MAX : (amt)))
+#define CONSTRAIN_HID_XY(amt) ((amt) < XY_REPORT_MIN ? XY_REPORT_MIN : ((amt) > XY_REPORT_MAX ? XY_REPORT_MAX : (amt)))
+
+// get_report functions should probably be moved to their respective drivers.
+
+#if defined(POINTING_DEVICE_DRIVER_adns5050)
+report_mouse_t adns5050_get_report(report_mouse_t mouse_report) {
+    report_adns5050_t data = adns5050_read_burst();
+
+    if (data.dx != 0 || data.dy != 0) {
+#    ifdef CONSOLE_ENABLE
+        if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.dx, data.dy);
+#    endif
+
+        mouse_report.x = (mouse_xy_report_t)data.dx;
+        mouse_report.y = (mouse_xy_report_t)data.dy;
+    }
+
+    return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init         = adns5050_init,
+    .get_report   = adns5050_get_report,
+    .set_cpi      = adns5050_set_cpi,
+    .get_cpi      = adns5050_get_cpi,
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_adns9800)
+
+report_mouse_t adns9800_get_report_driver(report_mouse_t mouse_report) {
+    report_adns9800_t sensor_report = adns9800_get_report();
+
+    mouse_report.x = CONSTRAIN_HID_XY(sensor_report.x);
+    mouse_report.y = CONSTRAIN_HID_XY(sensor_report.y);
+
+    return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = adns9800_init,
+    .get_report = adns9800_get_report_driver,
+    .set_cpi    = adns9800_set_cpi,
+    .get_cpi    = adns9800_get_cpi
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_analog_joystick)
+report_mouse_t analog_joystick_get_report(report_mouse_t mouse_report) {
+    report_analog_joystick_t data = analog_joystick_read();
+
+#    ifdef CONSOLE_ENABLE
+    if (debug_mouse) dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
+#    endif
+
+    mouse_report.x = data.x;
+    mouse_report.y = data.y;
+
+    mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, data.button, POINTING_DEVICE_BUTTON1);
+
+    return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = analog_joystick_init,
+    .get_report = analog_joystick_get_report,
+    .set_cpi    = NULL,
+    .get_cpi    = NULL
+};
+// clang-format on
+
+#elif defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_i2c) || defined(POINTING_DEVICE_DRIVER_cirque_pinnacle_spi)
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+static bool cursor_glide_enable = true;
+
+static cursor_glide_context_t glide = {.config = {
+                                           .coef       = 102, /* Good default friction coef */
+                                           .interval   = 10,  /* 100sps */
+                                           .trigger_px = 10,  /* Default threshold in case of hover, set to 0 if you'd like */
+                                       }};
+
+void cirque_pinnacle_enable_cursor_glide(bool enable) {
+    cursor_glide_enable = enable;
+}
+
+void cirque_pinnacle_configure_cursor_glide(float trigger_px) {
+    glide.config.trigger_px = trigger_px;
+}
+#    endif
+
+report_mouse_t cirque_pinnacle_get_report(report_mouse_t mouse_report) {
+    pinnacle_data_t          touchData = cirque_pinnacle_read_data();
+    mouse_xy_report_t        report_x = 0, report_y = 0;
+    static mouse_xy_report_t x = 0, y = 0;
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+    cursor_glide_t           glide_report = {0};
+
+    if (cursor_glide_enable) {
+        glide_report = cursor_glide_check(&glide);
+    }
+#    endif
+
+#    if !CIRQUE_PINNACLE_POSITION_MODE
+#        error Cirque Pinnacle with relative mode not implemented yet.
+#    endif
+
+    if (!touchData.valid) {
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+        if (cursor_glide_enable && glide_report.valid) {
+            report_x = glide_report.dx;
+            report_y = glide_report.dy;
+            goto mouse_report_update;
+        }
+#    endif
+        return mouse_report;
+    }
+
+#    if CONSOLE_ENABLE
+    if (debug_mouse && touchData.touchDown) {
+        dprintf("cirque_pinnacle touchData x=%4d y=%4d z=%2d\n", touchData.xValue, touchData.yValue, touchData.zValue);
+    }
+#    endif
+
+    // Scale coordinates to arbitrary X, Y resolution
+    cirque_pinnacle_scale_data(&touchData, cirque_pinnacle_get_scale(), cirque_pinnacle_get_scale());
+
+    if (!cirque_pinnacle_gestures(&mouse_report, touchData)) {
+        if (x && y && touchData.xValue && touchData.yValue) {
+            report_x = (mouse_xy_report_t)(touchData.xValue - x);
+            report_y = (mouse_xy_report_t)(touchData.yValue - y);
+        }
+        x = touchData.xValue;
+        y = touchData.yValue;
+
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+        if (cursor_glide_enable) {
+            if (touchData.touchDown) {
+                cursor_glide_update(&glide, report_x, report_y, touchData.zValue);
+            } else if (!glide_report.valid) {
+                glide_report = cursor_glide_start(&glide);
+                if (glide_report.valid) {
+                    report_x = glide_report.dx;
+                    report_y = glide_report.dy;
+                }
+            }
+        }
+#    endif
+    }
+
+#    ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+mouse_report_update:
+#    endif
+    mouse_report.x = report_x;
+    mouse_report.y = report_y;
+
+    return mouse_report;
+}
+
+uint16_t cirque_pinnacle_get_cpi(void) {
+    return CIRQUE_PINNACLE_PX_TO_INCH(cirque_pinnacle_get_scale());
+}
+void cirque_pinnacle_set_cpi(uint16_t cpi) {
+    cirque_pinnacle_set_scale(CIRQUE_PINNACLE_INCH_TO_PX(cpi));
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = cirque_pinnacle_init,
+    .get_report = cirque_pinnacle_get_report,
+    .set_cpi    = cirque_pinnacle_set_cpi,
+    .get_cpi    = cirque_pinnacle_get_cpi
+};
+// clang-format on
+#elif defined(POINTING_DEVICE_DRIVER_paw3204)
+
+report_mouse_t paw3204_get_report(report_mouse_t mouse_report) {
+    report_paw3204_t data = paw3204_read();
+    if (data.isMotion) {
+#    ifdef CONSOLE_ENABLE
+        dprintf("Raw ] X: %d, Y: %d\n", data.x, data.y);
+#    endif
+
+        mouse_report.x = data.x;
+        mouse_report.y = data.y;
+    }
+
+    return mouse_report;
+}
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = paw3204_init,
+    .get_report = paw3204_get_report,
+    .set_cpi    = paw3204_set_cpi,
+    .get_cpi    = paw3204_get_cpi,
+};
+#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)
+
+mouse_xy_report_t pimoroni_trackball_adapt_values(clamp_range_t* offset) {
+    if (*offset > XY_REPORT_MAX) {
+        *offset -= XY_REPORT_MAX;
+        return (mouse_xy_report_t)XY_REPORT_MAX;
+    } else if (*offset < XY_REPORT_MIN) {
+        *offset += XY_REPORT_MAX;
+        return (mouse_xy_report_t)XY_REPORT_MIN;
+    } else {
+        mouse_xy_report_t temp_return = *offset;
+        *offset                       = 0;
+        return temp_return;
+    }
+}
+
+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 clamp_range_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) {
+            error_count = 0;
+
+            if (!(pimoroni_data.click & 128)) {
+                mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, false, POINTING_DEVICE_BUTTON1);
+                if (!debounce) {
+                    x_offset += pimoroni_trackball_get_offsets(pimoroni_data.right, pimoroni_data.left, PIMORONI_TRACKBALL_SCALE);
+                    y_offset += pimoroni_trackball_get_offsets(pimoroni_data.down, pimoroni_data.up, PIMORONI_TRACKBALL_SCALE);
+                    mouse_report.x = pimoroni_trackball_adapt_values(&x_offset);
+                    mouse_report.y = pimoroni_trackball_adapt_values(&y_offset);
+                } else {
+                    debounce--;
+                }
+            } else {
+                mouse_report.buttons = pointing_device_handle_buttons(mouse_report.buttons, true, POINTING_DEVICE_BUTTON1);
+                debounce             = PIMORONI_TRACKBALL_DEBOUNCE_CYCLES;
+            }
+        } else {
+            error_count++;
+        }
+    }
+    return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .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) || defined(POINTING_DEVICE_DRIVER_pmw3389)
+static void pmw33xx_init_wrapper(void) {
+    pmw33xx_init(0);
+}
+
+static void pmw33xx_set_cpi_wrapper(uint16_t cpi) {
+    pmw33xx_set_cpi(0, cpi);
+}
+
+static uint16_t pmw33xx_get_cpi_wrapper(void) {
+    return pmw33xx_get_cpi(0);
+}
+
+report_mouse_t pmw33xx_get_report(report_mouse_t mouse_report) {
+    pmw33xx_report_t report    = pmw33xx_read_burst(0);
+    static bool      in_motion = false;
+
+    if (report.motion.b.is_lifted) {
+        return mouse_report;
+    }
+
+    if (!report.motion.b.is_motion) {
+        in_motion = false;
+        return mouse_report;
+    }
+
+    if (!in_motion) {
+        in_motion = true;
+        dprintf("PWM3360 (0): starting motion\n");
+    }
+
+    mouse_report.x = CONSTRAIN_HID_XY(report.delta_x);
+    mouse_report.y = CONSTRAIN_HID_XY(report.delta_y);
+    return mouse_report;
+}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = pmw33xx_init_wrapper,
+    .get_report = pmw33xx_get_report,
+    .set_cpi    = pmw33xx_set_cpi_wrapper,
+    .get_cpi    = pmw33xx_get_cpi_wrapper
+};
+// clang-format on
+
+#else
+__attribute__((weak)) void           pointing_device_driver_init(void) {}
+__attribute__((weak)) report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
+    return mouse_report;
+}
+__attribute__((weak)) uint16_t pointing_device_driver_get_cpi(void) {
+    return 0;
+}
+__attribute__((weak)) void pointing_device_driver_set_cpi(uint16_t cpi) {}
+
+// clang-format off
+const pointing_device_driver_t pointing_device_driver = {
+    .init       = pointing_device_driver_init,
+    .get_report = pointing_device_driver_get_report,
+    .get_cpi    = pointing_device_driver_get_cpi,
+    .set_cpi    = pointing_device_driver_set_cpi
+};
+// clang-format on
+
+#endif
diff --git a/quantum/pointing_device/pointing_device_gestures.c b/quantum/pointing_device/pointing_device_gestures.c
new file mode 100644
index 0000000000..02b11ebe3f
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_gestures.c
@@ -0,0 +1,133 @@
+/* Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <string.h>
+#include "pointing_device_gestures.h"
+#include "timer.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+#    ifdef POINTING_DEVICE_MOTION_PIN
+#        error POINTING_DEVICE_MOTION_PIN not supported when using inertial cursor. Need repeated calls to get_report() to generate glide events.
+#    endif
+
+static void cursor_glide_stop(cursor_glide_context_t* glide) {
+    memset(&glide->status, 0, sizeof(glide->status));
+}
+
+static cursor_glide_t cursor_glide(cursor_glide_context_t* glide) {
+    cursor_glide_status_t* status = &glide->status;
+    cursor_glide_t         report;
+    int32_t                p;
+    int32_t                x, y;
+
+    if (status->v0 == 0) {
+        report.dx    = 0;
+        report.dy    = 0;
+        report.valid = false;
+        cursor_glide_stop(glide);
+        goto exit;
+    }
+
+    status->counter++;
+    /* Calculate current 1D position */
+    p = status->v0 * status->counter - (int32_t)glide->config.coef * status->counter * status->counter / 2;
+    /*
+     * Translate to x & y axes
+     * Done this way instead of applying friction to each axis separately, so we don't end up with the shorter axis stuck at 0 towards the end of diagonal movements.
+     */
+    x            = (int32_t)(p * status->dx0 / status->v0);
+    y            = (int32_t)(p * status->dy0 / status->v0);
+    report.dx    = (mouse_xy_report_t)(x - status->x);
+    report.dy    = (mouse_xy_report_t)(y - status->y);
+    report.valid = true;
+    if (report.dx <= 1 && report.dx >= -1 && report.dy <= 1 && report.dy >= -1) {
+        /* Stop gliding once speed is low enough */
+        cursor_glide_stop(glide);
+        goto exit;
+    }
+    status->x     = x;
+    status->y     = y;
+    status->timer = timer_read();
+
+exit:
+    return report;
+}
+
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide) {
+    cursor_glide_t         invalid_report = {0, 0, false};
+    cursor_glide_status_t* status         = &glide->status;
+
+    if (status->z || (status->dx0 == 0 && status->dy0 == 0) || timer_elapsed(status->timer) < glide->config.interval) {
+        return invalid_report;
+    } else {
+        return cursor_glide(glide);
+    }
+}
+
+static inline uint16_t sqrt32(uint32_t x) {
+    uint32_t l, m, h;
+
+    if (x == 0) {
+        return 0;
+    } else if (x > (UINT16_MAX >> 2)) {
+        /* Safe upper bound to avoid integer overflow with m * m */
+        h = UINT16_MAX;
+    } else {
+        /* Upper bound based on closest log2 */
+        h = (1 << (((__builtin_clzl(1) - __builtin_clzl(x) + 1) + 1) >> 1));
+    }
+    /* Lower bound based on closest log2 */
+    l = (1 << ((__builtin_clzl(1) - __builtin_clzl(x)) >> 1));
+
+    /* Binary search to find integer square root */
+    while (l != h - 1) {
+        m = (l + h) / 2;
+        if (m * m <= x) {
+            l = m;
+        } else {
+            h = m;
+        }
+    }
+    return l;
+}
+
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide) {
+    cursor_glide_t         invalid_report = {0, 0, false};
+    cursor_glide_status_t* status         = &glide->status;
+
+    status->timer   = timer_read();
+    status->counter = 0;
+    status->v0      = (status->dx0 == 0 && status->dy0 == 0) ? 0.0 : sqrt32(((int32_t)status->dx0 * 256 * status->dx0 * 256) + ((int32_t)status->dy0 * 256 * status->dy0 * 256)); // skip trigonometry if not needed, calculate distance in Q8
+    status->x       = 0;
+    status->y       = 0;
+    status->z       = 0;
+
+    if (status->v0 < ((uint32_t)glide->config.trigger_px * 256)) { /* Q8 comparison */
+        /* Not enough velocity to be worth gliding, abort */
+        cursor_glide_stop(glide);
+        return invalid_report;
+    }
+
+    return cursor_glide(glide);
+}
+
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z) {
+    cursor_glide_status_t* status = &glide->status;
+
+    status->dx0 = dx;
+    status->dy0 = dy;
+    status->z   = z;
+}
+#endif
diff --git a/quantum/pointing_device/pointing_device_gestures.h b/quantum/pointing_device/pointing_device_gestures.h
new file mode 100644
index 0000000000..d2ea44971b
--- /dev/null
+++ b/quantum/pointing_device/pointing_device_gestures.h
@@ -0,0 +1,58 @@
+/* Copyright 2022 Daniel Kao <daniel.m.kao@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <stdint.h>
+#include "report.h"
+
+#ifdef POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE
+typedef struct {
+    mouse_xy_report_t dx;
+    mouse_xy_report_t dy;
+    bool              valid;
+} cursor_glide_t;
+
+typedef struct {
+    uint16_t trigger_px; /* Pixels of movement needed to trigger cursor glide */
+    uint16_t coef;       /* Coefficient of friction */
+    uint16_t interval;   /* Glide report interval, in milliseconds */
+} cursor_glide_config_t;
+
+typedef struct {
+    int32_t           v0;
+    int32_t           x;
+    int32_t           y;
+    uint16_t          z;
+    uint16_t          timer;
+    uint16_t          counter;
+    mouse_xy_report_t dx0;
+    mouse_xy_report_t dy0;
+} cursor_glide_status_t;
+
+typedef struct {
+    cursor_glide_config_t config;
+    cursor_glide_status_t status;
+} cursor_glide_context_t;
+
+/* Check glide report conditions, calculates glide coordinates */
+cursor_glide_t cursor_glide_check(cursor_glide_context_t* glide);
+
+/* Start glide reporting, gives first set of glide coordinates */
+cursor_glide_t cursor_glide_start(cursor_glide_context_t* glide);
+
+/* Update glide engine on the latest cursor movement, cursor glide is based on the final movement */
+void cursor_glide_update(cursor_glide_context_t* glide, mouse_xy_report_t dx, mouse_xy_report_t dy, uint16_t z);
+#endif