diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
index 508cb56f..0a05bdb3 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp
@@ -37,6 +37,10 @@ uint16_t MouseKeys_::move_start_time_;
uint16_t MouseKeys_::accel_start_time_;
uint16_t MouseKeys_::wheel_start_time_;
+uint8_t MouseKeys_::directions_ = 0;
+uint8_t MouseKeys_::pending_directions_ = 0;
+uint8_t MouseKeys_::buttons_ = 0;
+
// =============================================================================
// Configuration functions
@@ -105,75 +109,12 @@ EventHandlerResult MouseKeys_::afterEachCycle() {
}
// Check timeout for position update interval.
- bool update_position = Runtime.hasTimeExpired(move_start_time_, speedDelay);
- if (update_position) {
- move_start_time_ = Runtime.millisAtCycleStart();
- // Determine which mouse movement directions are active by searching through
- // all the currently active keys for mouse movement keys, and adding them to
- // a bitfield (`directions`).
- uint8_t directions = 0;
- int8_t vx = 0;
- int8_t vy = 0;
- for (Key key : live_keys.all()) {
- if (isMouseKey(key) && isMouseMoveKey(key)) {
- directions |= key.getKeyCode();
- }
- }
-
- if (directions == 0) {
- // If there are no mouse movement keys held, reset speed to zero.
- MouseWrapper.accelStep = 0;
- } else {
- // For each active direction, add the mouse movement speed.
- if (directions & KEY_MOUSE_LEFT)
- vx -= speed;
- if (directions & KEY_MOUSE_RIGHT)
- vx += speed;
- if (directions & KEY_MOUSE_UP)
- vy -= speed;
- if (directions & KEY_MOUSE_DOWN)
- vy += speed;
-
- // Prepare the mouse report.
- MouseWrapper.move(vx, vy);
- // Send the report.
- Runtime.hid().mouse().sendReport();
- }
- }
+ if (Runtime.hasTimeExpired(move_start_time_, speedDelay))
+ sendMouseMoveReport();
// Check timeout for scroll report interval.
- bool update_wheel = Runtime.hasTimeExpired(wheel_start_time_, wheelDelay);
- if (update_wheel) {
- wheel_start_time_ = Runtime.millisAtCycleStart();
- // Determine which scroll wheel keys are active, and add their directions to
- // a bitfield (`directions`).
- uint8_t directions = 0;
- int8_t vx = 0;
- int8_t vy = 0;
- for (Key key : live_keys.all()) {
- if (isMouseKey(key) && isMouseWheelKey(key)) {
- directions |= key.getKeyCode();
- }
- }
-
- if (directions != 0) {
- // Horizontal scroll wheel:
- if (directions & KEY_MOUSE_LEFT)
- vx -= wheelSpeed;
- if (directions & KEY_MOUSE_RIGHT)
- vx += wheelSpeed;
- // Vertical scroll wheel (note coordinates are opposite movement):
- if (directions & KEY_MOUSE_UP)
- vy += wheelSpeed;
- if (directions & KEY_MOUSE_DOWN)
- vy -= wheelSpeed;
-
- // Add scroll wheel changes to HID report.
- Runtime.hid().mouse().move(0, 0, vy, vx);
- // Send the report.
- Runtime.hid().mouse().sendReport();
- }
- }
+ if (Runtime.hasTimeExpired(wheel_start_time_, wheelDelay))
+ sendMouseWheelReport();
return EventHandlerResult::OK;
}
@@ -183,50 +124,85 @@ EventHandlerResult MouseKeys_::onKeyEvent(KeyEvent &event) {
if (!isMouseKey(event.key))
return EventHandlerResult::OK;
+ pending_directions_ = 0;
+
if (isMouseButtonKey(event.key)) {
- sendMouseButtonReport(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)) {
sendMouseWarpReport(event);
}
+ }
+
+ // Reports for mouse cursor and wheel movement keys are sent from the
+ // `afterReportingState()` handler (when first toggled on) and
+ // `afterEachCycle()` handler (when held). We need to return `OK` here so
+ // that processing of events for these keys will complete.
+ return EventHandlerResult::OK;
+}
+
+// -----------------------------------------------------------------------------
+EventHandlerResult MouseKeys_::afterReportingState(const KeyEvent &event) {
+ if (!isMouseKey(event.key))
+ return EventHandlerResult::OK;
+
+ // If a mouse button key has toggled on or off, we send a mouse report with
+ // the updated information.
+ if (isMouseButtonKey(event.key)) {
+ sendMouseButtonReport();
+ }
+
+ // 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;
- } else if (isMouseMoveKey(event.key)) {
- // No report is sent here; that's handled in `afterEachCycle()`.
- move_start_time_ = Runtime.millisAtCycleStart() - speedDelay;
+ 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)) {
- // No report is sent here; that's handled in `afterEachCycle()`.
- wheel_start_time_ = Runtime.millisAtCycleStart() - wheelDelay;
+ sendMouseWheelReport();
}
- return EventHandlerResult::EVENT_CONSUMED;
+ return EventHandlerResult::OK;
+}
+
+// -----------------------------------------------------------------------------
+// This handler is responsible for gathering information on which mouse cursor
+// and wheel movement keys are currently pressed, so the direction(s) of motion
+// will be up to date at the end of processing a mouse key event. We add bits
+// to the `pending` directions only; these get copied later if the event isn't
+// aborted.
+EventHandlerResult MouseKeys_::onAddToReport(Key key) {
+ if (!isMouseKey(key))
+ return EventHandlerResult::OK;
+
+ if (isMouseButtonKey(key))
+ buttons_ |= (key.getKeyCode() & ~KEY_MOUSE_BUTTON);
+
+ if (isMouseMoveKey(key))
+ pending_directions_ |= key.getKeyCode();
+
+ if (isMouseWheelKey(key))
+ pending_directions_ |= (key.getKeyCode() << wheel_offset_);
+
+ return EventHandlerResult::OK;
}
// =============================================================================
// HID report helper functions
// -----------------------------------------------------------------------------
-void MouseKeys_::sendMouseButtonReport(const KeyEvent &event) const {
- // Get ready to send a new mouse report by building it from live_keys. Note
- // that this also clears the movement and scroll values, but since those are
- // relative, that's what we want.
+void MouseKeys_::sendMouseButtonReport() const {
Runtime.hid().mouse().releaseAllButtons();
-
- uint8_t buttons = 0;
- for (KeyAddr key_addr : KeyAddr::all()) {
- if (key_addr == event.addr)
- continue;
- Key key = live_keys[key_addr];
- if (isMouseKey(key) && isMouseButtonKey(key)) {
- buttons |= key.getKeyCode();
- }
- }
- if (keyToggledOn(event.state))
- buttons |= event.key.getKeyCode();
- buttons &= ~KEY_MOUSE_BUTTON;
- Runtime.hid().mouse().pressButtons(buttons);
+ Runtime.hid().mouse().pressButtons(buttons_);
Runtime.hid().mouse().sendReport();
}
@@ -240,6 +216,62 @@ void MouseKeys_::sendMouseWarpReport(const KeyEvent &event) const {
((event.key.getKeyCode() & KEY_MOUSE_RIGHT) ? WARP_RIGHT : 0x00));
}
+// -----------------------------------------------------------------------------
+void MouseKeys_::sendMouseMoveReport() {
+ move_start_time_ = Runtime.millisAtCycleStart();
+
+ int8_t vx = 0;
+ int8_t vy = 0;
+ uint8_t direction = directions_ & move_mask_;
+
+ if (direction == 0) {
+ // If there are no mouse movement keys held, reset speed to zero.
+ MouseWrapper.accelStep = 0;
+ } else {
+ // For each active direction, add the mouse movement speed.
+ if (direction & KEY_MOUSE_LEFT)
+ vx -= speed;
+ if (direction & KEY_MOUSE_RIGHT)
+ vx += speed;
+ if (direction & KEY_MOUSE_UP)
+ vy -= speed;
+ if (direction & KEY_MOUSE_DOWN)
+ vy += speed;
+
+ // Prepare the mouse report.
+ MouseWrapper.move(vx, vy);
+ // Send the report.
+ Runtime.hid().mouse().sendReport();
+ }
+}
+
+// -----------------------------------------------------------------------------
+void MouseKeys_::sendMouseWheelReport() {
+ wheel_start_time_ = Runtime.millisAtCycleStart();
+
+ 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;
+ if (direction & KEY_MOUSE_RIGHT)
+ vx += wheelSpeed;
+ // Vertical scroll wheel (note coordinates are opposite movement):
+ if (direction & KEY_MOUSE_UP)
+ vy += wheelSpeed;
+ if (direction & KEY_MOUSE_DOWN)
+ vy -= wheelSpeed;
+
+ // Add scroll wheel changes to HID report.
+ Runtime.hid().mouse().move(0, 0, vy, vx);
+ // Send the report.
+ Runtime.hid().mouse().sendReport();
+ }
+}
+
} // namespace plugin
} // namespace kaleidoscope
diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
index 9f547998..c5ac49f0 100644
--- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
+++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h
@@ -41,20 +41,34 @@ class MouseKeys_ : public kaleidoscope::Plugin {
EventHandlerResult onNameQuery();
EventHandlerResult afterEachCycle();
EventHandlerResult onKeyEvent(KeyEvent &event);
+ EventHandlerResult onAddToReport(Key key);
+ EventHandlerResult afterReportingState(const KeyEvent &event);
private:
static uint16_t move_start_time_;
static uint16_t accel_start_time_;
static uint16_t wheel_start_time_;
+ // 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;
+ static uint8_t directions_;
+ static uint8_t pending_directions_;
+ static uint8_t buttons_;
+
bool isMouseKey(const Key &key) const;
bool isMouseButtonKey(const Key &key) const;
bool isMouseMoveKey(const Key &key) const;
bool isMouseWarpKey(const Key &key) const;
bool isMouseWheelKey(const Key &key) const;
- void sendMouseButtonReport(const KeyEvent &event) const;
+ void sendMouseButtonReport() const;
void sendMouseWarpReport(const KeyEvent &event) const;
+ void sendMouseMoveReport();
+ void sendMouseWheelReport();
};
}
diff --git a/tests/issues/1113/1113.ino b/tests/issues/1113/1113.ino
new file mode 100644
index 00000000..5e96bfe5
--- /dev/null
+++ b/tests/issues/1113/1113.ino
@@ -0,0 +1,67 @@
+/* -*- mode: c++ -*-
+ * 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
+#include
+#include
+
+// *INDENT-OFF*
+KEYMAPS(
+ [0] = KEYMAP_STACKED
+ (
+ Key_mouseUp, Key_mouseDn, Key_mouseL, Key_mouseR, ___, ___, ___,
+ Key_mouseScrollUp, Key_mouseScrollDn, Key_mouseScrollL, Key_mouseScrollR, ___, ___, ___,
+ Key_mouseBtnL, Key_mouseBtnM, Key_mouseBtnR, ___, ___, ___,
+ M(0), M(1), ___, ___, ___, ___, ___,
+ ___, ___, ___, ___,
+ ___,
+
+ ___, ___, ___, ___, ___, ___, ___,
+ ___, ___, ___, ___, ___, ___, ___,
+ ___, ___, ___, ___, ___, ___,
+ ___, ___, ___, ___, ___, ___, ___,
+ ___, ___, ___, ___,
+ ___
+ ),
+)
+// *INDENT-ON*
+
+const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
+ if (keyToggledOn(event.state)) {
+ switch (macro_id) {
+ case 0:
+ Macros.tap(Key_mouseBtnR);
+ break;
+ case 1:
+ Macros.press(Key_mouseScrollUp);
+ Macros.release(Key_mouseScrollUp);
+ break;
+ default:
+ break;
+ }
+ }
+ return MACRO_NONE;
+}
+
+KALEIDOSCOPE_INIT_PLUGINS(Macros, MouseKeys);
+
+void setup() {
+ Kaleidoscope.setup();
+}
+
+void loop() {
+ Kaleidoscope.loop();
+}
diff --git a/tests/issues/1113/sketch.json b/tests/issues/1113/sketch.json
new file mode 100644
index 00000000..8cc86922
--- /dev/null
+++ b/tests/issues/1113/sketch.json
@@ -0,0 +1,6 @@
+{
+ "cpu": {
+ "fqbn": "keyboardio:virtual:model01",
+ "port": ""
+ }
+}
diff --git a/tests/issues/1113/test.ktest b/tests/issues/1113/test.ktest
new file mode 100644
index 00000000..be7e47b3
--- /dev/null
+++ b/tests/issues/1113/test.ktest
@@ -0,0 +1,49 @@
+VERSION 1
+
+KEYSWITCH MOVE_UP 0 0
+KEYSWITCH MOVE_DOWN 0 1
+KEYSWITCH MOVE_LEFT 0 2
+KEYSWITCH MOVE_RIGHT 0 3
+
+KEYSWITCH SCROLL_UP 1 0
+KEYSWITCH SCROLL_DOWN 1 1
+KEYSWITCH SCROLL_LEFT 1 2
+KEYSWITCH SCROLL_RIGHT 1 3
+
+KEYSWITCH BUTTON_L 2 0
+KEYSWITCH BUTTON_M 2 1
+KEYSWITCH BUTTON_R 2 2
+
+KEYSWITCH MACRO_0 3 0
+KEYSWITCH MACRO_1 3 1
+
+# ==============================================================================
+NAME Mouse button key tap in Macros
+
+RUN 3 ms
+PRESS MACRO_0
+RUN 1 cycle
+EXPECT mouse-report button=R
+EXPECT mouse-report empty
+
+RUN 5 ms
+RELEASE MACRO_0
+RUN 1 cycle
+EXPECT no mouse-report
+
+RUN 5 ms
+
+# ==============================================================================
+NAME Mouse scroll key tap in Macros
+
+RUN 4 ms
+PRESS MACRO_1
+RUN 1 cycle
+EXPECT mouse-report v=1
+
+RUN 5 ms
+RELEASE MACRO_1
+RUN 1 cycle
+EXPECT no mouse-report
+
+RUN 5 ms