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