Extended functionality + fix Alt key under Windows

I made a bunch of changes to improve SpaceCadet for the better.

1. I resolved issue #9 ( https://github.com/keyboardio/Kaleidoscope-SpaceCadet/issues/9 ) to support mappings on Alt keys under Windows.  This change in behavior means that we don't send the initial key value (the key with a mapping) until we hit the timeout (if held) or if we hit another key in combination in the mean time (to keep modifiers working as expected in combination with other keys).  This means that when you place a mapping on Alt, we don't send Alt if you are just tapping -- we only send the other key value.  This prevents Alt capturing the menu bar in Windows apps, and probably means we can better support SpaceCadet on non-modifier keys.

2. I added support for enabling, disabling, and determining if SpaceCadet is currently enabled.  This allows other plugins and macros to better interact with SpaceCadet, and allows us to temporarily disable the behavior if that's desired.

3. I added two new virtual Key entries for placing on the user's keymap.  One key disables SpaceCadet, and the other key enables SpaceCadet.

I also updated the README.md with all of the relevant changes.
pull/389/head
Ben Gemperline 7 years ago
parent 3f35cab706
commit 47e5d84153

@ -121,6 +121,38 @@ properties:
> >
> Defaults to 1000. > Defaults to 1000.
### `.enable()`
> This method enables the SpaceCadet plugin. This is useful for interfacing
> with other plugins or macros, especially where SpaceCadet functionality isn't
> always desired.
>
> The default behavior is `enabled`.
### `.disable()`
> This method disables the SpaceCadet behavior. This is useful for interfacing
> with other plugins or macros, especially where SpaceCadet functionality isn't
> always desired.
### `.active()`
> This method returns `true` if SpaceCadet is enabled and `false` if SpaceCadet
> is disabled. This is useful for interfacing with other plugins or macros,
> especially where SpaceCadet functionality isn't always desired.
### `Key_SpaceCadetEnable`
> This provides a key for placing on a keymap for enabling the SpaceCadet
> behavior. This is only triggered on initial downpress, and does not
> trigger again if held down or when the key is released.
### `Key_SpaceCadetDisable`
> This provides a key for placing on a keymap for disabling the SpaceCadet
> behavior. This is only triggered on initial downpress, and does not
> trigger again if held down or when the key is released.
## Further reading ## Further reading
Starting from the [example][plugin:example] is the recommended way of getting Starting from the [example][plugin:example] is the recommended way of getting

