diff --git a/plugins/Kaleidoscope-MouseKeys/README.md b/plugins/Kaleidoscope-MouseKeys/README.md
index 4b3e123b..5730a279 100644
--- a/plugins/Kaleidoscope-MouseKeys/README.md
+++ b/plugins/Kaleidoscope-MouseKeys/README.md
@@ -37,21 +37,23 @@ void setup() {
The plugin provides a number of keys one can put on the keymap, that allow
control of the mouse. They can be divided into a few groups:
-### Cursor movement
+### Mouse buttons
+
+Mouse button keys are straightforward; pressing one is the same as pressing the
+corresponding button on a physical mouse. You can hold a mouse button key to
+perform drag gestures, as you might expect. MouseKeys supports five mouse
+buttons: left, right, middle, previous, and next.
+
+* `Key_mouseBtnL`, `Key_mouseBtnM`, `Key_mouseBtnR`, `Key_mouseBtnP`,
+ `Key_mouseBtnN`: The left, middle, right, previous, and next mouse buttons,
+ respectively.
+
-The simplest set of keys are the mouse cursor movement keys. These move the
-cursor one direction or the other, with speed and acceleration factored in. When
-a mouse cursor movement key is held down, it will move `.speed` pixels each
-`.speedDelay` milliseconds without acceleration. But when `.accelSpeed` is
-non-zero (and it is not zero by default,
-see [below](#accelspeed-and-acceldelay)), the speed will increase by
-`.accelSpeed` every `.accelDelay` milliseconds. Thus, unless configured
-otherwise, holding a direction will move that way at increasing speed.
+### Cursor movement
-One can hold more than one key down at the same time, and the cursor will move
-towards a direction that is the combination of the keys held. For example,
-holding the "mouse up" and "mouse right" keys together will move the cursor
-diagonally up and right.
+When a cursor movement key is pressed, the mouse cursor will begin to move
+slowly, then accelerate to full speed. Both the full speed and the time it
+takes to reach full speed are configurable.
The cursor movement keys are as follows:
@@ -60,26 +62,18 @@ The cursor movement keys are as follows:
* `Key_mouseUpL`, `Key_mouseUpR`, `Key_mouseDnL`, `Key_mouseDnR`: Move the
cursor up-left, up-right, down-left, down-right, respectively.
-### Scroll wheel
+### Scroll wheels
-Controlling the scroll wheel is similarly simple. It does not have acceleration,
-but one can control the speed with the `.wheelSpeed` and `.wheelDelay`
-properties (see below).
+Controlling the scroll wheel is similarly simple. It does not have
+acceleration, but one can control the speed with the
+`MouseKeys.setScrollInterval()` function, which controls the length of time
+between scroll events.
* `Key_mouseScrollUp`, `Key_mouseScrollDn`: Scroll the mouse wheel up or down,
respectively.
* `Key_mouseScrollL`, `Key_mouseScrollR`: Scroll the mouse wheel left or right,
respectively.
-### Buttons
-
-Buttons are even simpler than movement: there is no movement speed, nor
-acceleration involved. One just presses them.
-
-* `Key_mouseBtnL`, `Key_mouseBtnM`, `Key_mouseBtnR`, `Key_mouseBtnP`,
- `Key_mouseBtnN`: The left, middle, right, previous, and next mouse buttons,
- respectively.
-
## Warping
Warping is one of the most interesting features of the plugin, and is a feature
@@ -210,37 +204,32 @@ the following additions:
The plugin provides a `MouseKeys` object, with the following methods and
properties available:
-### `.speed` and `.speedDelay`
+### `.setCursorInitSpeed(speed)`/`.getCursorInitSpeed()`
-> These two control the speed of the mouse cursor, when a movement key is held.
-> The former, `.speed`, controls the amount of pixels the cursor moves, when it
-> has to move, and defaults to 1. The latter, `.speedDelay` is the amount of
-> time - in milliseconds - to wait between two movements, and defaults to 0, no
-> delay.
+> Controls (or returns) the current starting speed value for mouse cursor
+> movement. When a mouse movement key is pressed, the cursor starts moving at
+> this speed, then accelerates. The number is abstract, but linear, with higher
+> numbers representing faster speeds. Default starting speed is `1`.
-### `.accelSpeed` and `.accelDelay`
+### `.setCursorBaseSpeed(speed)`/`.getCursorBaseSpeed()`
-> These two properties control the speed of acceleration. The former,
-> `.accelSpeed`, controls how much the speed shall be increased at each step,
-> while the second, `.accelDelay`, controls how often (in milliseconds)
-> acceleration should be applied.
->
-> They default to 1 pixel and 50 milliseconds, respectively.
+> Controls (or returns) the current top speed value for mouse cursor movement.
+> When a mouse movement key is pressed, the cursor accelerates until it reaches
+> this speed. The number is abstract, but linear, with higher numbers
+> representing faster speeds. Default full-speed value is `50`.
-### `.wheelSpeed` and `.wheelDelay`
+### `.setCursorAccelDuration(duration)`/`.getCursorAccelDuration()`
-> The last two properties supported by the plugin control the mouse wheel
-> scrolling speed. The former, `.wheelSpeed`, controls the amount of ticks the
-> wheel shall scroll, and defaults to 1. The second, `.wheelDelay`, controls the
-> delay between two scroll events, and defaults to 50 milliseconds.
+> Controls (or returns) the current time it takes for the mouse cursor to reach
+> full speed (in milliseconds), starting from when the first movement key is
+> pressed. Default value is `800` ms.
-### `.setSpeedLimit`
+### `.setScrollInterval(interval)`/`.getScrollInterval()`
-> This method sets the maximum speed after which acceleration stops.
-> The default is 127, and the minimum value is 16 (things will not work
-> properly below 16).
+> Controls (or returns) the current scrolling speed, by setting the time between
+> mouse scroll reports (in milliseconds). Default value is `50` ms.
-### `.setWarpGridSize`
+### `.setWarpGridSize(size)`
> This method changes the size of the grid used for [warping](#warping). The
> following are valid sizes: `MOUSE_WARP_GRID_2X2`, `MOUSE_WARP_GRID_3X3`
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
index ab8c291e..0812aeee 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
@@ -1,5 +1,5 @@
/* Kaleidoscope-MouseKeys - Mouse keys for Kaleidoscope.
- * Copyright (C) 2017-2021 Keyboard.io, Inc.
+ * Copyright (C) 2017-2022 Keyboard.io, Inc.
*
* 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
@@ -29,11 +29,14 @@
#include "kaleidoscope/key_defs.h" // for Key, SYNTHETIC
#include "kaleidoscope/keyswitch_state.h" // for keyToggledOn
#include "kaleidoscope/plugin/mousekeys/MouseKeyDefs.h" // for KEY_MOUSE_BUTTON, KEY_MOUS...
-#include "kaleidoscope/plugin/mousekeys/MouseWrapper.h" // for MouseWrapper, wrapper, WAR...
+#include "kaleidoscope/plugin/mousekeys/MouseWrapper.h" // for MouseWrapper, WARP_DOWN
namespace kaleidoscope {
namespace plugin {
+#ifndef NDEPRECATED
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
uint8_t MouseKeys::speed = 1;
uint16_t MouseKeys::speedDelay = 1;
@@ -42,16 +45,14 @@ uint16_t MouseKeys::accelDelay = 64;
uint8_t MouseKeys::wheelSpeed = 1;
uint16_t MouseKeys::wheelDelay = 50;
+#pragma GCC diagnostic pop
+#endif
// =============================================================================
// Configuration functions
void MouseKeys::setWarpGridSize(uint8_t grid_size) {
- mousekeys::wrapper.warp_grid_size = grid_size;
-}
-
-void MouseKeys::setSpeedLimit(uint8_t speed_limit) {
- mousekeys::wrapper.speed_limit = speed_limit;
+ MouseWrapper.warp_grid_size = grid_size;
}
// =============================================================================
@@ -92,7 +93,6 @@ EventHandlerResult MouseKeys::onNameQuery() {
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys::onSetup() {
- kaleidoscope::Runtime.hid().mouse().setup();
kaleidoscope::Runtime.hid().absoluteMouse().setup();
return EventHandlerResult::OK;
@@ -100,23 +100,20 @@ EventHandlerResult MouseKeys::onSetup() {
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys::afterEachCycle() {
- // Check timeout for accel update interval.
- if (Runtime.hasTimeExpired(accel_start_time_, accelDelay)) {
- accel_start_time_ = Runtime.millisAtCycleStart();
- // `accel_step` determines the movement speed of the mouse pointer, and gets
- // reset to zero when no mouse movement keys is pressed (see below).
- if (mousekeys::wrapper.accel_step < 255 - accelSpeed) {
- mousekeys::wrapper.accel_step += accelSpeed;
- }
- }
+ if (directions_ == 0)
+ return EventHandlerResult::OK;
// Check timeout for position update interval.
- if (Runtime.hasTimeExpired(move_start_time_, speedDelay))
+ if (Runtime.hasTimeExpired(last_cursor_update_time_, cursor_update_interval_)) {
sendMouseMoveReport();
+ last_cursor_update_time_ += cursor_update_interval_;
+ }
// Check timeout for scroll report interval.
- if (Runtime.hasTimeExpired(wheel_start_time_, wheelDelay))
+ if (Runtime.hasTimeExpired(last_wheel_update_time_, settings_.wheel_update_interval)) {
sendMouseWheelReport();
+ last_wheel_update_time_ += settings_.wheel_update_interval;
+ }
return EventHandlerResult::OK;
}
@@ -132,10 +129,15 @@ EventHandlerResult MouseKeys::onKeyEvent(KeyEvent &event) {
// Clear button state; it will be repopulated by `onAddToReport()`, and the
// report will be sent by `afterReportingState()`.
buttons_ = 0;
+ }
- } else if (isMouseWarpKey(event.key)) {
- if (keyToggledOn(event.state)) {
+ if (keyToggledOn(event.state)) {
+ if (isMouseWarpKey(event.key)) {
+ // If a mouse warp key toggles on, we immediately send the warp report.
sendMouseWarpReport(event);
+ } else {
+ // If any non-warp mouse key toggles on, we cancel warping.
+ MouseWrapper.endWarping();
}
}
@@ -157,20 +159,27 @@ EventHandlerResult MouseKeys::afterReportingState(const KeyEvent &event) {
sendMouseButtonReport();
}
+ // If no mouse move keys were active before this event, and a mouse movement
+ // key toggled on, we need to set the move start time so that acceleration can
+ // begin correctly.
+ if ((directions_ & cursor_mask_) == 0) {
+ cursor_start_time_ = Runtime.millisAtCycleStart();
+ }
+
// A mouse key event has been successfully registered, and we have now
// gathered all the information on held mouse movement and wheel keys, so it's
// safe to update the direction information.
directions_ = pending_directions_;
pending_directions_ = 0;
- if (isMouseMoveKey(event.key)) {
- // When a cursor movement key toggles on, set the acceleration start time in
- // order to get consistent behavior.
- accel_start_time_ = Runtime.millisAtCycleStart();
- sendMouseMoveReport();
-
- } else if (isMouseWheelKey(event.key)) {
- sendMouseWheelReport();
+ if (keyToggledOn(event.state)) {
+ if (isMouseMoveKey(event.key)) {
+ sendMouseMoveReport();
+ last_cursor_update_time_ = Runtime.millisAtCycleStart();
+ } else if (isMouseWheelKey(event.key)) {
+ sendMouseWheelReport();
+ last_wheel_update_time_ = Runtime.millisAtCycleStart();
+ }
}
return EventHandlerResult::OK;
@@ -210,7 +219,7 @@ void MouseKeys::sendMouseButtonReport() const {
// -----------------------------------------------------------------------------
void MouseKeys::sendMouseWarpReport(const KeyEvent &event) const {
- mousekeys::wrapper.warp(
+ MouseWrapper.warp(
((event.key.getKeyCode() & KEY_MOUSE_WARP_END) ? WARP_END : 0x00) |
((event.key.getKeyCode() & KEY_MOUSE_UP) ? WARP_UP : 0x00) |
((event.key.getKeyCode() & KEY_MOUSE_DOWN) ? WARP_DOWN : 0x00) |
@@ -219,57 +228,148 @@ void MouseKeys::sendMouseWarpReport(const KeyEvent &event) const {
}
// -----------------------------------------------------------------------------
-void MouseKeys::sendMouseMoveReport() {
- move_start_time_ = Runtime.millisAtCycleStart();
+void MouseKeys::sendMouseMoveReport() const {
+ int8_t dx = 0;
+ int8_t dy = 0;
- int8_t vx = 0;
- int8_t vy = 0;
- uint8_t direction = directions_ & move_mask_;
+ uint8_t direction = directions_ & cursor_mask_;
- if (direction == 0) {
- // If there are no mouse movement keys held, reset speed to zero.
- mousekeys::wrapper.accel_step = 0;
- } else {
- // For each active direction, add the mouse movement speed.
+ if (direction != 0) {
+ // Calculate
+ uint8_t delta = cursorDelta();
+ // For each active direction, add the move update interval value to
+ // normalize speed of motion regardless of the frequency of updates.
if (direction & KEY_MOUSE_LEFT)
- vx -= speed;
+ dx -= delta;
if (direction & KEY_MOUSE_RIGHT)
- vx += speed;
+ dx += delta;
if (direction & KEY_MOUSE_UP)
- vy -= speed;
+ dy -= delta;
if (direction & KEY_MOUSE_DOWN)
- vy += speed;
+ dy += delta;
- // Prepare the mouse report.
- mousekeys::wrapper.move(vx, vy);
// Send the report.
+ Runtime.hid().mouse().move(dx, dy, 0, 0);
Runtime.hid().mouse().sendReport();
}
}
// -----------------------------------------------------------------------------
-void MouseKeys::sendMouseWheelReport() {
- wheel_start_time_ = Runtime.millisAtCycleStart();
+// Get the current point on the acceleration curve's x axis, translating time
+// elapsed since mouse movement started to a value between 0 and 255.
+uint8_t MouseKeys::accelStep() const {
+ uint16_t elapsed_time = Runtime.millisAtCycleStart() - cursor_start_time_;
+ uint16_t accel_duration = settings_.cursor_accel_duration;
+ if (elapsed_time > accel_duration)
+ return 255;
+ uint16_t accel_step = (uint32_t(elapsed_time) * 256) / accel_duration;
+ return uint8_t(accel_step);
+}
+
+// -----------------------------------------------------------------------------
+// Compute the acceleration factor for mouse movement. When a movement key is
+// first pressed, the cursor starts out slow then accelerates to full speed.
+// The speed during acceleration follows an approximation of a sigmoid function,
+// using two parabolas for simplicity.
+uint8_t accelFactor(uint8_t accel_step) {
+ if (accel_step < 128) {
+ uint16_t y = accel_step * accel_step;
+ return 1 + (y >> 7);
+ } else {
+ uint16_t remaining_steps = 256 - accel_step;
+
+ uint16_t y = remaining_steps * remaining_steps;
+ return 255 - (y >> 7);
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Compute the distance the mouse cursor should move in subpixels, return the
+// number of pixels the mouse should move (in active directions), and store the
+// remaining subpixels for the next move.
+uint8_t MouseKeys::cursorDelta() const {
+ // When the cursor speed is slow, it can be moving less than one pixel per
+ // update, so we need to calculate movement in "subpixels" and store the
+ // remaining subpixels to add to the next update's movement.
+ static uint8_t subpixel_remainder{0};
+
+ // First, we calculate where we are on the "time" axis of the acceleration
+ // curve, based on the time passed since the first cursor movement key was
+ // pressed.
+ uint8_t accel_step = accelStep();
+
+ // Next, we translate that into a speed scaling factor (from 1-255). If we
+ // had an FPU, we would do this in floating point, with a scale between 0 and
+ // 1, so this is how we emulate that using only integer (i.e. fixed-point)
+ // arithmetic.
+ uint8_t accel_factor = accelFactor(accel_step);
+
+ // We want the cursor to start out with some minimum speed, otherwise the user
+ // presses a movement key and then waits for a while before the cursor moves
+ // even one pixel. We need to multiply our speed-scaling factor by the
+ // difference between the starting speed and the full speed, then add the
+ // starting speed (multiplied by the full value of the scaling factor) to get
+ // the current speed.
+ uint8_t max_speed = settings_.cursor_base_speed;
+ uint8_t min_speed = settings_.cursor_init_speed;
+ uint8_t speed_range = max_speed - min_speed;
+ uint16_t subpixel_speed = (speed_range * accel_factor);
+ subpixel_speed += (min_speed * 256);
+
+ // We want to end up with small numbers of pixels, otherwise the speed will be
+ // too fast to be useful. But we also want to be able to make fine
+ // adjustments to the speed, so `settings_.cursor_base_speed` should be
+ // allowed to have a reasonbly high value, using all eight bits. This means
+ // that "decimal point" needs to be somewhere in the high byte of this 16-bit
+ // value. In order to store only eight bits of subpixel remainder, we need to
+ // do a shift first. This amount is arbitrary, but seems like a reasonable
+ // compromise.
+ subpixel_speed >>= 4;
+
+ // `max_speed` and `accel_factor` can both be up to 255. So we can't
+ // just multiply by `cursor_update_interval_ without risk of overflow. The
+ // update interval should be some low number, anyway (8 or less, I think), and
+ // should probably be fixed as a constexpr, so we could just leave it out.
+ subpixel_speed *= cursor_update_interval_;
+
+ // There's no risk of overflow here: (255 * 255) + 255 = 65535
+ subpixel_speed += subpixel_remainder;
+
+ // Set minimum speed
+ subpixel_speed += 64;
+
+ // This shift should be more than eight pixels; a single update of 100 pixels
+ // is a huge jump. See above.
+ uint8_t pixel_speed = subpixel_speed >> 8;
+ // Truncate to get only lower 8 bits.
+ subpixel_remainder = subpixel_speed;
+ //subpixel_remainder = subpixel_speed - (uint16_t(pixel_speed) << 8);
+ return pixel_speed;
+}
+
+// -----------------------------------------------------------------------------
+// Wheel speed should be controlled by changing the update interval, not by
+// setting `wheel_speed_`.
+void MouseKeys::sendMouseWheelReport() const {
+ int8_t dh = 0;
+ int8_t dv = 0;
- int8_t vx = 0;
- int8_t vy = 0;
uint8_t direction = directions_ >> wheel_offset_;
if (direction != 0) {
// Horizontal scroll wheel:
if (direction & KEY_MOUSE_LEFT)
- vx -= wheelSpeed;
+ dh -= 1;
if (direction & KEY_MOUSE_RIGHT)
- vx += wheelSpeed;
+ dh += 1;
// Vertical scroll wheel (note coordinates are opposite movement):
if (direction & KEY_MOUSE_UP)
- vy += wheelSpeed;
+ dv += 1;
if (direction & KEY_MOUSE_DOWN)
- vy -= wheelSpeed;
+ dv -= 1;
- // Add scroll wheel changes to HID report.
- Runtime.hid().mouse().move(0, 0, vy, vx);
// Send the report.
+ Runtime.hid().mouse().move(0, 0, dv, dh);
Runtime.hid().mouse().sendReport();
}
}
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
index 6483c578..465fbd48 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
@@ -1,5 +1,5 @@
/* Kaleidoscope-MouseKeys - Mouse keys for Kaleidoscope.
- * Copyright (C) 2017-2021 Keyboard.io, Inc.
+ * Copyright (C) 2017-2022 Keyboard.io, Inc.
*
* 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
@@ -22,20 +22,113 @@
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/key_defs.h" // for Key
#include "kaleidoscope/plugin.h" // for Plugin
+// =============================================================================
+// Deprecated MousKeys code
+#include "kaleidoscope_internal/deprecations.h" // for DEPRECATED
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_SET_SPEED_LIMIT \
+ "The `MouseKeys.setSpeedLimit()` function is deprecated. It no longer has\n" \
+ "any function, and can be safely removed."
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_SPEED \
+ "Direct access to the `MouseKeys.speed` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_SPEED_DELAY \
+ "Direct access to the `MouseKeys.speedDelay` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_ACCEL_SPEED \
+ "Direct access to the `MouseKeys.accelSpeed` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_ACCEL_DELAY \
+ "Direct access to the `MouseKeys.accelDelay` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_WHEEL_SPEED \
+ "Direct access to the `MouseKeys.wheelSpeed` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
+#define _DEPRECATED_MESSAGE_MOUSEKEYS_WHEEL_DELAY \
+ "Direct access to the `MouseKeys.wheelDelay` variable has been deprecated.\n" \
+ "Please refer to the MouseKeys documentation for instructions on how to\n" \
+ "configure the plugin.\n"
+
namespace kaleidoscope {
namespace plugin {
class MouseKeys : public kaleidoscope::Plugin {
public:
+#ifndef NDEPRECATED
+ DEPRECATED(MOUSEKEYS_SPEED)
static uint8_t speed;
+ DEPRECATED(MOUSEKEYS_SPEED_DELAY)
static uint16_t speedDelay;
+ DEPRECATED(MOUSEKEYS_ACCEL_SPEED)
static uint8_t accelSpeed;
+ DEPRECATED(MOUSEKEYS_ACCEL_DELAY)
static uint16_t accelDelay;
+ DEPRECATED(MOUSEKEYS_WHEEL_SPEED)
static uint8_t wheelSpeed;
+ DEPRECATED(MOUSEKEYS_WHEEL_DELAY)
static uint16_t wheelDelay;
- static void setWarpGridSize(uint8_t grid_size);
- static void setSpeedLimit(uint8_t speed_limit);
+ DEPRECATED(MOUSEKEYS_SET_SPEED_LIMIT)
+ static void setSpeedLimit(uint8_t speed_limit) {}
+#endif
+
+ void setWarpGridSize(uint8_t grid_size);
+
+ /// Get the current mouse (full) speed setting
+ ///
+ /// This returns the value for full-speed mouse movement (after the initial
+ /// acceleration period), not the current speed of the mouse cursor on screen.
+ /// The value does not have straightforward units, but it is linear.
+ uint8_t getCursorBaseSpeed() const {
+ return settings_.cursor_base_speed;
+ }
+ /// Set the full-speed mouse movement value
+ void setCursorBaseSpeed(uint8_t speed) {
+ settings_.cursor_base_speed = speed;
+ }
+
+ /// Get the initial mouse cursor movement speed setting
+ uint8_t getCursorInitSpeed() const {
+ return settings_.cursor_init_speed;
+ }
+ /// Set the initial mouse cursor movement speed
+ void setCursorInitSpeed(uint8_t speed) {
+ settings_.cursor_init_speed = speed;
+ }
+
+ /// Get the current acceleration window duration
+ uint16_t getCursorAccelDuration() const {
+ return settings_.cursor_accel_duration;
+ }
+ /// Set the acceleration window duration
+ void setCursorAccelDuration(uint16_t duration) {
+ settings_.cursor_accel_duration = duration;
+ }
+
+ /// Get the current mouse wheel update interval
+ ///
+ /// Returns the interval (in milliseconds) between mouse wheel updates while a
+ /// mouse wheel key is active (held).
+ uint8_t getScrollInterval() const {
+ return settings_.wheel_update_interval;
+ }
+ /// Set the current mouse wheel update interval
+ ///
+ /// Sets the wheel update interval to the specified number of milliseconds.
+ void setScrollInterval(uint8_t interval) {
+ settings_.wheel_update_interval = interval;
+ }
EventHandlerResult onSetup();
EventHandlerResult onNameQuery();
@@ -44,20 +137,39 @@ class MouseKeys : public kaleidoscope::Plugin {
EventHandlerResult onAddToReport(Key key);
EventHandlerResult afterReportingState(const KeyEvent &event);
+ // ---------------------------------------------------------------------------
+ // Structure for storing all user-configurable settings.
+ struct Settings {
+ uint8_t wheel_update_interval = 50;
+ uint8_t cursor_init_speed = 1;
+ uint8_t cursor_base_speed = 50;
+ uint16_t cursor_accel_duration = 1000;
+ };
+
+ // ---------------------------------------------------------------------------
+ // This lets the MouseKeysConfig plugin access the internal config variables
+ // directly. Mainly useful for calls to `Runtime.storage.get()`/`.put()`.
+ friend class MouseKeysConfig;
+
private:
- uint16_t move_start_time_ = 0;
- uint16_t accel_start_time_ = 0;
- uint16_t wheel_start_time_ = 0;
+ static constexpr uint8_t cursor_update_interval_ = 4;
+
+ Settings settings_;
+
+ uint16_t cursor_start_time_ = 0;
+ uint8_t last_cursor_update_time_ = 0;
+ uint8_t last_wheel_update_time_ = 0;
// Mouse cursor and wheel movement directions are stored in a single bitfield
// to save space. The low four bits are for cursor movement, and the high
// four are for wheel movement.
static constexpr uint8_t wheel_offset_ = 4;
static constexpr uint8_t wheel_mask_ = 0b11110000;
- static constexpr uint8_t move_mask_ = 0b00001111;
- uint8_t directions_ = 0;
- uint8_t pending_directions_ = 0;
- uint8_t buttons_ = 0;
+ static constexpr uint8_t cursor_mask_ = 0b00001111;
+
+ uint8_t directions_ = 0;
+ uint8_t pending_directions_ = 0;
+ uint8_t buttons_ = 0;
bool isMouseKey(const Key &key) const;
bool isMouseButtonKey(const Key &key) const;
@@ -67,11 +179,28 @@ class MouseKeys : public kaleidoscope::Plugin {
void sendMouseButtonReport() const;
void sendMouseWarpReport(const KeyEvent &event) const;
- void sendMouseMoveReport();
- void sendMouseWheelReport();
+ void sendMouseMoveReport() const;
+ void sendMouseWheelReport() const;
+
+ uint8_t accelStep() const;
+ uint8_t cursorDelta() const;
+};
+
+// =============================================================================
+// Plugin for configuration of MouseKeys via Focus and persistent storage of
+// settins in EEPROM (i.e. Chrysalis).
+class MouseKeysConfig : public Plugin {
+ public:
+ EventHandlerResult onSetup();
+ EventHandlerResult onFocusEvent(const char *command);
+
+ private:
+ // The base address in persistent storage for configuration data:
+ uint16_t settings_addr_;
};
} // namespace plugin
} // namespace kaleidoscope
extern kaleidoscope::plugin::MouseKeys MouseKeys;
+extern kaleidoscope::plugin::MouseKeysConfig MouseKeysConfig;
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeysConfig.cpp b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeysConfig.cpp
new file mode 100644
index 00000000..eb2caa3b
--- /dev/null
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeysConfig.cpp
@@ -0,0 +1,144 @@
+/* -*- mode: c++ -*-
+ * Kaleidoscope-MouseKeys -- Mouse keys for Kaleidoscope.
+ * Copyright (C) 2022 Keyboard.io, Inc
+ *
+ * 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, version 3.
+ *
+ * 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 .
+ */
+
+#include "kaleidoscope/plugin/MouseKeys.h" // IWYU pragma: associated
+
+#include // for PSTR, strcmp_P, strncmp_P
+#include // for EEPROMSettings
+#include // for Focus, FocusSerial
+#include // for uint16_t, uint32_t, uint8_t
+
+#include "kaleidoscope/Runtime.h" // for Runtime, Runtime_
+#include "kaleidoscope/device/device.h" // for VirtualProps::Storage, Base<>::Storage
+#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult, EventHandlerResult::OK
+
+namespace kaleidoscope {
+namespace plugin {
+
+// =============================================================================
+// MouseKeys configurator
+
+EventHandlerResult MouseKeysConfig::onSetup() {
+ settings_addr_ = ::EEPROMSettings.requestSlice(sizeof(MouseKeys::Settings));
+ uint32_t checker;
+
+ Runtime.storage().get(settings_addr_, checker);
+
+ // If the EEPROM is empty, storre the default settings.
+ if (checker == 0xffffffff) {
+ Runtime.storage().put(settings_addr_, ::MouseKeys.settings_);
+ Runtime.storage().commit();
+ }
+
+ Runtime.storage().get(settings_addr_, ::MouseKeys.settings_);
+ return EventHandlerResult::OK;
+}
+
+// -----------------------------------------------------------------------------
+EventHandlerResult MouseKeysConfig::onFocusEvent(const char *command) {
+ // If the focus command is a request for help, provide the list of valid
+ // commands.
+ if (::Focus.handleHelp(command, PSTR("mousekeys.scroll_interval\n"
+ "mousekeys.init_speed\n"
+ "mousekeys.base_speed\n"
+ "mousekeys.accel_duration")))
+ return EventHandlerResult::OK;
+
+ // The length of the string `mousekeys.`:
+ constexpr uint8_t base_cmd_len = 10;
+
+ // If this is not a MouseKeys command, do nothing.
+ if (strncmp_P(command, PSTR("mousekeys."), base_cmd_len) != 0)
+ return EventHandlerResult::OK;
+ // Otherwise, advance the pointer to the subcommand.
+ command += base_cmd_len;
+
+ enum Command : uint8_t {
+ SCROLL_INTERVAL,
+ INIT_SPEED,
+ BASE_SPEED,
+ ACCEL_DURATION,
+ };
+ Command cmd;
+
+ // Parse the (sub)command. If it's not a valid command, abort.
+ if (strcmp_P(command, PSTR("scroll_interval")) == 0)
+ cmd = Command::SCROLL_INTERVAL;
+ else if (strcmp_P(command, PSTR("init_speed")) == 0)
+ cmd = Command::INIT_SPEED;
+ else if (strcmp_P(command, PSTR("base_speed")) == 0)
+ cmd = Command::BASE_SPEED;
+ else if (strcmp_P(command, PSTR("accel_duration")) == 0)
+ cmd = Command::ACCEL_DURATION;
+ else
+ return EventHandlerResult::ABORT;
+
+ if (::Focus.isEOL()) {
+ // If there is no argument given, we send back the current value of the
+ // setting that was requested.
+ uint16_t val;
+ switch (cmd) {
+ case Command::SCROLL_INTERVAL:
+ val = ::MouseKeys.getScrollInterval();
+ break;
+ case Command::INIT_SPEED:
+ val = ::MouseKeys.getCursorInitSpeed();
+ break;
+ case Command::BASE_SPEED:
+ val = ::MouseKeys.getCursorBaseSpeed();
+ break;
+ case Command::ACCEL_DURATION:
+ val = ::MouseKeys.getCursorAccelDuration();
+ break;
+ default:
+ return EventHandlerResult::ABORT;
+ }
+ ::Focus.send(val);
+ return EventHandlerResult::EVENT_CONSUMED;
+ } else {
+ // If there is an argument, we read it, then pass it to the corresponding
+ // setter method of MouseKeys.
+ uint16_t arg;
+ ::Focus.read(arg);
+
+ switch (cmd) {
+ case Command::SCROLL_INTERVAL:
+ ::MouseKeys.setScrollInterval(arg);
+ break;
+ case Command::INIT_SPEED:
+ ::MouseKeys.setCursorInitSpeed(arg);
+ break;
+ case Command::BASE_SPEED:
+ ::MouseKeys.setCursorBaseSpeed(arg);
+ break;
+ case Command::ACCEL_DURATION:
+ ::MouseKeys.setCursorAccelDuration(arg);
+ break;
+ }
+ }
+
+ // Update settings stored in EEPROM, and indicate that this Focus event has
+ // been handled successfully.
+ Runtime.storage().put(settings_addr_, ::MouseKeys.settings_);
+ Runtime.storage().commit();
+ return EventHandlerResult::EVENT_CONSUMED;
+}
+
+} // namespace plugin
+} // namespace kaleidoscope
+
+kaleidoscope::plugin::MouseKeysConfig MouseKeysConfig;
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.cpp b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.cpp
index 768a11d9..b3944b38 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.cpp
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.cpp
@@ -1,5 +1,5 @@
/* Kaleidoscope-MouseKeys - Mouse keys for Kaleidoscope.
- * Copyright (C) 2017-2018 Keyboard.io, Inc.
+ * Copyright (C) 2017-2022 Keyboard.io, Inc.
*
* 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
@@ -16,27 +16,22 @@
#include "kaleidoscope/plugin/mousekeys/MouseWrapper.h"
-#include // for uint16_t, uint8_t, int8_t
+#include // for uint16_t, uint8_t
#include "kaleidoscope/Runtime.h" // for Runtime, Runtime_
#include "kaleidoscope/device/device.h" // for Base<>::HID, VirtualProps:...
#include "kaleidoscope/driver/hid/keyboardio/AbsoluteMouse.h" // for AbsoluteMouse
-#include "kaleidoscope/driver/hid/keyboardio/Mouse.h" // for Mouse
-#include "kaleidoscope/plugin/mousekeys/MouseWarpModes.h" // for MOUSE_WARP_GRID_2X2
namespace kaleidoscope {
namespace plugin {
namespace mousekeys {
-uint8_t MouseWrapper::warp_grid_size = MOUSE_WARP_GRID_2X2;
-uint16_t MouseWrapper::next_width;
-uint16_t MouseWrapper::next_height;
-uint16_t MouseWrapper::section_top;
-uint16_t MouseWrapper::section_left;
-bool MouseWrapper::is_warping;
-
-uint8_t MouseWrapper::accel_step;
-uint8_t MouseWrapper::speed_limit = 127;
+// uint8_t MouseWrapper::warp_grid_size = MOUSE_WARP_GRID_2X2;
+// uint16_t MouseWrapper::next_width;
+// uint16_t MouseWrapper::next_height;
+// uint16_t MouseWrapper::section_top;
+// uint16_t MouseWrapper::section_left;
+// bool MouseWrapper::is_warping;
void MouseWrapper::warpJump(uint16_t left, uint16_t top, uint16_t height, uint16_t width) {
uint16_t x_center = left + width / 2;
@@ -100,52 +95,9 @@ void MouseWrapper::warp(uint8_t warp_cmd) {
warpJump(section_left, section_top, next_height, next_width);
}
-// To approximate a sine wave, this uses two parabolas. Acceleration begins
-// slowly, grows rapidly in the middle, and slows again near the top.
-uint8_t MouseWrapper::acceleration(uint8_t cycles) {
- if (cycles < 128) {
- uint16_t c2 = cycles * cycles;
- return 1 + (c2 >> 7);
- } else {
- uint16_t remaining_cycles = 256 - cycles;
- uint16_t c2 = remaining_cycles * remaining_cycles;
- return 255 - (c2 >> 7);
- }
-}
-
-void MouseWrapper::move(int8_t x, int8_t y) {
- int16_t moveX = 0;
- int16_t moveY = 0;
- static int8_t remainderX = 0;
- static int8_t remainderY = 0;
- int16_t effectiveSpeedLimit = speed_limit;
-
- if (x != 0) {
- moveX = remainderX + (x * acceleration(accel_step));
- if (moveX > effectiveSpeedLimit)
- moveX = effectiveSpeedLimit;
- else if (moveX < -effectiveSpeedLimit)
- moveX = -effectiveSpeedLimit;
- }
-
- if (y != 0) {
- moveY = remainderY + (y * acceleration(accel_step));
- if (moveY > effectiveSpeedLimit)
- moveY = effectiveSpeedLimit;
- else if (moveY < -effectiveSpeedLimit)
- moveY = -effectiveSpeedLimit;
- }
-
- endWarping();
- // move by whole pixels, not subpixels
- Runtime.hid().mouse().move(moveX / subpixels_per_pixel, moveY / subpixels_per_pixel);
- // save leftover subpixel movements for later
- remainderX = moveX - moveX / subpixels_per_pixel * subpixels_per_pixel;
- remainderY = moveY - moveY / subpixels_per_pixel * subpixels_per_pixel;
-}
+} // namespace mousekeys
-MouseWrapper wrapper;
+mousekeys::MouseWrapper MouseWrapper;
-} // namespace mousekeys
} // namespace plugin
} // namespace kaleidoscope
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.h b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.h
index e8807c2e..6ff18e25 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.h
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/mousekeys/MouseWrapper.h
@@ -1,5 +1,5 @@
/* Kaleidoscope-MouseKeys - Mouse keys for Kaleidoscope.
- * Copyright (C) 2017-2018 Keyboard.io, Inc.
+ * Copyright (C) 2017-2022 Keyboard.io, Inc.
*
* 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
@@ -16,58 +16,56 @@
#pragma once
-#include // for uint16_t, uint8_t, int8_t
+#include // for uint16_t, uint8_t
+
+#include "kaleidoscope/plugin/mousekeys/MouseWarpModes.h" // for MOUSE_WARP_GRID_2X2
+
+// Mouse acceleration
+
+namespace kaleidoscope {
+namespace plugin {
// Warping commands
-#define WARP_END 1
-#define WARP_UP 2
-#define WARP_DOWN 4
-#define WARP_LEFT 8
-#define WARP_RIGHT 16
+constexpr uint8_t WARP_END = 1 << 0;
+constexpr uint8_t WARP_UP = 1 << 1;
+constexpr uint8_t WARP_DOWN = 1 << 2;
+constexpr uint8_t WARP_LEFT = 1 << 3;
+constexpr uint8_t WARP_RIGHT = 1 << 4;
// apparently, the mac discards 15% of the value space for mouse movement.
// need to test this on other platforms
-#define MAX_WARP_WIDTH 32767
-#define MAX_WARP_HEIGHT 32767
+constexpr uint16_t MAX_WARP_WIDTH = 32767;
+constexpr uint16_t MAX_WARP_HEIGHT = 32767;
-#define WARP_ABS_TOP 0
-#define WARP_ABS_LEFT 0
+constexpr uint8_t WARP_ABS_TOP = 0;
+constexpr uint8_t WARP_ABS_LEFT = 0;
-// Mouse acceleration
-
-namespace kaleidoscope {
-namespace plugin {
namespace mousekeys {
class MouseWrapper {
public:
- static void move(int8_t x, int8_t y);
- static void warp(uint8_t warp_cmd);
+ void warp(uint8_t warp_cmd);
+ void endWarping();
- static uint8_t accel_step;
- static uint8_t speed_limit;
- static constexpr uint8_t subpixels_per_pixel = 16;
- static uint8_t warp_grid_size;
+ uint8_t warp_grid_size = MOUSE_WARP_GRID_2X2;
private:
- static uint16_t next_width;
- static uint16_t next_height;
- static uint16_t section_top;
- static uint16_t section_left;
- static bool is_warping;
-
- static uint8_t acceleration(uint8_t cycles);
-
- static void beginWarping();
- static void endWarping();
- static void resetWarping();
- static void warpJump(uint16_t left, uint16_t top, uint16_t height, uint16_t width);
+ uint16_t next_width;
+ uint16_t next_height;
+ uint16_t section_top;
+ uint16_t section_left;
+ bool is_warping = false;
+
+ void beginWarping();
+ void resetWarping();
+ void warpJump(uint16_t left, uint16_t top, uint16_t height, uint16_t width);
};
-extern MouseWrapper wrapper;
-
} // namespace mousekeys
+
+extern mousekeys::MouseWrapper MouseWrapper;
+
} // namespace plugin
} // namespace kaleidoscope
diff --git a/tests/plugins/MouseKeys/basic/basic.ino b/tests/plugins/MouseKeys/basic/basic.ino
index 09df9b29..ab50c9c8 100644
--- a/tests/plugins/MouseKeys/basic/basic.ino
+++ b/tests/plugins/MouseKeys/basic/basic.ino
@@ -42,6 +42,8 @@ KALEIDOSCOPE_INIT_PLUGINS(MouseKeys);
void setup() {
Kaleidoscope.setup();
+
+ MouseKeys.setCursorAccelDuration(200);
}
void loop() {
diff --git a/tests/plugins/MouseKeys/basic/test.ktest b/tests/plugins/MouseKeys/basic/test.ktest
index aaf85b2f..ad882b96 100644
--- a/tests/plugins/MouseKeys/basic/test.ktest
+++ b/tests/plugins/MouseKeys/basic/test.ktest
@@ -21,15 +21,33 @@ RUN 3 ms
PRESS MOVE_UP
RUN 1 cycle
-RUN 15 ms
+RUN 4 ms
EXPECT mouse-report y=-1
-RUN 1 cycle
-
-RUN 15 ms
+RUN 8 ms
EXPECT mouse-report y=-1
-RUN 1 cycle
+RUN 8 ms
+EXPECT mouse-report y=-1
+RUN 4 ms
+EXPECT mouse-report y=-1
+RUN 4 ms
+EXPECT mouse-report y=-1
+RUN 4 ms
+EXPECT mouse-report y=-1
+RUN 4 ms
+EXPECT mouse-report y=-1
+RUN 4 ms
+EXPECT mouse-report y=-2
+RUN 4 ms
+EXPECT mouse-report y=-2
+RUN 4 ms
+EXPECT mouse-report y=-2
+RUN 4 ms
+EXPECT mouse-report y=-2
+RUN 4 ms
+EXPECT mouse-report y=-2
+RUN 4 ms
+EXPECT mouse-report y=-3
-RUN 5 ms
RELEASE MOVE_UP
RUN 1 cycle
EXPECT no mouse-report