Adapt SpaceCadet plugin to KeyEvent handlers

Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
pull/1024/head
Michael Richters 4 years ago
parent 2842b377e9
commit b5a006c228
No known key found for this signature in database
GPG Key ID: 1288FD13E4EEF0C0

@ -21,261 +21,203 @@
#include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/key_events.h" #include "kaleidoscope/key_events.h"
//#include <Kaleidoscope-Devel-ArduinoTrace.h>
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
//Constructor with input and output, and assume default timeout // Constructor with input and output, and assume default timeout
SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_) { SpaceCadet::KeyBinding::KeyBinding(Key input, Key output)
input = input_; : input(input), output(output) {}
output = output_;
}
//Constructor with all three set // Constructor with all three set
SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_, uint16_t timeout_) { SpaceCadet::KeyBinding::KeyBinding(Key input, Key output, uint16_t timeout)
input = input_; : input(input), output(output), timeout(timeout) {}
output = output_;
timeout = timeout_; // =============================================================================
} // Space Cadet class variables
// -----------------------------------------------------------------------------
// Plugin configuration variables
//Space Cadet
SpaceCadet::KeyBinding * SpaceCadet::map; SpaceCadet::KeyBinding * SpaceCadet::map;
uint16_t SpaceCadet::time_out = 1000; uint16_t SpaceCadet::time_out = 200;
// -----------------------------------------------------------------------------
// State variables
bool SpaceCadet::disabled = false; 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() { SpaceCadet::SpaceCadet() {
static SpaceCadet::KeyBinding initialmap[] = { static SpaceCadet::KeyBinding initialmap[] = {
//By default, respect the default timeout // By default, respect the default timeout
{Key_LeftShift, Key_LeftParen, 0} {Key_LeftShift, Key_LeftParen, 0},
, {Key_RightShift, Key_RightParen, 0} {Key_RightShift, Key_RightParen, 0},
//These may be uncommented, added, or set in the main sketch // These may be uncommented, added, or set in the main sketch
/*,{Key_LeftGui,Key_LeftCurlyBracket, 250} /*
,{Key_RightAlt,Key_RightCurlyBracket, 250} {Key_LeftGui, Key_LeftCurlyBracket, 250},
,{Key_LeftControl,Key_LeftBracket, 250} {Key_RightAlt, Key_RightCurlyBracket, 250},
,{Key_RightControl,Key_RightBracket, 250}*/ {Key_LeftControl, Key_LeftBracket, 250},
, SPACECADET_MAP_END {Key_RightControl, Key_RightBracket, 250},
*/
SPACECADET_MAP_END
}; };
map = initialmap; 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() { void SpaceCadet::enable() {
disabled = false; disabled = false;
} }
//Function to disable SpaceCadet behavior // Function to disable SpaceCadet behavior
void SpaceCadet::disable() { void SpaceCadet::disable() {
disabled = true; disabled = true;
} }
//Function to determine whether SpaceCadet is active (useful for Macros and other plugins) // =============================================================================
bool SpaceCadet::active() { // Event handler hook functions
return !disabled;
}
// -----------------------------------------------------------------------------
EventHandlerResult SpaceCadet::onNameQuery() { EventHandlerResult SpaceCadet::onNameQuery() {
return ::Focus.sendName(F("SpaceCadet")); 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 EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
if (mapped_key.getRaw() >= kaleidoscope::ranges::SC_FIRST && // If SpaceCadet has already processed and released this event, ignore
mapped_key.getRaw() <= kaleidoscope::ranges::SC_LAST) { // it. There's no need to update the event tracker in this case.
//Only fire the activate / deactivate on the initial press (not held or release) if (event_tracker_.shouldIgnore(event)) {
if (keyToggledOn(key_state)) { // We should never get an event that's in our queue here, but just in case
if (mapped_key == Key_SpaceCadetEnable) { // some other plugin sends one, abort.
enable(); if (event_queue_.shouldAbort(event))
} else if (mapped_key == Key_SpaceCadetDisable) { return EventHandlerResult::ABORT;
disable(); return EventHandlerResult::OK;
}
}
return EventHandlerResult::EVENT_CONSUMED;
} }
//if SpaceCadet is disabled, this was an injected key, it was NoKey, // If event.addr is not a physical key, ignore it; some other plugin injected
//or if they key somehow came here without being either pressed or released, // it. This check should be unnecessary.
//return the mapped key and just get out of here. if (!event.addr.isValid() || (event.state & INJECTED) != 0) {
if (
disabled
|| (key_state & INJECTED)
|| mapped_key == Key_NoKey
|| (!keyIsPressed(key_state) && !keyWasPressed(key_state))
) {
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
// If a key has been just toggled on... // Turn SpaceCadet on or off.
if (keyToggledOn(key_state)) { if (keyToggledOn(event.state)) {
if (event.key == Key_SpaceCadetEnable) {
//check to see if we found a valid key. Assume not valid. enable();
bool valid_key = false; return EventHandlerResult::EVENT_CONSUMED;
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;
}
} }
if (event.key == Key_SpaceCadetDisable) {
//If we found a valid key in our map, we don't actually want to send anything. disable();
//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) {
return EventHandlerResult::EVENT_CONSUMED; 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, // Do nothing if disabled, but keep the event tracker current.
// or we used another key in the interim. in both cases, nothing special to do. if (disabled)
bool valid_key = false; return EventHandlerResult::OK;
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;
}
//the key we're looking at was valid (in the map) if (!event_queue_.isEmpty()) {
if (map[i].input == mapped_key) { // There's an unresolved SpaceCadet key press.
pressed_key_was_valid = true; 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 // Event queue is now empty
//was originally passed in. if (keyToggledOn(event.state)) {
if (!valid_key) { // Check for a SpaceCadet key
return EventHandlerResult::OK; pending_map_index_ = getSpaceCadetKeyIndex(event.key);
} if (pending_map_index_ >= 0) {
// A SpaceCadet key was pressed
//use the map index to find the local timeout for this key event_queue_.append(event);
uint16_t current_timeout = map[index].timeout; return EventHandlerResult::ABORT;
//If that isn't set, use the global timeout setting. }
if (current_timeout == 0) {
current_timeout = time_out;
} }
//Check to determine if we have surpassed our timeout for holding this key return EventHandlerResult::OK;
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;
}
// 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 EventHandlerResult SpaceCadet::afterEachCycle() {
// interrupt us. // If there's no pending event, return.
if (!pressed_key_was_valid) { if (event_queue_.isEmpty())
return EventHandlerResult::OK; return EventHandlerResult::OK;
}
// if a key toggled off (and that must be one of the mapped keys at this point), // Get timeout value for the pending key.
// send the alternative key instead (if we were interrupted, we bailed out earlier). uint16_t pending_timeout = time_out;
if (keyToggledOff(key_state)) { if (map[pending_map_index_].timeout != 0)
Key alternate_key = map[index].output; 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), if (Runtime.hasTimeExpired(start_time, pending_timeout)) {
//only need to send that key and not the original key. // 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. int8_t SpaceCadet::getSpaceCadetKeyIndex(Key key) const {
map[index].flagged = false; for (uint8_t i = 0; !map[i].isEmpty(); ++i) {
map[index].start_time = 0; 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. void SpaceCadet::flushQueue() {
//If it's a valid key, and it's continuing to be held, return NoKey. while (!event_queue_.isEmpty()) {
//This prevents us from accidentally triggering a keypress that we didn't flushEvent(false);
//mean to handle.
if (valid_key) {
return EventHandlerResult::EVENT_CONSUMED;
} }
//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; kaleidoscope::plugin::SpaceCadet SpaceCadet;

@ -19,6 +19,8 @@
#pragma once #pragma once
#include "kaleidoscope/Runtime.h" #include "kaleidoscope/Runtime.h"
#include "kaleidoscope/KeyEventTracker.h"
#include "kaleidoscope/KeyAddrEventQueue.h"
#include <Kaleidoscope-Ranges.h> #include <Kaleidoscope-Ranges.h>
#ifndef SPACECADET_MAP_END #ifndef SPACECADET_MAP_END
@ -33,44 +35,60 @@ namespace plugin {
class SpaceCadet : public kaleidoscope::Plugin { class SpaceCadet : public kaleidoscope::Plugin {
public: public:
//Internal Class // Internal Class
//Declarations for the modifier key mapping // Declarations for the modifier key mapping
class KeyBinding { class KeyBinding {
public: public:
//Empty constructor; set the vars separately // Empty constructor; set the vars separately
KeyBinding(void) {} KeyBinding() {}
//Constructor with input and output // Constructor with input and output
KeyBinding(Key input_, Key output_); KeyBinding(Key input, Key output);
//Constructor with all three set // Constructor with all three set
KeyBinding(Key input_, Key output_, uint16_t timeout_); KeyBinding(Key input, Key output, uint16_t timeout);
//The key that is pressed // The key that is pressed
Key input; Key input;
//the key that is sent // the key that is sent
Key output; Key output;
//The timeout (default to global timeout) // The timeout (default to global timeout)
uint16_t timeout = 0; uint16_t timeout = 0;
//The flag (set to 0) // to check for the end of a list (SPACECADET_MAP_END)
bool flagged = false; bool isEmpty() const {
//the start time for this key press return (input == Key_NoKey && output == Key_NoKey && timeout == 0);
uint16_t start_time = 0; }
}; };
SpaceCadet(void); SpaceCadet(void);
//Methods // Methods
static void enable(void); static void enable(void);
static void disable(void); static void disable(void);
static bool active(void); static bool active(void);
//Publically accessible variables // Publically accessible variables
static uint16_t time_out; // The global timeout in milliseconds static uint16_t time_out; // The global timeout in milliseconds
static SpaceCadet::KeyBinding * map; // The map of key bindings static SpaceCadet::KeyBinding * map; // The map of key bindings
EventHandlerResult onNameQuery(); EventHandlerResult onNameQuery();
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult afterEachCycle();
private: private:
static bool disabled; static bool disabled;
static KeyEventTracker event_tracker_;
// The maximum number of events in the queue at a time.
static constexpr uint8_t queue_capacity_{4};
// The event queue stores a series of press and release events.
KeyAddrEventQueue<queue_capacity_> event_queue_;
static int8_t pending_map_index_;
int8_t getSpaceCadetKeyIndex(Key key) const;
void flushEvent(bool is_tap = false);
void flushQueue();
}; };
} }

Loading…
Cancel
Save