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-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp

226 lines
7.9 KiB

/* -*- mode: c++ -*-
* Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended
* Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc, Ben Gemperline
* Copyright (C) 2019-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/SpaceCadet.h"
#include <Arduino.h> // for F, __FlashStringHelper
#include <Kaleidoscope-FocusSerial.h> // for Focus, FocusSerial
#include <stdint.h> // for uint16_t, int8_t, uin...
#include "kaleidoscope/KeyAddr.h" // for KeyAddr, MatrixAddr
#include "kaleidoscope/KeyAddrEventQueue.h" // for KeyAddrEventQueue
#include "kaleidoscope/KeyEvent.h" // for KeyEvent
#include "kaleidoscope/KeyEventTracker.h" // for KeyEventTracker
#include "kaleidoscope/Runtime.h" // for Runtime, Runtime_
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/key_defs.h" // for Key, Key_LeftParen
#include "kaleidoscope/keyswitch_state.h" // for keyToggledOn, WAS_PRE...
namespace kaleidoscope {
namespace plugin {
// Constructor with input and output, and assume default timeout
SpaceCadet::KeyBinding::KeyBinding(Key input, Key output)
: input(input), output(output) {}
// Constructor with all three set
SpaceCadet::KeyBinding::KeyBinding(Key input, Key output, uint16_t timeout)
: input(input), output(output), timeout(timeout) {}
// =============================================================================
// Space Cadet class variables
// -----------------------------------------------------------------------------
// Plugin configuration variables
SpaceCadet::KeyBinding * SpaceCadet::map;
uint16_t SpaceCadet::time_out = 200;
// -----------------------------------------------------------------------------
// State variables
uint8_t SpaceCadet::mode_ = SpaceCadet::Mode::ON;
// These variables are used to keep track of any pending unresolved SpaceCadet
// key that has been pressed. If `pending_map_index_` is negative, it means
// there is no such pending keypress. Otherwise, it holds the value of the index
// of that key in the array.
int8_t SpaceCadet::pending_map_index_ = -1;
KeyEventTracker SpaceCadet::event_tracker_;
// =============================================================================
// SpaceCadet functions
// Constructor
SpaceCadet::SpaceCadet() {
static SpaceCadet::KeyBinding initialmap[] = {
// By default, respect the default timeout
{Key_LeftShift, Key_LeftParen, 0},
{Key_RightShift, Key_RightParen, 0},
// These may be uncommented, added, or set in the main sketch
/*
{Key_LeftGui, Key_LeftCurlyBracket, 250},
{Key_RightAlt, Key_RightCurlyBracket, 250},
{Key_LeftControl, Key_LeftBracket, 250},
{Key_RightControl, Key_RightBracket, 250},
*/
SPACECADET_MAP_END
};
map = initialmap;
}
// =============================================================================
// Event handler hook functions
// -----------------------------------------------------------------------------
EventHandlerResult SpaceCadet::onNameQuery() {
return ::Focus.sendName(F("SpaceCadet"));
}
// -----------------------------------------------------------------------------
EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
// If SpaceCadet has already processed and released this event, ignore
// it. There's no need to update the event tracker in this 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 (event_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;
}
// Turn SpaceCadet on or off.
if (keyToggledOn(event.state)) {
if (event.key == Key_SpaceCadetEnable) {
enable();
return EventHandlerResult::EVENT_CONSUMED;
}
if (event.key == Key_SpaceCadetDisable) {
disable();
return EventHandlerResult::EVENT_CONSUMED;
}
}
// Do nothing if disabled, but keep the event tracker current.
if (mode_ == Mode::OFF)
return EventHandlerResult::OK;
if (!event_queue_.isEmpty()) {
// There's an unresolved SpaceCadet key press.
if (keyToggledOff(event.state)) {
if (event.addr == event_queue_.addr(0)) {
// SpaceCadet key released before timing out; send the event with the
// SpaceCadet key's alternate `Key` value before flushing the rest of
// the queue (see below).
flushEvent(true);
} else if (!event_queue_.isFull()) {
// Queue not full; add event and abort
event_queue_.append(event);
return EventHandlerResult::ABORT;
}
}
// Either a new key was pressed, or the SpaceCadet key was released and has
// been flushed (see above), or the queue is full and is about to overflow.
// In all cases, we fulsh the whole queue now.
flushQueue();
}
// Event queue is now empty
if (keyToggledOn(event.state)) {
// Check for a SpaceCadet key
pending_map_index_ = getSpaceCadetKeyIndex(event.key);
if (pending_map_index_ >= 0) {
// A SpaceCadet key has just toggled on. First, if we're in no-delay mode,
// we need to send the event unchanged (with the primary `Key` value),
// bypassing other `onKeyswitchEvent()` handlers.
if (mode_ == Mode::NO_DELAY)
Runtime.handleKeyEvent(event);
// Queue the press event and abort; this press event will be resolved
// later.
event_queue_.append(event);
return EventHandlerResult::ABORT;
}
}
return EventHandlerResult::OK;
}
// -----------------------------------------------------------------------------
EventHandlerResult SpaceCadet::afterEachCycle() {
// If there's no pending event, return.
if (event_queue_.isEmpty())
return EventHandlerResult::OK;
// Get timeout value for the pending key.
uint16_t pending_timeout = time_out;
if (map[pending_map_index_].timeout != 0)
pending_timeout = map[pending_map_index_].timeout;
uint16_t start_time = event_queue_.timestamp(0);
if (Runtime.hasTimeExpired(start_time, pending_timeout)) {
// The timer has expired; release the pending event unchanged.
flushQueue();
}
return EventHandlerResult::OK;
}
// =============================================================================
// Private helper function(s)
int8_t SpaceCadet::getSpaceCadetKeyIndex(Key key) const {
for (uint8_t i = 0; !map[i].isEmpty(); ++i) {
if (map[i].input == key) {
return i;
}
}
return -1;
}
void SpaceCadet::flushQueue() {
while (!event_queue_.isEmpty()) {
flushEvent(false);
}
}
void SpaceCadet::flushEvent(bool is_tap) {
KeyEvent event = event_queue_.event(0);
if (is_tap && pending_map_index_ >= 0) {
// If we're in no-delay mode, we should first send the release of the
// modifier key as a courtesy before sending the tap event.
if (mode_ == Mode::NO_DELAY) {
Runtime.handleKeyEvent(KeyEvent(event.addr, WAS_PRESSED));
}
event.key = map[pending_map_index_].output;
}
event_queue_.shift();
Runtime.handleKeyswitchEvent(event);
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::SpaceCadet SpaceCadet;