diff --git a/doc/plugin/SpaceCadet.md b/doc/plugin/SpaceCadet.md new file mode 100644 index 00000000..545108dd --- /dev/null +++ b/doc/plugin/SpaceCadet.md @@ -0,0 +1,164 @@ +# Kaleidoscope-SpaceCadet + +[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. + +What it does, is that it turns your left and right `Shift` keys into parens if +you tap and release them, without pressing any other key while holding them. +Therefore, to input, say, `(print foo)`, you don't need to press `Shift`, hold +it, and press `9` to get a `(`, you simply press and release `Shift`, and +continue writing. You use it as if you had a dedicated key for parens! + +But if you wish to write capital letters, you hold it, as usual, and you will +not see any parens when you release it. You can also hold it for a longer time, +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 + +Using the plugin with its defaults is as simple as including the header, and +enabling the plugin: + +```c++ +#include +#include + +KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet); + +void setup() { + Kaleidoscope.setup(); +} +``` + +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 + +KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet); + +void setup() { + Kaleidoscope.setup(); + + //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::plugin::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 + +The plugin provides the `SpaceCadet` object, with the following methods and +properties: + +### `.map` + +> Set the key map. This takes an array of +> `kaleidoscope::plugin::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 +> +> If not explicitly set, defaults to mapping left `shift` to `(` and right `shift` +> to `)`. + +### `kaleidoscope::plugin::SpaceCadet::KeyBinding` + +> 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. 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. + +### `.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. + +## Dependencies + +* [Kaleidoscope-Ranges](Ranges.md) + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + + [plugin:example]: ../../examples/SpaceCadet/SpaceCadet.ino + +## Upgrading + +Previous versions of `SpaceCadet` used +`kaleidoscope::SpaceCadet::KeyBinding` as a type for defining keybindings. In newer versions, this is +`kaleidoscope::plugin::SpaceCadet::KeyBinding`. The old name still works, +but will be removed by 2019-01-14. diff --git a/examples/SpaceCadet/SpaceCadet.ino b/examples/SpaceCadet/SpaceCadet.ino new file mode 100644 index 00000000..bd0d7a34 --- /dev/null +++ b/examples/SpaceCadet/SpaceCadet.ino @@ -0,0 +1,67 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-SpaceCadet -- Space Cadet Shift + * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * + * 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 . + */ + +#include +#include + +// *INDENT-OFF* +const Key keymaps[][ROWS][COLS] PROGMEM = { + [0] = KEYMAP_STACKED + ( + 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, + + Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, + 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, + + Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, + Key_skip), +}; +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet); + +void setup() { + Kaleidoscope.setup(); + + //Set the SpaceCadet map + //Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS} + //Note: must end with the SPACECADET_MAP_END delimiter + static kaleidoscope::plugin::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; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/src/Kaleidoscope-SpaceCadet.h b/src/Kaleidoscope-SpaceCadet.h new file mode 100644 index 00000000..197bdc37 --- /dev/null +++ b/src/Kaleidoscope-SpaceCadet.h @@ -0,0 +1,20 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-SpaceCadet -- Space Cadet Shift + * Copyright (C) 2016, 2017 Keyboard.io, Inc + * + * 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 . + */ + +#pragma once + +#include diff --git a/src/kaleidoscope/plugin/SpaceCadet.cpp b/src/kaleidoscope/plugin/SpaceCadet.cpp new file mode 100644 index 00000000..f248fd3a --- /dev/null +++ b/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -0,0 +1,250 @@ +/* -*- 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 . + */ + +#include +#include + +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; diff --git a/src/kaleidoscope/plugin/SpaceCadet.h b/src/kaleidoscope/plugin/SpaceCadet.h new file mode 100644 index 00000000..a81ed954 --- /dev/null +++ b/src/kaleidoscope/plugin/SpaceCadet.h @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include +#include + +#ifndef SPACECADET_MAP_END +#define SPACECADET_MAP_END (kaleidoscope::SpaceCadet::KeyBinding) { Key_NoKey, Key_NoKey, 0 } +#endif + +#define Key_SpaceCadetEnable (Key) { .raw = kaleidoscope::ranges::SC_FIRST } +#define Key_SpaceCadetDisable (Key) { .raw = kaleidoscope::ranges::SC_LAST } + +namespace kaleidoscope { +namespace plugin { + +class SpaceCadet : public kaleidoscope::Plugin { + public: + //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; + }; + + SpaceCadet(void); + + //Methods + 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 + static SpaceCadet::KeyBinding * map; // The map of key bindings + + EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); + + private: + static bool disabled; +}; +} + +// Backwards compatibility +typedef plugin::SpaceCadet SpaceCadet; + +} + +extern kaleidoscope::plugin::SpaceCadet SpaceCadet;