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

251 lines
8.2 KiB

/* -*- mode: c++ -*-
* Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended
* Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc, Ben Gemperline
*
* 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-SpaceCadet.h>
#include <kaleidoscope/hid.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 all three set
SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_, uint16_t timeout_) {
input = input_;
output = output_;
timeout = timeout_;
}
//Space Cadet
SpaceCadet::KeyBinding * SpaceCadet::map;
uint16_t SpaceCadet::time_out = 1000;
bool SpaceCadet::disabled = false;
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;
}
//Function to enable SpaceCadet behavior
void SpaceCadet::enable() {
disabled = false;
}
//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;
}
EventHandlerResult SpaceCadet::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) {
//Handle our synthetic keys for enabling and disabling functionality
if (mapped_key.raw >= kaleidoscope::ranges::SC_FIRST &&
mapped_key.raw <= 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;
}
//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))
) {
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;
//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.raw == Key_NoKey.raw
&& map[i].output.raw == Key_NoKey.raw
&& map[i].timeout == 0
) ;
++i
) {
if (mapped_key.raw == map[i].input.raw) {
//The keypress was valid and a match. Mark it as flagged and reset the counter
map[i].flagged = true;
map[i].start_time = millis();
//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, row, col, 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 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) {
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.raw == Key_NoKey.raw
&& map[i].output.raw == Key_NoKey.raw
&& 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 (map[i].input.raw == mapped_key.raw) {
pressed_key_was_valid = true;
}
}
//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;
}
//Check to determine if we have surpassed our timeout for holding this key
if ((millis() - 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
// interrupt us.
if (!pressed_key_was_valid) {
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;
//Since we are sending the actual key (no need for shift, etc),
//only need to send that key and not the original key.
//inject our new key
handleKeyswitchEvent(alternate_key, row, col, IS_PRESSED | INJECTED);
//Unflag the key so we don't try this again.
map[index].flagged = false;
map[index].start_time = 0;
}
//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;
}
//Finally, as a final sanity check, simply return the passed-in key as-is.
return EventHandlerResult::OK;
}
}
}
kaleidoscope::plugin::SpaceCadet SpaceCadet;