add "inertia" mode for mouse keys (#18774)

Co-authored-by: Selene ToyKeeper <git@toykeeper.net>
This commit is contained in:
Selene ToyKeeper 2022-10-26 13:49:44 -06:00 committed by GitHub
parent 4c0c491e31
commit 2a61bfc27d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 208 additions and 12 deletions

View File

@ -48,8 +48,9 @@ Mouse keys supports three different modes to move the cursor:
* **Kinetic:** Holding movement keys accelerates the cursor with its speed following a quadratic curve until it reaches its maximum speed. * **Kinetic:** Holding movement keys accelerates the cursor with its speed following a quadratic curve until it reaches its maximum speed.
* **Constant:** Holding movement keys moves the cursor at constant speeds. * **Constant:** Holding movement keys moves the cursor at constant speeds.
* **Combined:** Holding movement keys accelerates the cursor until it reaches its maximum speed, but holding acceleration and movement keys simultaneously moves the cursor at constant speeds. * **Combined:** Holding movement keys accelerates the cursor until it reaches its maximum speed, but holding acceleration and movement keys simultaneously moves the cursor at constant speeds.
* **Inertia:** Cursor accelerates when key held, and decelerates after key release. Tracks X and Y velocity separately for more nuanced movements. Applies to cursor only, not scrolling.
The same principle applies to scrolling. The same principle applies to scrolling, in most modes.
Configuration options that are times, intervals or delays are given in milliseconds. Scroll speed is given as multiples of the default scroll step. For example, a scroll speed of 8 means that each scroll action covers 8 times the length of the default scroll step as defined by your operating system or application. Configuration options that are times, intervals or delays are given in milliseconds. Scroll speed is given as multiples of the default scroll step. For example, a scroll speed of 8 means that each scroll action covers 8 times the length of the default scroll step as defined by your operating system or application.
@ -170,6 +171,37 @@ To use combined speed mode, you must at least define `MK_COMBINED` in your keyma
#define MK_COMBINED #define MK_COMBINED
``` ```
### Inertia mode
This mode provides smooth motion, like sliding on ice. The cursor accelerates
along a quadratic curve while a key is held, then glides to a stop after the
key is released. Vertical and horizontal movements are tracked independently,
so the cursor can move in many directions and make curves.
Cannot be used at the same time as Kinetic mode, Constant mode, or Combined mode.
Recommended settings in your keymaps `config.h` file:
|Define |Default |Description |
|----------------------------|---------|-----------------------------------------------------------|
|`MOUSEKEY_INERTIA` |undefined|Enable Inertia mode |
|`MOUSEKEY_DELAY` |150 |Delay between pressing a movement key and cursor movement |
|`MOUSEKEY_INTERVAL` |16 |Time between cursor movements in milliseconds (16 = 60fps) |
|`MOUSEKEY_MAX_SPEED` |32 |Maximum cursor speed at which acceleration stops |
|`MOUSEKEY_TIME_TO_MAX` |32 |Number of frames until maximum cursor speed is reached |
|`MOUSEKEY_FRICTION` |24 |How quickly the cursor stops after releasing a key |
|`MOUSEKEY_MOVE_DELTA` |1 |How much to move on first frame (1 strongly recommended) |
Tips:
* Set `MOUSEKEY_DELAY` to roughly the same value as your host computer's key repeat delay, in ms. Recommended values are 100 to 300.
* Set `MOUSEKEY_INTERVAL` to a value of 1000 / your monitor's FPS. For 60 FPS, 1000/60 = 16.
* Set `MOUSEKEY_MAX_SPEED` based on your screen resolution and refresh rate, like Width / FPS. For example, 1920 pixels / 60 FPS = 32 pixels per frame.
* Set `MOUSEKEY_TIME_TO_MAX` to a value of approximately FPS / 2, to make it reach full speed in half a second (or so).
* Set `MOUSEKEY_FRICTION` to something between 1 and 255. Lower makes the cursor glide longer. Values from 8 to 40 are the most effective.
* Keep `MOUSEKEY_MOVE_DELTA` at 1. This allows precise movements before the gliding effect starts.
* Mouse wheel options are the same as the default accelerated mode, and do not use inertia.
## Use with PS/2 Mouse and Pointing Device ## Use with PS/2 Mouse and Pointing Device
Mouse keys button state is shared with [PS/2 mouse](feature_ps2_mouse.md) and [pointing device](feature_pointing_device.md) so mouse keys button presses can be used for clicks and drags. Mouse keys button state is shared with [PS/2 mouse](feature_ps2_mouse.md) and [pointing device](feature_pointing_device.md) so mouse keys button presses can be used for clicks and drags.

View File

@ -37,6 +37,13 @@ static void mousekey_debug(void);
static uint8_t mousekey_accel = 0; static uint8_t mousekey_accel = 0;
static uint8_t mousekey_repeat = 0; static uint8_t mousekey_repeat = 0;
static uint8_t mousekey_wheel_repeat = 0; static uint8_t mousekey_wheel_repeat = 0;
#ifdef MOUSEKEY_INERTIA
static uint8_t mousekey_frame = 0; // track whether gesture is inactive, first frame, or repeating
static int8_t mousekey_x_dir = 0; // -1 / 0 / 1 = left / neutral / right
static int8_t mousekey_y_dir = 0; // -1 / 0 / 0 = up / neutral / down
static int8_t mousekey_x_inertia = 0; // current velocity, limit +/- MOUSEKEY_TIME_TO_MAX
static int8_t mousekey_y_inertia = 0; // ...
#endif
#ifdef MK_KINETIC_SPEED #ifdef MK_KINETIC_SPEED
static uint16_t mouse_timer = 0; static uint16_t mouse_timer = 0;
#endif #endif
@ -76,6 +83,7 @@ uint8_t mk_wheel_time_to_max = MOUSEKEY_WHEEL_TIME_TO_MAX;
# ifndef MK_COMBINED # ifndef MK_COMBINED
# ifndef MK_KINETIC_SPEED # ifndef MK_KINETIC_SPEED
# ifndef MOUSEKEY_INERTIA
/* Default accelerated mode */ /* Default accelerated mode */
@ -97,6 +105,53 @@ static uint8_t move_unit(void) {
return (unit > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : (unit == 0 ? 1 : unit)); return (unit > MOUSEKEY_MOVE_MAX ? MOUSEKEY_MOVE_MAX : (unit == 0 ? 1 : unit));
} }
# else // MOUSEKEY_INERTIA mode
static int8_t move_unit(uint8_t axis) {
int16_t unit;
// handle X or Y axis
int8_t inertia, dir;
if (axis) {
inertia = mousekey_y_inertia;
dir = mousekey_y_dir;
} else {
inertia = mousekey_x_inertia;
dir = mousekey_x_dir;
}
if (mousekey_frame < 2) { // first frame(s): initial keypress moves one pixel
mousekey_frame = 1;
unit = dir * MOUSEKEY_MOVE_DELTA;
} else { // acceleration
// linear acceleration (is here for reference, but doesn't feel as good during use)
// unit = (MOUSEKEY_MOVE_DELTA * mk_max_speed * inertia) / mk_time_to_max;
// x**2 acceleration (quadratic, more precise for short movements)
int16_t percent = (inertia << 8) / mk_time_to_max;
percent = (percent * percent) >> 8;
if (inertia < 0) percent = -percent;
// unit = sign(inertia) + (percent of max speed)
if (inertia > 0)
unit = 1;
else if (inertia < 0)
unit = -1;
else
unit = 0;
unit = unit + ((mk_max_speed * percent) >> 8);
}
if (unit > MOUSEKEY_MOVE_MAX)
unit = MOUSEKEY_MOVE_MAX;
else if (unit < -MOUSEKEY_MOVE_MAX)
unit = -MOUSEKEY_MOVE_MAX;
return unit;
}
# endif // end MOUSEKEY_INERTIA mode
static uint8_t wheel_unit(void) { static uint8_t wheel_unit(void) {
uint16_t unit; uint16_t unit;
if (mousekey_accel & (1 << 0)) { if (mousekey_accel & (1 << 0)) {
@ -213,6 +268,28 @@ static uint8_t wheel_unit(void) {
# endif /* #ifndef MK_COMBINED */ # endif /* #ifndef MK_COMBINED */
# ifdef MOUSEKEY_INERTIA
static int8_t calc_inertia(int8_t direction, int8_t velocity) {
// simulate acceleration and deceleration
// deceleration
if ((direction > -1) && (velocity < 0))
velocity = (velocity + 1) * (256 - MOUSEKEY_FRICTION) / 256;
else if ((direction < 1) && (velocity > 0))
velocity = velocity * (256 - MOUSEKEY_FRICTION) / 256;
// acceleration
if ((direction > 0) && (velocity < mk_time_to_max))
velocity++;
else if ((direction < 0) && (velocity > -mk_time_to_max))
velocity--;
return velocity;
}
# endif
void mousekey_task(void) { void mousekey_task(void) {
// report cursor and scroll movement independently // report cursor and scroll movement independently
report_mouse_t tmpmr = mouse_report; report_mouse_t tmpmr = mouse_report;
@ -222,6 +299,32 @@ void mousekey_task(void) {
mouse_report.v = 0; mouse_report.v = 0;
mouse_report.h = 0; mouse_report.h = 0;
# ifdef MOUSEKEY_INERTIA
// if an animation is in progress and it's time for the next frame
if ((mousekey_frame) && timer_elapsed(last_timer_c) > ((mousekey_frame > 1) ? mk_interval : mk_delay * 10)) {
mousekey_x_inertia = calc_inertia(mousekey_x_dir, mousekey_x_inertia);
mousekey_y_inertia = calc_inertia(mousekey_y_dir, mousekey_y_inertia);
mouse_report.x = move_unit(0);
mouse_report.y = move_unit(1);
// prevent sticky "drift"
if ((!mousekey_x_dir) && (!mousekey_x_inertia)) tmpmr.x = 0;
if ((!mousekey_y_dir) && (!mousekey_y_inertia)) tmpmr.y = 0;
if (mousekey_frame < 2) mousekey_frame++;
}
// reset if not moving and no movement keys are held
if ((!mousekey_x_dir) && (!mousekey_y_dir) && (!mousekey_x_inertia) && (!mousekey_y_inertia)) {
mousekey_frame = 0;
tmpmr.x = 0;
tmpmr.y = 0;
}
# else // default acceleration
if ((tmpmr.x || tmpmr.y) && timer_elapsed(last_timer_c) > (mousekey_repeat ? mk_interval : mk_delay * 10)) { if ((tmpmr.x || tmpmr.y) && timer_elapsed(last_timer_c) > (mousekey_repeat ? mk_interval : mk_delay * 10)) {
if (mousekey_repeat != UINT8_MAX) mousekey_repeat++; if (mousekey_repeat != UINT8_MAX) mousekey_repeat++;
if (tmpmr.x != 0) mouse_report.x = move_unit() * ((tmpmr.x > 0) ? 1 : -1); if (tmpmr.x != 0) mouse_report.x = move_unit() * ((tmpmr.x > 0) ? 1 : -1);
@ -239,6 +342,9 @@ void mousekey_task(void) {
} }
} }
} }
# endif // MOUSEKEY_INERTIA or not
if ((tmpmr.v || tmpmr.h) && timer_elapsed(last_timer_w) > (mousekey_wheel_repeat ? mk_wheel_interval : mk_wheel_delay * 10)) { if ((tmpmr.v || tmpmr.h) && timer_elapsed(last_timer_w) > (mousekey_wheel_repeat ? mk_wheel_interval : mk_wheel_delay * 10)) {
if (mousekey_wheel_repeat != UINT8_MAX) mousekey_wheel_repeat++; if (mousekey_wheel_repeat != UINT8_MAX) mousekey_wheel_repeat++;
if (tmpmr.v != 0) mouse_report.v = wheel_unit() * ((tmpmr.v > 0) ? 1 : -1); if (tmpmr.v != 0) mouse_report.v = wheel_unit() * ((tmpmr.v > 0) ? 1 : -1);
@ -260,6 +366,7 @@ void mousekey_task(void) {
if (has_mouse_report_changed(&mouse_report, &tmpmr) || should_mousekey_report_send(&mouse_report)) { if (has_mouse_report_changed(&mouse_report, &tmpmr) || should_mousekey_report_send(&mouse_report)) {
mousekey_send(); mousekey_send();
} }
// save the state for later
memcpy(&mouse_report, &tmpmr, sizeof(tmpmr)); memcpy(&mouse_report, &tmpmr, sizeof(tmpmr));
} }
@ -270,6 +377,19 @@ void mousekey_on(uint8_t code) {
} }
# endif /* #ifdef MK_KINETIC_SPEED */ # endif /* #ifdef MK_KINETIC_SPEED */
# ifdef MOUSEKEY_INERTIA
// initial keypress sets impulse and activates first frame of movement
if ((code == KC_MS_UP) || (code == KC_MS_DOWN)) {
mousekey_y_dir = (code == KC_MS_DOWN) ? 1 : -1;
if (mousekey_frame < 2) mouse_report.y = move_unit(1);
} else if ((code == KC_MS_LEFT) || (code == KC_MS_RIGHT)) {
mousekey_x_dir = (code == KC_MS_RIGHT) ? 1 : -1;
if (mousekey_frame < 2) mouse_report.x = move_unit(0);
}
# else // no inertia
if (code == KC_MS_UP) if (code == KC_MS_UP)
mouse_report.y = move_unit() * -1; mouse_report.y = move_unit() * -1;
else if (code == KC_MS_DOWN) else if (code == KC_MS_DOWN)
@ -278,6 +398,9 @@ void mousekey_on(uint8_t code) {
mouse_report.x = move_unit() * -1; mouse_report.x = move_unit() * -1;
else if (code == KC_MS_RIGHT) else if (code == KC_MS_RIGHT)
mouse_report.x = move_unit(); mouse_report.x = move_unit();
# endif // inertia or not
else if (code == KC_MS_WH_UP) else if (code == KC_MS_WH_UP)
mouse_report.v = wheel_unit(); mouse_report.v = wheel_unit();
else if (code == KC_MS_WH_DOWN) else if (code == KC_MS_WH_DOWN)
@ -297,6 +420,20 @@ void mousekey_on(uint8_t code) {
} }
void mousekey_off(uint8_t code) { void mousekey_off(uint8_t code) {
# ifdef MOUSEKEY_INERTIA
// key release clears impulse unless opposite direction is held
if ((code == KC_MS_UP) && (mousekey_y_dir < 1))
mousekey_y_dir = 0;
else if ((code == KC_MS_DOWN) && (mousekey_y_dir > -1))
mousekey_y_dir = 0;
else if ((code == KC_MS_LEFT) && (mousekey_x_dir < 1))
mousekey_x_dir = 0;
else if ((code == KC_MS_RIGHT) && (mousekey_x_dir > -1))
mousekey_x_dir = 0;
# else // no inertia
if (code == KC_MS_UP && mouse_report.y < 0) if (code == KC_MS_UP && mouse_report.y < 0)
mouse_report.y = 0; mouse_report.y = 0;
else if (code == KC_MS_DOWN && mouse_report.y > 0) else if (code == KC_MS_DOWN && mouse_report.y > 0)
@ -305,6 +442,9 @@ void mousekey_off(uint8_t code) {
mouse_report.x = 0; mouse_report.x = 0;
else if (code == KC_MS_RIGHT && mouse_report.x > 0) else if (code == KC_MS_RIGHT && mouse_report.x > 0)
mouse_report.x = 0; mouse_report.x = 0;
# endif // inertia or not
else if (code == KC_MS_WH_UP && mouse_report.v > 0) else if (code == KC_MS_WH_UP && mouse_report.v > 0)
mouse_report.v = 0; mouse_report.v = 0;
else if (code == KC_MS_WH_DOWN && mouse_report.v < 0) else if (code == KC_MS_WH_DOWN && mouse_report.v < 0)
@ -476,6 +616,13 @@ void mousekey_clear(void) {
mousekey_repeat = 0; mousekey_repeat = 0;
mousekey_wheel_repeat = 0; mousekey_wheel_repeat = 0;
mousekey_accel = 0; mousekey_accel = 0;
#ifdef MOUSEKEY_INERTIA
mousekey_frame = 0;
mousekey_x_inertia = 0;
mousekey_y_inertia = 0;
mousekey_x_dir = 0;
mousekey_y_dir = 0;
#endif
} }
static void mousekey_debug(void) { static void mousekey_debug(void) {

View File

@ -36,34 +36,48 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# endif # endif
# ifndef MOUSEKEY_MOVE_DELTA # ifndef MOUSEKEY_MOVE_DELTA
# ifndef MK_KINETIC_SPEED # if defined(MK_KINETIC_SPEED)
# define MOUSEKEY_MOVE_DELTA 8
# else
# define MOUSEKEY_MOVE_DELTA 16 # define MOUSEKEY_MOVE_DELTA 16
# elif defined(MOUSEKEY_INERTIA)
# define MOUSEKEY_MOVE_DELTA 1
# else
# define MOUSEKEY_MOVE_DELTA 8
# endif # endif
# endif # endif
# ifndef MOUSEKEY_WHEEL_DELTA # ifndef MOUSEKEY_WHEEL_DELTA
# define MOUSEKEY_WHEEL_DELTA 1 # define MOUSEKEY_WHEEL_DELTA 1
# endif # endif
# ifndef MOUSEKEY_DELAY # ifndef MOUSEKEY_DELAY
# ifndef MK_KINETIC_SPEED # if defined(MK_KINETIC_SPEED)
# define MOUSEKEY_DELAY 10
# else
# define MOUSEKEY_DELAY 5 # define MOUSEKEY_DELAY 5
# elif defined(MOUSEKEY_INERTIA)
# define MOUSEKEY_DELAY 150 // allow single-pixel movements before repeat activates
# else
# define MOUSEKEY_DELAY 10
# endif # endif
# endif # endif
# ifndef MOUSEKEY_INTERVAL # ifndef MOUSEKEY_INTERVAL
# ifndef MK_KINETIC_SPEED # if defined(MK_KINETIC_SPEED)
# define MOUSEKEY_INTERVAL 20
# else
# define MOUSEKEY_INTERVAL 10 # define MOUSEKEY_INTERVAL 10
# elif defined(MOUSEKEY_INERTIA)
# define MOUSEKEY_INTERVAL 16 // 60 fps
# else
# define MOUSEKEY_INTERVAL 20
# endif # endif
# endif # endif
# ifndef MOUSEKEY_MAX_SPEED # ifndef MOUSEKEY_MAX_SPEED
# define MOUSEKEY_MAX_SPEED 10 # if defined(MOUSEKEY_INERTIA)
# define MOUSEKEY_MAX_SPEED 32
# else
# define MOUSEKEY_MAX_SPEED 10
# endif
# endif # endif
# ifndef MOUSEKEY_TIME_TO_MAX # ifndef MOUSEKEY_TIME_TO_MAX
# define MOUSEKEY_TIME_TO_MAX 30 # if defined(MOUSEKEY_INERTIA)
# define MOUSEKEY_TIME_TO_MAX 32
# else
# define MOUSEKEY_TIME_TO_MAX 30
# endif
# endif # endif
# ifndef MOUSEKEY_WHEEL_DELAY # ifndef MOUSEKEY_WHEEL_DELAY
# define MOUSEKEY_WHEEL_DELAY 10 # define MOUSEKEY_WHEEL_DELAY 10
@ -78,6 +92,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
# define MOUSEKEY_WHEEL_TIME_TO_MAX 40 # define MOUSEKEY_WHEEL_TIME_TO_MAX 40
# endif # endif
# ifndef MOUSEKEY_FRICTION
# define MOUSEKEY_FRICTION 24 // 0 to 255
# endif
# ifndef MOUSEKEY_INITIAL_SPEED # ifndef MOUSEKEY_INITIAL_SPEED
# define MOUSEKEY_INITIAL_SPEED 100 # define MOUSEKEY_INITIAL_SPEED 100
# endif # endif