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 (with feature report parsing) #24424

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 28 additions & 0 deletions docs/features/pointing_device.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,34 @@ 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`.

The function `pointing_device_get_hires_scroll_resolution()` can be called to get the pre-computed resolution multiplier value as a `uint16_t`.

The function `is_hires_scroll_on()` provides a way to determine the state of high resolution scrolling. This function will return `true` if the host computer has sent a feature report indicating that it supports high resolution wheel input.

::: 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
58 changes: 45 additions & 13 deletions tmk_core/protocol/chibios/usb_main.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2023 Stefan Kerkmann

Check failure on line 1 in tmk_core/protocol/chibios/usb_main.c

View workflow job for this annotation

GitHub Actions / lint

Requires Formatting
// Copyright 2020-2021 Ryan (@fauxpark)
// Copyright 2020 Nick Brassel (@tzarc)
// Copyright 2020 a-chol
Expand Down Expand Up @@ -57,6 +57,9 @@
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 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 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 @@
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 @@
#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
@@ -1,4 +1,4 @@
/*

Check failure on line 1 in tmk_core/protocol/host.c

View workflow job for this annotation

GitHub Actions / lint

Requires Formatting
Copyright 2011,2012 Jun Wako <[email protected]>

This program is free software: you can redistribute it and/or modify
Expand Down Expand Up @@ -55,7 +55,7 @@
#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 @@
(*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);
54 changes: 42 additions & 12 deletions tmk_core/protocol/lufa/lufa.c
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ uint8_t keyboard_idle = 0;
/* 0: Boot Protocol, 1: Report Protocol(default) */
uint8_t keyboard_protocol = 1;
static uint8_t keyboard_led_state = 0;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
static uint8_t hires_scroll_state = 0;
#endif

static report_keyboard_t keyboard_report_sent;

Expand Down Expand Up @@ -439,29 +442,50 @@ void EVENT_USB_Device_ControlRequest(void) {
if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE)) {
// Interface
switch (USB_ControlRequest.wIndex) {
#if !defined(KEYBOARD_SHARED_EP)
case KEYBOARD_INTERFACE:
#if defined(SHARED_EP_ENABLE) && !defined(KEYBOARD_SHARED_EP)
case SHARED_INTERFACE:
Endpoint_ClearSETUP();
while (!(Endpoint_IsOUTReceived())) {
if (USB_DeviceState == DEVICE_STATE_Unattached) return;
}
keyboard_led_state = Endpoint_Read_8();
Endpoint_ClearOUT();
Endpoint_ClearStatusStage();
break;
#endif
#if defined(MOUSE_ENABLE) && !defined(MOUSE_SHARED_EP) && defined(POINTING_DEVICE_HIRES_SCROLL_ENABLE)
case MOUSE_INTERFACE:
Endpoint_ClearSETUP();

while (!(Endpoint_IsOUTReceived())) {
if (USB_DeviceState == DEVICE_STATE_Unattached) return;
}

if (Endpoint_BytesInEndpoint() == 2) {
uint8_t report_id = Endpoint_Read_8();

if (report_id == REPORT_ID_KEYBOARD || report_id == REPORT_ID_NKRO) {
hires_scroll_state = Endpoint_Read_8();
Endpoint_ClearOUT();
Endpoint_ClearStatusStage();
break;
#endif
#if defined(SHARED_EP_ENABLE)
case SHARED_INTERFACE:
Endpoint_ClearSETUP();
while (!(Endpoint_IsOUTReceived())) {
if (USB_DeviceState == DEVICE_STATE_Unattached) return;
}
uint8_t report_id = Endpoint_Read_8();
switch (report_id) {
case REPORT_ID_KEYBOARD:
case REPORT_ID_NKRO:
keyboard_led_state = Endpoint_Read_8();
}
} else {
keyboard_led_state = Endpoint_Read_8();
break;
# if defined(POINTING_DEVICE_HIRES_SCROLL_ENABLE)
case REPORT_ID_MOUSE:
hires_scroll_state = Endpoint_Read_8();
break;
# endif
}

Endpoint_ClearOUT();
Endpoint_ClearStatusStage();
break;
#endif
}
}

Expand Down Expand Up @@ -575,6 +599,12 @@ static void send_extra(report_extra_t *report) {
#endif
}

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

void send_joystick(report_joystick_t *report) {
#ifdef JOYSTICK_ENABLE
send_report(JOYSTICK_IN_EPNUM, report, sizeof(report_joystick_t));
Expand Down
62 changes: 58 additions & 4 deletions tmk_core/protocol/vusb/vusb.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,11 @@ _Static_assert(TOTAL_INTERFACES <= MAX_INTERFACES, "There are not enough availab
#endif

static uint8_t keyboard_led_state = 0;
uint8_t keyboard_idle = 0;
uint8_t keyboard_protocol = 1;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
static uint8_t hires_scroll_state = 0;
#endif
uint8_t keyboard_idle = 0;
uint8_t keyboard_protocol = 1;

static report_keyboard_t keyboard_report_sent;

Expand Down Expand Up @@ -258,6 +261,12 @@ static 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

static void send_extra(report_extra_t *report) {
#ifdef EXTRAKEY_ENABLE
send_report(SHARED_IN_EPNUM, report, sizeof(report_extra_t));
Expand Down Expand Up @@ -287,7 +296,11 @@ void send_programmable_button(report_programmable_button_t *report) {
*------------------------------------------------------------------*/
static struct {
uint16_t len;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
enum { NONE, SET_LED, SET_HIRES_SCROLL } kind;
#else
enum { NONE, SET_LED } kind;
#endif
} last_req;

usbMsgLen_t usbFunctionSetup(uchar data[8]) {
Expand All @@ -312,12 +325,45 @@ usbMsgLen_t usbFunctionSetup(uchar data[8]) {
return 1;
case USBRQ_HID_SET_REPORT:
dprint("SET_REPORT:");
// Report Type: 0x02(Out)/ReportID: 0x00(none) && Interface: 0(keyboard)
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
// note: when using vusb, the mouse always uses the shared endpoint, so we don't check for MOUSE_INTERFACE
switch (rq->wIndex.word) {
# ifndef KEYBOARD_SHARED_EP
case KEYBOARD_INTERFACE:
// Report Type: 0x02(Out) / ReportID: none
if (rq->wValue.word == 0x0200) {
dprint("SET_LED:");
last_req.kind = SET_LED;
last_req.len = rq->wLength.word;
}
break;
# endif
# ifdef SHARED_EP_ENABLE
case SHARED_INTERFACE:
switch (rq->wValue.word) {
// 0x02XX indicates output from computer
case 0x0200 + REPORT_ID_KEYBOARD:
case 0x0200 + REPORT_ID_NKRO:
dprint("SET_LED:");
last_req.kind = SET_LED;
last_req.len = rq->wLength.word;
break;
case 0x0200 + REPORT_ID_MOUSE:
dprint("SET_HIRES_SCROLL:");
last_req.kind = SET_HIRES_SCROLL;
last_req.len = rq->wLength.word;
break;
}
break;
# endif
}
#else
if (rq->wValue.word == 0x0200 && rq->wIndex.word == KEYBOARD_INTERFACE) {
dprint("SET_LED:");
last_req.kind = SET_LED;
last_req.len = rq->wLength.word;
last_req.len = rq->wLength.word;
}
#endif
return USB_NO_MSG; // to get data in usbFunctionWrite
case USBRQ_HID_SET_IDLE:
keyboard_idle = (rq->wValue.word & 0xFF00) >> 8;
Expand Down Expand Up @@ -352,6 +398,14 @@ uchar usbFunctionWrite(uchar *data, uchar len) {
last_req.len = 0;
return 1;
break;
#ifdef POINTING_DEVICE_HIRES_SCROLL_ENABLE
case SET_HIRES_SCROLL:
dprintf("SET_HIRES_SCROLL: %02X\n", data[0]);
hires_scroll_state = data[0];
last_req.len = 0;
return 1;
break;
#endif
case NONE:
default:
return -1;
Expand Down
Loading