Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High resolution scrolling and extended mouse wheel reports #24347

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ae15d8b
high resolution scrolling and extended wheel reports
eynsai Aug 30, 2024
e49046e
documentation and examples for high resolution scrolling and extended…
eynsai Aug 30, 2024
b13c74c
replace 'wheel report' with 'mouse report' in docs for clarity; minor…
eynsai Aug 30, 2024
9ecf71f
rename HIGH_RESOLUTION to HIRES for conciseness
eynsai Aug 31, 2024
d076726
handle hires scrolling feature reports on chibios and lufa
eynsai Aug 31, 2024
74f6236
rename HIGH_RESOLUTION to HIRES for conciseness (pt2)
eynsai Aug 31, 2024
862112a
formatting
eynsai Aug 31, 2024
0d75c51
demove drag-scroll examples from docs (will make a separate PR for dr…
eynsai Aug 31, 2024
266025b
restore accidently deleted docs
eynsai Aug 31, 2024
8eaa5af
formatting
eynsai Aug 31, 2024
a05a537
add support for extended wheel reports when using combined mouse reports
eynsai Aug 31, 2024
99fd47d
mouse_vh_report_t to mouse_hv_report_t for consistency
eynsai Sep 1, 2024
6436674
added vusb support (untested)
eynsai Sep 1, 2024
225fc62
move defaults for hires scroll flags to usb_descriptor.h so things co…
eynsai Sep 5, 2024
41f1062
memory optimizations for when the feature is turned off
eynsai Sep 6, 2024
16f8571
qmk format-c
eynsai Sep 6, 2024
ef7302f
move defaults again to support vusb
eynsai Sep 6, 2024
2cd1165
manually fix lint errors
eynsai Sep 7, 2024
c2914a2
add utility to compute hires scroll resolution
eynsai Sep 15, 2024
6ac9673
fix naming
eynsai Sep 15, 2024
0341070
format-c
eynsai Sep 16, 2024
cf31ae0
let pointing_device.c import default parameter values for hires scrol…
eynsai Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/features/pointing_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ Ideally, new sensor hardware should be added to `drivers/sensors/` and `quantum/
| Setting | Description | Default |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `MOUSE_EXTENDED_REPORT` | (Optional) Enables support for extended mouse reports. (-32767 to 32767, instead of just -127 to 127). | _not defined_ |
| `WHEEL_EXTENDED_REPORT` | (Optional) Enables support for extended wheel reports. (-32767 to 32767, instead of just -127 to 127). | _not defined_ |
| `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_ |
Expand All @@ -418,6 +419,30 @@ The `POINTING_DEVICE_CS_PIN`, `POINTING_DEVICE_SDIO_PIN`, and `POINTING_DEVICE_S
Any pointing device with a lift/contact status can integrate inertial cursor feature into its driver, controlled by `POINTING_DEVICE_GESTURES_CURSOR_GLIDE_ENABLE`. e.g. PMW3360 can use Lift_Stat from Motion register. Note that `POINTING_DEVICE_MOTION_PIN` cannot be used with this feature; continuous polling of `get_report()` is needed to generate glide reports.
:::

## High Resolution Scrolling

| Setting | Description | Default |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `POINTING_DEVICE_HIRES_SCROLL_ENABLE` | (Optional) Enables high resolution scrolling. | _not defined_ |
| `POINTING_DEVICE_HIRES_SCROLL_MULTIPLIER`| (Optional) Resolution mutiplier value used by high resolution scrolling. Must be between 1 and 127, inclusive. | `120` |
| `POINTING_DEVICE_HIRES_SCROLL_EXPONENT` | (Optional) Resolution exponent value used by high resolution scrolling. Must be between 1 and 127, inclusive. | `0` |

The `POINTING_DEVICE_HIRES_SCROLL_ENABLE` setting enables smooth and continuous scrolling when using trackballs or high-end encoders as mouse wheels (as opposed to the typical stepped behavior of most mouse wheels).
This works by adding a resolution multiplier to the HID descriptor for mouse wheel reports, causing the host computer to interpret each wheel tick sent by the keyboard as a fraction of a normal wheel tick.
The resolution multiplier is set to `1 / (POINTING_DEVICE_HIRES_SCROLL_MULTIPLIER * (10 ^ POINTING_DEVICE_HIRES_SCROLL_EXPONENT))`, which is `1 / 120` by default.
If even smoother scrolling than provided by this default value is desired, first try using `#define POINTING_DEVICE_HIRES_SCROLL_EXPONENT 1` which will result in a multiplier of `1 / 1200`.

::: warning
High resolution scrolling usually results in larger and/or more frequent mouse reports. This can result in overflow errors and overloading of the host computer's input buffer.
To deal with these issues, define `WHEEL_EXTENDED_REPORT` and throttle the rate at which mouse reports are sent.
:::

::: warning
Many programs, especially those that implement their own smoothing for scrolling, don't work well when they receive simultaneous vertical and horizontal wheel inputs (e.g. from high resolution drag-scroll using a trackball).
These programs typically implement their smoothing in a way that assumes the user will only scroll in one axis at a time, resulting in slow or jittery motion when trying to scroll at an angle.
This can be addressed by snapping scrolling to one axis at a time.
:::

## Split Keyboard Configuration

The following configuration options are only available when using `SPLIT_POINTING_ENABLE` see [data sync options](split_keyboard#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.
Expand Down
45 changes: 30 additions & 15 deletions quantum/pointing_device/pointing_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ uint16_t pointing_device_get_shared_cpi(void) {

static report_mouse_t local_mouse_report = {};
static bool pointing_device_force_send = false;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
static uint16_t hires_scroll_resolution;
#endif

extern const pointing_device_driver_t pointing_device_driver;

Expand Down Expand Up @@ -155,6 +158,12 @@ __attribute__((weak)) void pointing_device_init(void) {
# endif
#endif
}
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
hires_scroll_resolution = POINTING_DEVICE_HIRES_SCROLL_MULTIPLIER;
eynsai marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < POINTING_DEVICE_HIRES_SCROLL_EXPONENT; i++) {
hires_scroll_resolution *= 10;
}
#endif

pointing_device_init_kb();
pointing_device_init_user();
Expand Down Expand Up @@ -377,28 +386,28 @@ void pointing_device_set_cpi_on_side(bool left, uint16_t cpi) {
}

/**
* @brief clamps int16_t to int8_t
* @brief clamps int16_t to int8_t, or int32_t to int16_t
*
* @param[in] int16_t value
* @return int8_t clamped value
* @param[in] hv_clamp_range_t value
* @return mouse_hv_report_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;
static inline mouse_hv_report_t pointing_device_hv_clamp(hv_clamp_range_t value) {
if (value < HV_REPORT_MIN) {
return HV_REPORT_MIN;
} else if (value > HV_REPORT_MAX) {
return HV_REPORT_MAX;
} else {
return value;
}
}

/**
* @brief clamps int16_t to int8_t
* @brief clamps int16_t to int8_t, or int32_t to int16_t
*
* @param[in] clamp_range_t value
* @param[in] xy_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) {
static inline mouse_xy_report_t pointing_device_xy_clamp(xy_clamp_range_t value) {
if (value < XY_REPORT_MIN) {
return XY_REPORT_MIN;
} else if (value > XY_REPORT_MAX) {
Expand All @@ -419,10 +428,10 @@ static inline mouse_xy_report_t pointing_device_xy_clamp(clamp_range_t value) {
* @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.x = pointing_device_xy_clamp((xy_clamp_range_t)left_report.x + right_report.x);
left_report.y = pointing_device_xy_clamp((xy_clamp_range_t)left_report.y + right_report.y);
left_report.h = pointing_device_hv_clamp((hv_clamp_range_t)left_report.h + right_report.h);
left_report.v = pointing_device_hv_clamp((hv_clamp_range_t)left_report.v + right_report.v);
left_report.buttons |= right_report.buttons;
return left_report;
}
Expand Down Expand Up @@ -502,3 +511,9 @@ __attribute__((weak)) void pointing_device_keycode_handler(uint16_t keycode, boo
pointing_device_send();
}
}

#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
uint16_t pointing_device_get_hires_scroll_resolution(void) {
return hires_scroll_resolution;
}
#endif
18 changes: 16 additions & 2 deletions quantum/pointing_device/pointing_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,21 @@ typedef enum {
#ifdef MOUSE_EXTENDED_REPORT
# define XY_REPORT_MIN INT16_MIN
# define XY_REPORT_MAX INT16_MAX
typedef int32_t clamp_range_t;
typedef int32_t xy_clamp_range_t;
#else
# define XY_REPORT_MIN INT8_MIN
# define XY_REPORT_MAX INT8_MAX
typedef int16_t clamp_range_t;
typedef int16_t xy_clamp_range_t;
#endif

#ifdef WHEEL_EXTENDED_REPORT
# define HV_REPORT_MIN INT16_MIN
# define HV_REPORT_MAX INT16_MAX
typedef int32_t hv_clamp_range_t;
#else
# define HV_REPORT_MIN INT8_MIN
# define HV_REPORT_MAX INT8_MAX
typedef int16_t hv_clamp_range_t;
#endif

void pointing_device_init(void);
Expand All @@ -118,6 +128,10 @@ uint8_t pointing_device_handle_buttons(uint8_t buttons, bool pressed, poi
report_mouse_t pointing_device_adjust_by_defines(report_mouse_t mouse_report);
void pointing_device_keycode_handler(uint16_t keycode, bool pressed);

#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
uint16_t pointing_device_get_hires_scroll_resolution(void);
#endif

#if defined(SPLIT_POINTING_ENABLE)
void pointing_device_set_shared_report(report_mouse_t report);
uint16_t pointing_device_get_shared_cpi(void);
Expand Down
10 changes: 5 additions & 5 deletions quantum/pointing_device/pointing_device_drivers.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ const pointing_device_driver_t pointing_device_driver = {
};
#elif defined(POINTING_DEVICE_DRIVER_pimoroni_trackball)

mouse_xy_report_t pimoroni_trackball_adapt_values(clamp_range_t* offset) {
mouse_xy_report_t pimoroni_trackball_adapt_values(xy_clamp_range_t* offset) {
if (*offset > XY_REPORT_MAX) {
*offset -= XY_REPORT_MAX;
return (mouse_xy_report_t)XY_REPORT_MAX;
Expand All @@ -406,10 +406,10 @@ mouse_xy_report_t pimoroni_trackball_adapt_values(clamp_range_t* offset) {
}

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;
static uint16_t debounce = 0;
static uint8_t error_count = 0;
pimoroni_data_t pimoroni_data = {0};
static xy_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);
Expand Down
58 changes: 45 additions & 13 deletions tmk_core/protocol/chibios/usb_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ extern usb_endpoint_out_t usb_endpoints_out[USB_ENDPOINT_OUT_COUNT];
uint8_t _Alignas(2) keyboard_idle = 0;
uint8_t _Alignas(2) keyboard_protocol = 1;
uint8_t keyboard_led_state = 0;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
uint8_t hires_scroll_state = 0;
#endif

static bool __attribute__((__unused__)) send_report_buffered(usb_endpoint_in_lut_t endpoint, void *report, size_t size);
static void __attribute__((__unused__)) flush_report_buffered(usb_endpoint_in_lut_t endpoint, bool padded);
Expand Down Expand Up @@ -84,7 +87,7 @@ static const USBDescriptor *usb_get_descriptor_cb(USBDriver *usbp, uint8_t dtype

static USBDescriptor descriptor;
descriptor.ud_string = NULL;
descriptor.ud_size = get_usb_descriptor(setup->wValue.word, setup->wIndex, setup->wLength, (const void **const) & descriptor.ud_string);
descriptor.ud_size = get_usb_descriptor(setup->wValue.word, setup->wIndex, setup->wLength, (const void **const)&descriptor.ud_string);

if (descriptor.ud_string == NULL) {
return NULL;
Expand Down Expand Up @@ -244,18 +247,32 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) {

static uint8_t _Alignas(4) set_report_buf[2];

static void set_led_transfer_cb(USBDriver *usbp) {
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;

if (setup->wLength == 2) {
uint8_t report_id = set_report_buf[0];
if ((report_id == REPORT_ID_KEYBOARD) || (report_id == REPORT_ID_NKRO)) {
#if !defined(KEYBOARD_SHARED_EP)
static void set_transfer_cb_keyboard(USBDriver *usbp) {
keyboard_led_state = set_report_buf[0];
}
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) && defined(POINTING_DEVICE_HIRES_SCROLL_ENABLE)
static void set_transfer_cb_mouse(USBDriver *usbp) {
hires_scroll_state = set_report_buf[0];
}
#endif
#if defined(SHARED_EP_ENABLE)
static void set_transfer_cb_shared(USBDriver *usbp) {
uint8_t report_id = set_report_buf[0];
switch (report_id) {
case REPORT_ID_KEYBOARD:
case REPORT_ID_NKRO:
keyboard_led_state = set_report_buf[1];
}
} else {
keyboard_led_state = set_report_buf[0];
return;
# if defined(POINTING_DEVICE_HIRES_SCROLL_ENABLE)
case REPORT_ID_MOUSE:
hires_scroll_state = set_report_buf[1];
return;
# endif
}
}
#endif

static bool usb_requests_hook_cb(USBDriver *usbp) {
usb_control_request_t *setup = (usb_control_request_t *)usbp->setup;
Expand All @@ -282,12 +299,21 @@ static bool usb_requests_hook_cb(USBDriver *usbp) {
switch (setup->bRequest) {
case HID_REQ_SetReport:
switch (setup->wIndex) {
#if !defined(KEYBOARD_SHARED_EP)
case KEYBOARD_INTERFACE:
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
case SHARED_INTERFACE:
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_transfer_cb_keyboard);
return true;
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) && defined(POINTING_DEVICE_HIRES_SCROLL_ENABLE)
case MOUSE_INTERFACE:
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_transfer_cb_mouse);
return true;
#endif
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb);
#if defined(SHARED_EP_ENABLE)
case SHARED_INTERFACE:
usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_transfer_cb_shared);
return true;
#endif
}
break;
case HID_REQ_SetProtocol:
Expand Down Expand Up @@ -482,6 +508,12 @@ void send_mouse(report_mouse_t *report) {
#endif
}

#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
bool is_hires_scroll_on(void) {
return hires_scroll_state > 0;
}
#endif

/* ---------------------------------------------------------
* Extrakey functions
* ---------------------------------------------------------
Expand Down
10 changes: 9 additions & 1 deletion tmk_core/protocol/host.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ host_driver_t *host_get_driver(void) {
#ifdef SPLIT_KEYBOARD
uint8_t split_led_state = 0;
void set_split_host_keyboard_leds(uint8_t led_state) {
split_led_state = led_state;
split_led_state = led_state;
}
#endif

Expand Down Expand Up @@ -129,6 +129,14 @@ void host_mouse_send(report_mouse_t *report) {
(*driver->send_mouse)(report);
}

__attribute__((weak)) bool is_hires_scroll_on(void) {
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
return true;
#else
return false;
#endif
}

void host_system_send(uint16_t usage) {
if (usage == last_system_usage) return;
last_system_usage = usage;
Expand Down
1 change: 1 addition & 0 deletions tmk_core/protocol/host_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ typedef struct {
void (*send_extra)(report_extra_t *);
} host_driver_t;

bool is_hires_scroll_on(void);
void send_joystick(report_joystick_t *report);
void send_digitizer(report_digitizer_t *report);
void send_programmable_button(report_programmable_button_t *report);
Loading