From 47e5d841530e4e8a7e5483e6c5e3d982876febbe Mon Sep 17 00:00:00 2001 From: Ben Gemperline Date: Sat, 10 Mar 2018 15:34:33 -0700 Subject: [PATCH] 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. --- README.md | 32 ++++++++ examples/SpaceCadet/SpaceCadet.ino | 4 +- src/Kaleidoscope/SpaceCadet.cpp | 114 ++++++++++++++++++++++++----- src/Kaleidoscope/SpaceCadet.h | 10 +++ 4 files changed, 139 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d4b38aed..5a8fc2c9 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,38 @@ properties: > > 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 Starting from the [example][plugin:example] is the recommended way of getting diff --git a/examples/SpaceCadet/SpaceCadet.ino b/examples/SpaceCadet/SpaceCadet.ino index b584666c..4cff2d47 100644 --- a/examples/SpaceCadet/SpaceCadet.ino +++ b/examples/SpaceCadet/SpaceCadet.ino @@ -22,7 +22,7 @@ const Key keymaps[][ROWS][COLS] PROGMEM = { [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_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, @@ -30,7 +30,7 @@ const Key keymaps[][ROWS][COLS] PROGMEM = { Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, 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_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, diff --git a/src/Kaleidoscope/SpaceCadet.cpp b/src/Kaleidoscope/SpaceCadet.cpp index 7fbd090a..657a2468 100644 --- a/src/Kaleidoscope/SpaceCadet.cpp +++ b/src/Kaleidoscope/SpaceCadet.cpp @@ -37,6 +37,7 @@ SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_, uint16_t timeout_) { //Space Cadet SpaceCadet::KeyBinding * SpaceCadet::map; uint16_t SpaceCadet::time_out = 1000; +bool SpaceCadet::disabled = false; //Empty Constructor SpaceCadet::SpaceCadet() { @@ -55,23 +56,61 @@ SpaceCadet::SpaceCadet() { 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() { Kaleidoscope.useEventHandlerHook(eventHandlerHook); } 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 (!keyIsPressed(key_state) && !keyWasPressed(key_state)) { + //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 mapped_key; } // If a key has been just toggled on... if (keyToggledOn(key_state)) { - //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 sentinal + //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 ; !( @@ -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) { - //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].start_time = millis(); + + //yes, we found a valid key + valid_key = true; + } 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].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; } - // 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. bool valid_key = false; bool pressed_key_was_valid = false; uint8_t index = 0; - //Look to see if any keys in our map are flagged. - //Exit condition is if we reach the sentinal + //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 keycode + //If no valid mapped keys were pressed, simply return the key that + //was originally passed in. if (!valid_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; } + //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 mapped_key; } // 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 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; //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 - //may want to even UNSET the originally pressed key (future - //enhanacement?). This might also mean we don't need to return the - //key that was pressed, though I haven't confirmed that. + //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. @@ -170,6 +237,15 @@ Key SpaceCadet::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key 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; } diff --git a/src/Kaleidoscope/SpaceCadet.h b/src/Kaleidoscope/SpaceCadet.h index 6fa9d1cf..b0a82ca2 100644 --- a/src/Kaleidoscope/SpaceCadet.h +++ b/src/Kaleidoscope/SpaceCadet.h @@ -24,6 +24,12 @@ #define SPACECADET_MAP_END (kaleidoscope::SpaceCadet::KeyBinding) { Key_NoKey, Key_NoKey, 0 } #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 { //Declaration for the method (implementing KaleidoscopePlugin) class SpaceCadet : public KaleidoscopePlugin { @@ -55,6 +61,9 @@ class SpaceCadet : public KaleidoscopePlugin { //Methods void begin(void) final; + static void enable(void); + static void disable(void); + static bool active(void); //Publically accessible variables static uint16_t time_out; // The global timeout in milliseconds @@ -62,6 +71,7 @@ class SpaceCadet : public KaleidoscopePlugin { private: static Key eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state); + static bool disabled; }; };