/* Kaleidoscope-MouseKeys - Mouse keys for Kaleidoscope.
* Copyright (C) 2017-2021 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 "kaleidoscope/Runtime.h"
#include "Kaleidoscope-MouseKeys.h"
#include "Kaleidoscope-FocusSerial.h"
#include "kaleidoscope/keyswitch_state.h"
namespace kaleidoscope {
namespace plugin {
uint8_t MouseKeys_::speed = 1;
uint16_t MouseKeys_::speedDelay = 1;
uint8_t MouseKeys_::accelSpeed = 1;
uint16_t MouseKeys_::accelDelay = 64;
uint8_t MouseKeys_::wheelSpeed = 1;
uint16_t MouseKeys_::wheelDelay = 50;
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
void MouseKeys_::setWarpGridSize(uint8_t grid_size) {
MouseWrapper.warp_grid_size = grid_size;
}
void MouseKeys_::setSpeedLimit(uint8_t speed_limit) {
MouseWrapper.speedLimit = speed_limit;
}
// =============================================================================
// Key variant tests
bool MouseKeys_::isMouseKey(const Key& key) const {
return (key.getFlags() == (SYNTHETIC | IS_MOUSE_KEY));
}
bool MouseKeys_::isMouseButtonKey(const Key& key) const {
uint8_t variant = key.getKeyCode() & (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP);
return variant == KEY_MOUSE_BUTTON;
}
bool MouseKeys_::isMouseMoveKey(const Key& key) const {
uint8_t mask = (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP | KEY_MOUSE_WHEEL);
uint8_t variant = key.getKeyCode() & mask;
return variant == 0;
}
bool MouseKeys_::isMouseWarpKey(const Key& key) const {
return (key.getKeyCode() & KEY_MOUSE_WARP) != 0;
}
bool MouseKeys_::isMouseWheelKey(const Key& key) const {
uint8_t mask = (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP | KEY_MOUSE_WHEEL);
uint8_t variant = key.getKeyCode() & mask;
return variant == KEY_MOUSE_WHEEL;
}
// =============================================================================
// Event Handlers
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys_::onNameQuery() {
return ::Focus.sendName(F("MouseKeys"));
}
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys_::onSetup(void) {
kaleidoscope::Runtime.hid().mouse().setup();
kaleidoscope::Runtime.hid().absoluteMouse().setup();
return EventHandlerResult::OK;
}
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys_::afterEachCycle() {
// Check timeout for accel update interval.
if (Runtime.hasTimeExpired(accel_start_time_, accelDelay)) {
accel_start_time_ = Runtime.millisAtCycleStart();
// `accelStep` determines the movement speed of the mouse pointer, and gets
// reset to zero when no mouse movement keys is pressed (see below).
if (MouseWrapper.accelStep < 255 - accelSpeed) {
MouseWrapper.accelStep += accelSpeed;
}
}
// Check timeout for position update interval.
if (Runtime.hasTimeExpired(move_start_time_, speedDelay))
sendMouseMoveReport();
// Check timeout for scroll report interval.
if (Runtime.hasTimeExpired(wheel_start_time_, wheelDelay))
sendMouseWheelReport();
return EventHandlerResult::OK;
}
// -----------------------------------------------------------------------------
EventHandlerResult MouseKeys_::onKeyEvent(KeyEvent &event) {
if (!isMouseKey(event.key))
return EventHandlerResult::OK;
pending_directions_ = 0;
if (isMouseButtonKey(event.key)) {
// 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;
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();
}
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 {
Runtime.hid().mouse().releaseAllButtons();
Runtime.hid().mouse().pressButtons(buttons_);
Runtime.hid().mouse().sendReport();
}
// -----------------------------------------------------------------------------
void MouseKeys_::sendMouseWarpReport(const KeyEvent &event) const {
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) |
((event.key.getKeyCode() & KEY_MOUSE_LEFT) ? WARP_LEFT : 0x00) |
((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
kaleidoscope::plugin::MouseKeys_ MouseKeys;