|
|
|
@ -21,261 +21,203 @@
|
|
|
|
|
#include "kaleidoscope/keyswitch_state.h"
|
|
|
|
|
#include "kaleidoscope/key_events.h"
|
|
|
|
|
|
|
|
|
|
//#include <Kaleidoscope-Devel-ArduinoTrace.h>
|
|
|
|
|
|
|
|
|
|
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 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_;
|
|
|
|
|
}
|
|
|
|
|
// 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
|
|
|
|
|
|
|
|
|
|
//Space Cadet
|
|
|
|
|
SpaceCadet::KeyBinding * SpaceCadet::map;
|
|
|
|
|
uint16_t SpaceCadet::time_out = 1000;
|
|
|
|
|
uint16_t SpaceCadet::time_out = 200;
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// State variables
|
|
|
|
|
|
|
|
|
|
bool SpaceCadet::disabled = false;
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Function to enable SpaceCadet behavior
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
// Function to determine whether SpaceCadet is active (useful for Macros and
|
|
|
|
|
// other plugins).
|
|
|
|
|
bool SpaceCadet::active() {
|
|
|
|
|
return !disabled;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Function to enable SpaceCadet behavior
|
|
|
|
|
void SpaceCadet::enable() {
|
|
|
|
|
disabled = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Function to disable SpaceCadet behavior
|
|
|
|
|
// Function to disable SpaceCadet behavior
|
|
|
|
|
void SpaceCadet::disable() {
|
|
|
|
|
disabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Function to determine whether SpaceCadet is active (useful for Macros and other plugins)
|
|
|
|
|
bool SpaceCadet::active() {
|
|
|
|
|
return !disabled;
|
|
|
|
|
}
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Event handler hook functions
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
EventHandlerResult SpaceCadet::onNameQuery() {
|
|
|
|
|
return ::Focus.sendName(F("SpaceCadet"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventHandlerResult SpaceCadet::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) {
|
|
|
|
|
//Handle our synthetic keys for enabling and disabling functionality
|
|
|
|
|
if (mapped_key.getRaw() >= kaleidoscope::ranges::SC_FIRST &&
|
|
|
|
|
mapped_key.getRaw() <= kaleidoscope::ranges::SC_LAST) {
|
|
|
|
|
//Only fire the activate / deactivate on the initial press (not held or release)
|
|
|
|
|
if (keyToggledOn(key_state)) {
|
|
|
|
|
if (mapped_key == Key_SpaceCadetEnable) {
|
|
|
|
|
enable();
|
|
|
|
|
} else if (mapped_key == Key_SpaceCadetDisable) {
|
|
|
|
|
disable();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return EventHandlerResult::EVENT_CONSUMED;
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
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 SpaceCadet is disabled, this was an injected key, it was NoKey,
|
|
|
|
|
//or if they key somehow came here without being either pressed or released,
|
|
|
|
|
//return the mapped key and just get out of here.
|
|
|
|
|
if (
|
|
|
|
|
disabled
|
|
|
|
|
|| (key_state & INJECTED)
|
|
|
|
|
|| mapped_key == Key_NoKey
|
|
|
|
|
|| (!keyIsPressed(key_state) && !keyWasPressed(key_state))
|
|
|
|
|
) {
|
|
|
|
|
// If event.addr is not a physical key, ignore it; some other plugin injected
|
|
|
|
|
// it. This check should be unnecessary.
|
|
|
|
|
if (!event.addr.isValid() || (event.state & INJECTED) != 0) {
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If a key has been just toggled on...
|
|
|
|
|
if (keyToggledOn(key_state)) {
|
|
|
|
|
|
|
|
|
|
//check to see if we found a valid key. Assume not valid.
|
|
|
|
|
bool valid_key = false;
|
|
|
|
|
bool other_mapped_key_flagged = false;
|
|
|
|
|
|
|
|
|
|
//Check the current map to see if any other key has been already flagged
|
|
|
|
|
//Exit condition is if we reach the special SPACECADET_MAP_END sentinel
|
|
|
|
|
for (
|
|
|
|
|
uint8_t i = 0 ;
|
|
|
|
|
!(
|
|
|
|
|
map[i].input == Key_NoKey
|
|
|
|
|
&& map[i].output == Key_NoKey
|
|
|
|
|
&& map[i].timeout == 0
|
|
|
|
|
) ;
|
|
|
|
|
++i
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
if (map[i].flagged
|
|
|
|
|
&& map[i].input != mapped_key) {
|
|
|
|
|
other_mapped_key_flagged = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//This will only set one key, and, if it isn't in our map, it clears everything for the non-pressed key
|
|
|
|
|
//Exit condition is if we reach the special SPACECADET_MAP_END sentinel
|
|
|
|
|
for (
|
|
|
|
|
uint8_t i = 0 ;
|
|
|
|
|
!(
|
|
|
|
|
map[i].input == Key_NoKey
|
|
|
|
|
&& map[i].output == Key_NoKey
|
|
|
|
|
&& map[i].timeout == 0
|
|
|
|
|
) ;
|
|
|
|
|
++i
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
if (mapped_key == map[i].input) {
|
|
|
|
|
//Only activate this as part of the mapping if there isn't already a
|
|
|
|
|
//key waiting for timeout. This allows us to return OK later and for
|
|
|
|
|
//this loop to inject all the other flagged keys
|
|
|
|
|
if (!other_mapped_key_flagged) {
|
|
|
|
|
//The keypress was valid and a match. Mark it as flagged and reset the counter
|
|
|
|
|
map[i].flagged = true;
|
|
|
|
|
map[i].start_time = Runtime.millisAtCycleStart();
|
|
|
|
|
|
|
|
|
|
//yes, we found a valid key
|
|
|
|
|
valid_key = true;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
//If the key entry we're looking at was flagged previously, add it to the
|
|
|
|
|
//report before we do anything else (this handles the situation where we
|
|
|
|
|
//hit another key after this -- if it's a modifier, we want the modifier
|
|
|
|
|
//key to be added to the report, for things like ctrl, alt, shift, etc)
|
|
|
|
|
if (map[i].flagged) {
|
|
|
|
|
handleKeyswitchEvent(map[i].input, UnknownKeyswitchLocation, IS_PRESSED | INJECTED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//The keypress wasn't a match, so we need to mark it as not flagged and
|
|
|
|
|
//reset the timer for it to disable everything.
|
|
|
|
|
map[i].flagged = false;
|
|
|
|
|
map[i].start_time = 0;
|
|
|
|
|
}
|
|
|
|
|
// Turn SpaceCadet on or off.
|
|
|
|
|
if (keyToggledOn(event.state)) {
|
|
|
|
|
if (event.key == Key_SpaceCadetEnable) {
|
|
|
|
|
enable();
|
|
|
|
|
return EventHandlerResult::EVENT_CONSUMED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If we found a valid key in our map, we don't actually want to send anything.
|
|
|
|
|
//This gets around an issue in Windows if we map a SpaceCadet function on top
|
|
|
|
|
//of Alt -- sending Alt by itself activates the menubar. We don't want to send
|
|
|
|
|
//anything until we know that we're either sending the alternate key or we
|
|
|
|
|
//know for sure that we want to send the originally pressed key.
|
|
|
|
|
if (valid_key) {
|
|
|
|
|
if (event.key == Key_SpaceCadetDisable) {
|
|
|
|
|
disable();
|
|
|
|
|
return EventHandlerResult::EVENT_CONSUMED;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//this is all we need to do on keypress, let the next handler do its thing too.
|
|
|
|
|
//This case assumes we weren't a valid key that we were watching, so we don't
|
|
|
|
|
//need to do anything else.
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if the state is empty, that means that either an activating key wasn't pressed,
|
|
|
|
|
// or we used another key in the interim. in both cases, nothing special to do.
|
|
|
|
|
bool valid_key = false;
|
|
|
|
|
bool pressed_key_was_valid = false;
|
|
|
|
|
uint8_t index = 0;
|
|
|
|
|
|
|
|
|
|
//Look to see if any keys in our map are currently flagged.
|
|
|
|
|
//Exit condition is if we reach the special SPACECADET_MAP_END sentinel
|
|
|
|
|
for (
|
|
|
|
|
uint8_t i = 0 ;
|
|
|
|
|
!(
|
|
|
|
|
map[i].input == Key_NoKey
|
|
|
|
|
&& map[i].output == Key_NoKey
|
|
|
|
|
&& map[i].timeout == 0
|
|
|
|
|
);
|
|
|
|
|
++i
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
//The key we're looking at was previously flagged (so perform action)
|
|
|
|
|
if (map[i].flagged) {
|
|
|
|
|
valid_key = true;
|
|
|
|
|
index = i;
|
|
|
|
|
}
|
|
|
|
|
// Do nothing if disabled, but keep the event tracker current.
|
|
|
|
|
if (disabled)
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
|
|
|
|
|
//the key we're looking at was valid (in the map)
|
|
|
|
|
if (map[i].input == mapped_key) {
|
|
|
|
|
pressed_key_was_valid = true;
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//If no valid mapped keys were pressed, simply return the key that
|
|
|
|
|
//was originally passed in.
|
|
|
|
|
if (!valid_key) {
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//use the map index to find the local timeout for this key
|
|
|
|
|
uint16_t current_timeout = map[index].timeout;
|
|
|
|
|
//If that isn't set, use the global timeout setting.
|
|
|
|
|
if (current_timeout == 0) {
|
|
|
|
|
current_timeout = time_out;
|
|
|
|
|
// 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 was pressed
|
|
|
|
|
event_queue_.append(event);
|
|
|
|
|
return EventHandlerResult::ABORT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Check to determine if we have surpassed our timeout for holding this key
|
|
|
|
|
if (Runtime.hasTimeExpired(map[index].start_time, current_timeout)) {
|
|
|
|
|
// if we timed out, that means we need to keep pressing the mapped
|
|
|
|
|
// key, but we won't need to send the alternative key in the end
|
|
|
|
|
map[index].flagged = false;
|
|
|
|
|
map[index].start_time = 0;
|
|
|
|
|
|
|
|
|
|
//Just return this key itself (we won't run alternative keys check)
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the key that was pressed isn't one of our mapped keys, just
|
|
|
|
|
// return. This can happen when another key is released, and that should not
|
|
|
|
|
// interrupt us.
|
|
|
|
|
if (!pressed_key_was_valid) {
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
EventHandlerResult SpaceCadet::afterEachCycle() {
|
|
|
|
|
// If there's no pending event, return.
|
|
|
|
|
if (event_queue_.isEmpty())
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if a key toggled off (and that must be one of the mapped keys at this point),
|
|
|
|
|
// send the alternative key instead (if we were interrupted, we bailed out earlier).
|
|
|
|
|
if (keyToggledOff(key_state)) {
|
|
|
|
|
Key alternate_key = map[index].output;
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
//Since we are sending the actual key (no need for shift, etc),
|
|
|
|
|
//only need to send that key and not the original key.
|
|
|
|
|
if (Runtime.hasTimeExpired(start_time, pending_timeout)) {
|
|
|
|
|
// The timer has expired; release the pending event unchanged.
|
|
|
|
|
flushQueue();
|
|
|
|
|
}
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//inject our new key
|
|
|
|
|
handleKeyswitchEvent(alternate_key, key_addr, IS_PRESSED | INJECTED);
|
|
|
|
|
// =============================================================================
|
|
|
|
|
// Private helper function(s)
|
|
|
|
|
|
|
|
|
|
//Unflag the key so we don't try this again.
|
|
|
|
|
map[index].flagged = false;
|
|
|
|
|
map[index].start_time = 0;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Special case here for if we had a valid key that's continuing to be held.
|
|
|
|
|
//If it's a valid key, and it's continuing to be held, return NoKey.
|
|
|
|
|
//This prevents us from accidentally triggering a keypress that we didn't
|
|
|
|
|
//mean to handle.
|
|
|
|
|
if (valid_key) {
|
|
|
|
|
return EventHandlerResult::EVENT_CONSUMED;
|
|
|
|
|
void SpaceCadet::flushQueue() {
|
|
|
|
|
while (!event_queue_.isEmpty()) {
|
|
|
|
|
flushEvent(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Finally, as a final sanity check, simply return the passed-in key as-is.
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SpaceCadet::flushEvent(bool is_tap) {
|
|
|
|
|
KeyEvent event = event_queue_.event(0);
|
|
|
|
|
if (is_tap && pending_map_index_ >= 0) {
|
|
|
|
|
event.key = map[pending_map_index_].output;
|
|
|
|
|
}
|
|
|
|
|
event_queue_.shift();
|
|
|
|
|
Runtime.handleKeyswitchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace plugin
|
|
|
|
|
} // namespace kaleidoscope
|
|
|
|
|
|
|
|
|
|
kaleidoscope::plugin::SpaceCadet SpaceCadet;
|
|
|
|
|