@ -22,7 +22,7 @@
const Key keymaps[][ROWS][COLS] PROGMEM = { const Key keymaps[][ROWS][COLS] PROGMEM = {
[0] = KEYMAP_STACKED [0] = KEYMAP_STACKED
( (
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey, Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_SpaceCadetEnable,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
@ -30,7 +30,7 @@ const Key keymaps[][ROWS][COLS] PROGMEM = {
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
Key_skip, Key_skip,
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip, Key_SpaceCadetDisable, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,

@ -37,6 +37,7 @@ SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_, uint16_t timeout_) {
//Space Cadet //Space Cadet
SpaceCadet::KeyBinding * SpaceCadet::map; SpaceCadet::KeyBinding * SpaceCadet::map;
uint16_t SpaceCadet::time_out = 1000; uint16_t SpaceCadet::time_out = 1000;
bool SpaceCadet::disabled = false;
//Empty Constructor //Empty Constructor
SpaceCadet::SpaceCadet() { SpaceCadet::SpaceCadet() {
@ -55,23 +56,61 @@ SpaceCadet::SpaceCadet() {
map = initialmap; 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;
}
void SpaceCadet::begin() { void SpaceCadet::begin() {
Kaleidoscope.useEventHandlerHook(eventHandlerHook); Kaleidoscope.useEventHandlerHook(eventHandlerHook);
} }
Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state) { Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state) {
//Handle our synthetic keys for enabling and disabling functionality
if (mapped_key.flags == (SYNTHETIC | IS_INTERNAL | SPACECADET_TOGGLE)) {
//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();
}
}
//in any case, return NoKey (these don't do anything else)
return Key_NoKey;
}
// If nothing happened, bail out fast. //if SpaceCadet is disabled, this was an injected key, it was NoKey,
if (!keyIsPressed(key_state) && !keyWasPressed(key_state)) { //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 mapped_key; return mapped_key;
} }
// If a key has been just toggled on... // If a key has been just toggled on...
if (keyToggledOn(key_state)) { if (keyToggledOn(key_state)) {
//This will only set one key, and if it isn't in our map it clears everything //check to see if we found a valid key. Assume not valid.
//for the non-pressed key bool valid_key = false;
//Exit condition is if we reach the sentinal
//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 ( for (
uint8_t i = 0 ; uint8_t i = 0 ;
!( !(
@ -83,49 +122,76 @@ Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key
) { ) {
if (mapped_key.raw == map[i].input.raw) { if (mapped_key.raw == map[i].input.raw) {
//The keypress was valid and a match. //The keypress was valid and a match. Mark it as flagged and reset the counter
map[i].flagged = true; map[i].flagged = true;
map[i].start_time = millis(); map[i].start_time = millis();
//yes, we found a valid key
valid_key = true;
} else { } else {
//The keypress wasn't a match. //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].flagged = false;
map[i].start_time = 0; map[i].start_time = 0;
} }
} }
// this is all we need to do on keypress, let the next handler do its thing too. //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 Key_NoKey;
}
//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 mapped_key; return mapped_key;
} }
// if the state is empty, that means that either the shifts weren't pressed, // 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. // or we used another key in the interim. in both cases, nothing special to do.
bool valid_key = false; bool valid_key = false;
bool pressed_key_was_valid = false; bool pressed_key_was_valid = false;
uint8_t index = 0; uint8_t index = 0;
//Look to see if any keys in our map are flagged. //Look to see if any keys in our map are currently flagged.
//Exit condition is if we reach the sentinal //Exit condition is if we reach the special SPACECADET_MAP_END sentinel
for ( for (
uint8_t i = 0 ; uint8_t i = 0 ;
!( !(
map[i].input.raw == Key_NoKey.raw map[i].input.raw == Key_NoKey.raw
&& map[i].output.raw == Key_NoKey.raw && map[i].output.raw == Key_NoKey.raw
&& map[i].timeout == 0 && map[i].timeout == 0
) ; );
++i ++i
) { ) {
//The key we're looking at was previously flagged (so perform action)
if (map[i].flagged) { if (map[i].flagged) {
valid_key = true; valid_key = true;
index = i; index = i;
} }
//the key we're looking at was valid (in the map)
if (map[i].input.raw == mapped_key.raw) { if (map[i].input.raw == mapped_key.raw) {
pressed_key_was_valid = true; pressed_key_was_valid = true;
} }
} }
//If no valid mapped keys were pressed, simply return the key that
//If no valid mapped keys were pressed, simply return the keycode //was originally passed in.
if (!valid_key) { if (!valid_key) {
return mapped_key; return mapped_key;
} }
@ -137,18 +203,20 @@ Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key
current_timeout = time_out; 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 ((millis() - map[index].start_time) >= current_timeout) {
// if we timed out, that means we need to keep pressing the mapped // 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 // key, but we won't need to send the alternative key in the end
map[index].flagged = false; map[index].flagged = false;
map[index].start_time = 0; map[index].start_time = 0;
//Just return this key itself (we won't run alternative keys check)
return mapped_key; return mapped_key;
} }
// If the key that was pressed isn't one of our mapped keys, just // 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 // return. This can happen when another key is released, and that should not
// interrupt us. // interrupt us.
if (!pressed_key_was_valid) { if (!pressed_key_was_valid) {
return mapped_key; return mapped_key;
} }
@ -159,10 +227,9 @@ Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key
Key alternate_key = map[index].output; Key alternate_key = map[index].output;
//Since we are sending the actual key (no need for shift, etc), //Since we are sending the actual key (no need for shift, etc),
//only need to send that key and not the original key. In fact, we //only need to send that key and not the original key.
//may want to even UNSET the originally pressed key (future
//enhanacement?). This might also mean we don't need to return the //inject our new key
//key that was pressed, though I haven't confirmed that.
handleKeyswitchEvent(alternate_key, row, col, IS_PRESSED | INJECTED); handleKeyswitchEvent(alternate_key, row, col, IS_PRESSED | INJECTED);
//Unflag the key so we don't try this again. //Unflag the key so we don't try this again.
@ -170,6 +237,15 @@ Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key
map[index].start_time = 0; 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 Key_NoKey;
}
//Finally, as a final sanity check, simply return the passed-in key as-is.
return mapped_key; return mapped_key;
} }

@ -24,6 +24,12 @@
#define SPACECADET_MAP_END (kaleidoscope::SpaceCadet::KeyBinding) { Key_NoKey, Key_NoKey, 0 } #define SPACECADET_MAP_END (kaleidoscope::SpaceCadet::KeyBinding) { Key_NoKey, Key_NoKey, 0 }
#endif #endif
#ifndef SPACECADET_TOGGLE
#define SPACECADET_TOGGLE B00000011 // Synthetic, internal
#define Key_SpaceCadetEnable (Key) { 0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | SPACECADET_TOGGLE }
#define Key_SpaceCadetDisable (Key) { 1, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | SPACECADET_TOGGLE }
#endif
namespace kaleidoscope { namespace kaleidoscope {
//Declaration for the method (implementing KaleidoscopePlugin) //Declaration for the method (implementing KaleidoscopePlugin)
class SpaceCadet : public KaleidoscopePlugin { class SpaceCadet : public KaleidoscopePlugin {
@ -55,6 +61,9 @@ class SpaceCadet : public KaleidoscopePlugin {
//Methods //Methods
void begin(void) final; void begin(void) final;
static void enable(void);
static void disable(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
@ -62,6 +71,7 @@ class SpaceCadet : public KaleidoscopePlugin {
private: private:
static Key eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state); static Key eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state);
static bool disabled;
}; };
}; };

Loading…
Cancel
Save