You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Kaleidoscope/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp

220 lines
7.3 KiB

/* -*- mode: c++ -*-
* Kaleidoscope-AutoShift -- Automatic shift on long press
* Copyright (C) 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 <http://www.gnu.org/licenses/>.
*/
#include "kaleidoscope/plugin/AutoShift.h"
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/Runtime.h"
namespace kaleidoscope {
namespace plugin {
// =============================================================================
// AutoShift static class variables
// Configuration settings that can be saved to persistent storage.
AutoShift::Settings AutoShift::settings_ = {
.enabled = true,
.timeout = 175,
.enabled_categories = AutoShift::Categories::printableKeys(),
};
// The event tracker ensures that the `onKeyswitchEvent()` handler will follow
// the rules in order to avoid interference with other plugins and prevent
// processing the same event more than once.
KeyEventTracker AutoShift::event_tracker_;
// If there's a delayed keypress from AutoShift, this stored event will contain
// a valid `KeyAddr`. The default constructor produces an event addr of
// `KeyAddr::none()`, so the plugin will start in an inactive state.
KeyEvent pending_event_;
// =============================================================================
// AutoShift functions
void AutoShift::disable() {
settings_.enabled = false;
if (pending_event_.addr.isValid()) {
Runtime.handleKeyswitchEvent(pending_event_);
}
}
// -----------------------------------------------------------------------------
// Test for whether or not to apply AutoShift to a given `Key`. This function
// can be overridden from the user sketch.
__attribute__((weak))
bool AutoShift::isAutoShiftable(Key key) {
return enabledForKey(key);
}
// The default method that determines whether a particular key is an auto-shift
// candidate. Used by `isAutoShiftable()`, separate to allow re-use when the
// caller is overridden.
bool AutoShift::enabledForKey(Key key) {
// We only support auto-shifting keyboard keys. We could also explicitly
// ignore modifier keys, but there's no need to do so, as none of the code
// below matches modifiers.
if (!key.isKeyboardKey())
return false;
// We compare only the keycode, and disregard any modifier flags applied to
// the key. This simplifies the comparison, and also allows AutoShift to
// apply to keys like `RALT(Key_E)`.
uint8_t keycode = key.getKeyCode();
if (isEnabled(Categories::allKeys())) {
if (keycode < HID_KEYBOARD_FIRST_MODIFIER)
return true;
}
if (isEnabled(Categories::letterKeys())) {
if (keycode >= Key_A.getKeyCode() && keycode <= Key_Z.getKeyCode())
return true;
}
if (isEnabled(Categories::numberKeys())) {
if (keycode >= Key_1.getKeyCode() && keycode <= Key_0.getKeyCode())
return true;
}
if (isEnabled(Categories::symbolKeys())) {
if ((keycode >= Key_Minus.getKeyCode() && keycode <= Key_Slash.getKeyCode()) ||
(keycode == Key_NonUsBackslashAndPipe.getKeyCode()))
return true;
}
if (isEnabled(Categories::arrowKeys())) {
if (keycode >= Key_RightArrow.getKeyCode() &&
keycode <= Key_LeftArrow.getKeyCode())
return true;
}
if (isEnabled(Categories::functionKeys())) {
if ((keycode >= Key_F1.getKeyCode() && keycode <= Key_F12.getKeyCode()) ||
(keycode >= Key_F13.getKeyCode() && keycode <= Key_F24.getKeyCode()))
return true;
}
return false;
}
// =============================================================================
// Event handler hook functions
// -----------------------------------------------------------------------------
EventHandlerResult AutoShift::onKeyswitchEvent(KeyEvent &event) {
// If AutoShift has already processed and released this event, ignore it.
// There's no need to update the event tracker in this one case.
if (event_tracker_.shouldIgnore(event)) {
// We should never get an event that's in our queue here, but just in case
// some other plugin sends one, abort.
if (queue_.shouldAbort(event))
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
// If event.addr is not a physical key, ignore it; some other plugin injected
// it. This check should be unnecessary.
if (!event.addr.isValid() || keyIsInjected(event.state)) {
return EventHandlerResult::OK;
}
// Do nothing if disabled.
if (!settings_.enabled)
return EventHandlerResult::OK;
if (!queue_.isEmpty()) {
// There's an unresolved AutoShift key press.
if (keyToggledOn(event.state) ||
event.addr == queue_.addr(0) ||
queue_.isFull()) {
// If a new key toggled on, the unresolved key toggled off (it was a
// "tap"), or if the queue is full, we clear the queue, and the key event
// does not get modified.
flushEvent(false);
flushQueue();
} else {
// Otherwise, add the release event to the queue. We do this so that
// rollover from a modifier to an auto-shifted key will result in the
// modifier being applied to the key.
queue_.append(event);
return EventHandlerResult::ABORT;
}
}
if (keyToggledOn(event.state) && isAutoShiftable(event.key)) {
// The key is eligible to be auto-shifted, so we add it to the queue and
// defer processing of the event.
queue_.append(event);
return EventHandlerResult::ABORT;
}
return EventHandlerResult::OK;
}
// -----------------------------------------------------------------------------
EventHandlerResult AutoShift::afterEachCycle() {
// If there's a pending AutoShift event, and it has timed out, we need to
// release the event with the `shift` flag applied.
if (!queue_.isEmpty() &&
Runtime.hasTimeExpired(queue_.timestamp(0), settings_.timeout)) {
// Toggle the state of the `SHIFT_HELD` bit in the modifier flags for the
// key for the pending event.
flushEvent(true);
flushQueue();
}
return EventHandlerResult::OK;
}
void AutoShift::flushQueue() {
while (!queue_.isEmpty()) {
if (queue_.isRelease(0) || checkForRelease()) {
flushEvent(false);
} else {
return;
}
}
}
bool AutoShift::checkForRelease() const {
KeyAddr queue_head_addr = queue_.addr(0);
for (uint8_t i = 1; i < queue_.length(); ++i) {
if (queue_.addr(i) == queue_head_addr) {
// This key's release is also in the queue
return true;
}
}
return false;
}
void AutoShift::flushEvent(bool is_long_press) {
if (queue_.isEmpty())
return;
KeyEvent event = queue_.event(0);
if (is_long_press) {
event.key = Runtime.lookupKey(event.addr);
uint8_t flags = event.key.getFlags();
flags ^= SHIFT_HELD;
event.key.setFlags(flags);
}
queue_.shift();
Runtime.handleKeyswitchEvent(event);
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::AutoShift AutoShift;