diff --git a/.gitignore b/.gitignore index be16c9be..f4adf160 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .#* *~ /hardware/ -/output/ \ No newline at end of file +/output/ +.DS_store diff --git a/README.md b/README.md index 88aa8e22..d4b38aed 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [st:broken]: https://img.shields.io/badge/broken-X-black.svg?style=flat&colorA=e05d44&colorB=494e52 [st:experimental]: https://img.shields.io/badge/experimental----black.svg?style=flat&colorA=dfb317&colorB=494e52 -[Space Cadet][space-cadet] Shift is a way to make it more convenient to input +[Space Cadet][space-cadet] is a way to make it more convenient to input parens - those `(` and `)` things -, symbols that a lot of programming languages use frequently. If you are working with Lisp, you are using these all the time. @@ -25,6 +25,15 @@ and it still would act as a `Shift`, without the parens inserted on release: this is useful when you want to augment some mouse action with `Shift`, to select text, for example. +After getting used to the Space Cadet style of typing, you may wish to enable +this sort of functionality on other keys, as well. Fortunately, the Space Cadet +plugin is configurable and extensible to support adding symbols to other keys. +Along with `(` on your left `Shift` key and `)` on your right `Shift` key, +you may wish to add other such programming mainstays as `{` to your left-side `cmd` key, +`}` to your right-side `alt` key, `[` to your left `Control` key, and `]` to your right +`Control` key. You can map the keys in whatever way you may wish to do, so feel free to +experiment with different combinations and discover what works best for you! + [space-cadet]: https://en.wikipedia.org/wiki/Space-cadet_keyboard ## Using the plugin @@ -37,38 +46,78 @@ enabling the plugin: #include void setup() { - Kaleidoscope.use(&SpaceCadetShift); + Kaleidoscope.use(&SpaceCadet); Kaleidoscope.setup(); } ``` -This assumes a US QWERTY layout on the host computer, and will use the `9` and -`0` keys for the left and right parens, respectively. To change these keys, use -the `.opening_paren` and `.closing_paren` properties outlined below. +This assumes a US QWERTY layout on the host computer, though the plugin sends +the correct keymap code for each symbol. Because the mapping is entirely +configurable, though, you may switch out keys at your leisure. + +If you wish to enable additional modifier keys (or disable the default behavior +for the shift and parentheses combinations), configuration is as simple as +passing a new keymap into the SpaceCadet object, as shown below: + + +```c++ +#include +#include + +void setup() { + Kaleidoscope.use(&SpaceCadet); + + //Set the keymap with a 250ms timeout per-key + //Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS} + //Note: must end with the SPACECADET_MAP_END delimiter + static kaleidoscope::SpaceCadet::KeyBinding spacecadetmap[] = { + {Key_LeftShift, Key_LeftParen, 250} + , {Key_RightShift, Key_RightParen, 250} + , {Key_LeftGui, Key_LeftCurlyBracket, 250} + , {Key_RightAlt, Key_RightCurlyBracket, 250} + , {Key_LeftAlt, Key_RightCurlyBracket, 250} + , {Key_LeftControl, Key_LeftBracket, 250} + , {Key_RightControl, Key_RightBracket, 250} + , SPACECADET_MAP_END + }; + //Set the map. + SpaceCadet.map = spacecadetmap; -## Plugin methods + Kaleidoscope.setup(); +} +``` + +## Plugin methods -The plugin provides the `SpaceCadetShift` object, with the following methods and +The plugin provides the `SpaceCadet` object, with the following methods and properties: -### `.opening_paren` +### `.map` -> Set this property to the key that - when shifted - will result in an opening paren. +> Set the key map. This takes an array of `kaleidoscope::SpaceCadet::KeyBinding` +> objects with the special `SPACECADET_MAP_END` sentinal to mark the end of the map. +> Each KeyBinding object takes, in order, the key that was pressed, the key that +> should be sent instead, and an optional per-key timeout override > -> Defaults to `Key_9`. +> If not explicitly set, defaults to mapping left `shift` to `(` and right `shift` +> to `)`. -### `.closing_paren` +### `kaleidoscope::SpaceCadet::KeyBinding` -> Set this property to the key that - when shifted - will result in a closing paren. -> -> Defaults to `Key_0`. +> An object consisting of the key that is pressed, the key that should be sent +> in its place, and the timeout (in milliseconds) until the key press is +> considered to be a "held" key press. The third parameter, the timeout, is +> optional and may be set per-key or left out entirely (or set to `0`) to use +> the default timeout value. ### `.time_out` > Set this property to the number of milliseconds to wait before considering a > held key in isolation as its secondary role. That is, we'd have to hold a -> `Shift` key this long, by itself, to trigger the `Shift` role in itself. +> `Shift` key this long, by itself, to trigger the `Shift` role in itself. This +> timeout setting can be overridden by an individual key in the keymap, but if +> it is omitted or set to `0` in the key map, the global timeout will be used. > > Defaults to 1000. diff --git a/examples/SpaceCadet/SpaceCadet.ino b/examples/SpaceCadet/SpaceCadet.ino index 4dd5c4a4..b584666c 100644 --- a/examples/SpaceCadet/SpaceCadet.ino +++ b/examples/SpaceCadet/SpaceCadet.ino @@ -40,7 +40,24 @@ const Key keymaps[][ROWS][COLS] PROGMEM = { }; void setup() { - Kaleidoscope.use(&SpaceCadetShift); + //Tell Kaleidoscope to use SpaceCadet + Kaleidoscope.use(&SpaceCadet); + + //Set the SpaceCadet map + //Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS} + //Note: must end with the SPACECADET_MAP_END delimiter + static kaleidoscope::SpaceCadet::KeyBinding spacecadetmap[] = { + {Key_LeftShift, Key_LeftParen, 250} + , {Key_RightShift, Key_RightParen, 250} + , {Key_LeftGui, Key_LeftCurlyBracket, 250} + , {Key_RightAlt, Key_RightCurlyBracket, 250} + , {Key_LeftAlt, Key_RightCurlyBracket, 250} + , {Key_LeftControl, Key_LeftBracket, 250} + , {Key_RightControl, Key_RightBracket, 250} + , SPACECADET_MAP_END + }; + //Set the map. + SpaceCadet.map = spacecadetmap; Kaleidoscope.setup(); } diff --git a/src/Kaleidoscope/SpaceCadet.cpp b/src/Kaleidoscope/SpaceCadet.cpp index 7ee88dd1..4e5e7615 100644 --- a/src/Kaleidoscope/SpaceCadet.cpp +++ b/src/Kaleidoscope/SpaceCadet.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- - * Kaleidoscope-SpaceCadet -- Space Cadet Shift - * Copyright (C) 2016, 2017 Gergely Nagy + * Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended + * Copyright (C) 2016, 2017 Gergely Nagy, 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 @@ -21,19 +21,46 @@ namespace kaleidoscope { -uint8_t SpaceCadetShift::paren_needed_; -uint32_t SpaceCadetShift::start_time_; -uint16_t SpaceCadetShift::time_out = 1000; -Key SpaceCadetShift::opening_paren = Key_9, SpaceCadetShift::closing_paren = Key_0; +//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_; +} -SpaceCadetShift::SpaceCadetShift() { +//Space Cadet +SpaceCadet::KeyBinding * SpaceCadet::map; +uint16_t SpaceCadet::time_out = 1000; + +//Empty Constructor +SpaceCadet::SpaceCadet() { + 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; } -void SpaceCadetShift::begin() { +void SpaceCadet::begin() { Kaleidoscope.useEventHandlerHook(eventHandlerHook); } -Key SpaceCadetShift::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) { + // If nothing happened, bail out fast. if (!keyIsPressed(key_state) && !keyWasPressed(key_state)) { return mapped_key; @@ -41,15 +68,29 @@ Key SpaceCadetShift::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_ // If a key has been just toggled on... if (keyToggledOn(key_state)) { - if (mapped_key.raw == Key_LeftShift.raw) { // if it is LShift, remember it - bitWrite(paren_needed_, 0, 1); - start_time_ = millis(); - } else if (mapped_key.raw == Key_RightShift.raw) { // if it is RShift, remember it - bitWrite(paren_needed_, 1, 1); - start_time_ = millis(); - } else { // if it is something else, we do not need a paren at the end. - paren_needed_ = 0; - start_time_ = 0; + + //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 + 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. + map[i].flagged = true; + map[i].start_time = millis(); + } else { + //The keypress wasn't a match. + 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. @@ -58,35 +99,76 @@ Key SpaceCadetShift::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_ // if the state is empty, that means that either the shifts weren't pressed, // or we used another key in the interim. in both cases, nothing special to do. - if (!paren_needed_) + 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 + 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 (map[i].flagged) { + valid_key = true; + index = i; + } + 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 (!valid_key) { return mapped_key; + } + + //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; + } - // if we timed out, that means we need to keep pressing shift, but won't - // need the parens in the end. - if ((millis() - start_time_) >= time_out) { - paren_needed_ = 0; + 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; return mapped_key; } - // if we have a state, but the key in question is not either of the shifts, + // 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 (mapped_key.raw != Key_LeftShift.raw && - mapped_key.raw != Key_RightShift.raw) + + if (!pressed_key_was_valid) { return mapped_key; + } - // if a key toggled off (and that must be one of the shifts at this point), - // send the parens too (if we were interrupted, we bailed out earlier). + // 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 paren = opening_paren; - if (bitRead(paren_needed_, 1)) - paren = closing_paren; - - handleKeyswitchEvent(mapped_key, row, col, IS_PRESSED | INJECTED); - handleKeyswitchEvent(paren, row, col, IS_PRESSED | INJECTED); + 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. + handleKeyswitchEvent(alternate_key, row, col, IS_PRESSED | INJECTED); hid::sendKeyboardReport(); - paren_needed_ = 0; + //Unflag the key so we don't try this again. + map[index].flagged = false; + map[index].start_time = 0; } return mapped_key; @@ -94,4 +176,4 @@ Key SpaceCadetShift::eventHandlerHook(Key mapped_key, byte row, byte col, uint8_ } -kaleidoscope::SpaceCadetShift SpaceCadetShift; +kaleidoscope::SpaceCadet SpaceCadet; diff --git a/src/Kaleidoscope/SpaceCadet.h b/src/Kaleidoscope/SpaceCadet.h index 626ef573..6fa9d1cf 100644 --- a/src/Kaleidoscope/SpaceCadet.h +++ b/src/Kaleidoscope/SpaceCadet.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- - * Kaleidoscope-SpaceCadet -- Space Cadet Shift - * Copyright (C) 2016, 2017 Gergely Nagy + * Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended + * Copyright (C) 2016, 2017 Gergely Nagy, 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 @@ -20,23 +20,49 @@ #include -namespace kaleidoscope { +#ifndef SPACECADET_MAP_END +#define SPACECADET_MAP_END (kaleidoscope::SpaceCadet::KeyBinding) { Key_NoKey, Key_NoKey, 0 } +#endif -class SpaceCadetShift : public KaleidoscopePlugin { +namespace kaleidoscope { +//Declaration for the method (implementing KaleidoscopePlugin) +class SpaceCadet : public KaleidoscopePlugin { public: - SpaceCadetShift(void); + //Internal Class + //Declarations for the modifier key mapping + class KeyBinding { + public: + //Empty constructor; set the vars separately + KeyBinding(void) {} + //Constructor with input and output + KeyBinding(Key input_, Key output_); + //Constructor with all three set + KeyBinding(Key input_, Key output_, uint16_t timeout_); + //The key that is pressed + Key input; + //the key that is sent + Key output; + //The timeout (default to global timeout) + uint16_t timeout = 0; + //The flag (set to 0) + bool flagged = false; + //the start time for this key press + uint32_t start_time = 0; + }; + //Empty constructor + SpaceCadet(void); + + //Methods void begin(void) final; - static uint16_t time_out; - static Key opening_paren, closing_paren; + //Publically accessible variables + static uint16_t time_out; // The global timeout in milliseconds + static SpaceCadet::KeyBinding * map; // The map of key bindings private: - static uint8_t paren_needed_; - static uint32_t start_time_; - static Key eventHandlerHook(Key mapped_key, byte row, byte col, uint8_t key_state); }; }; -extern kaleidoscope::SpaceCadetShift SpaceCadetShift; +extern kaleidoscope::SpaceCadet SpaceCadet;