diff --git a/plugins/Kaleidoscope-SpaceCadet/README.md b/plugins/Kaleidoscope-SpaceCadet/README.md index 9ebaeb74..d2ccdfe7 100644 --- a/plugins/Kaleidoscope-SpaceCadet/README.md +++ b/plugins/Kaleidoscope-SpaceCadet/README.md @@ -81,7 +81,11 @@ void setup() { ## Plugin methods -The plugin provides the `SpaceCadet` object, with the following methods: +The plugin provides two objects, `SpaceCadet` and `SpaceCadetConfig`. The latter +requires the first, and allows configuring some aspects of `SpaceCadet` through +[Focus][focus]. + +The `SpaceCadet` object provides the following methods: ### `.setMap(map)` @@ -110,7 +114,14 @@ The plugin provides the `SpaceCadet` object, with the following methods: > 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. +> Defaults to 200. + +### `.getTimeout()` + +> Returns the number of milliseconds SpaceCadet will wait before considering a +> key held in isolation as its secondary role. This returns the *global* +> setting, as set by `.setTimeout()`. If any key in the mapping set by +> `.setMap()` has a different timeout, that is not considered here. ### `.enable()` @@ -120,6 +131,13 @@ The plugin provides the `SpaceCadet` object, with the following methods: > > The default behavior is `enabled`. +### `.enableWithoutDelay()` + +> This method enables the SpaceCadet plugin in "no-delay" mode. In this mode, +> SpaceCadet immediately sends the primary (modifier) value of the SpaceCadet +> key when it is pressed. If it is then released before timing out, it sends the +> alternate "tap" value, replacing the modifier. + ### `.disable()` > This method disables the SpaceCadet behavior. This is useful for interfacing @@ -132,12 +150,11 @@ The plugin provides the `SpaceCadet` object, with the following methods: > is disabled. This is useful for interfacing with other plugins or macros, > especially where SpaceCadet functionality isn't always desired. -### `.enableWithoutDelay()` -> This method enables the SpaceCadet plugin in "no-delay" mode. In this mode, -> SpaceCadet immediately sends the primary (modifier) value of the SpaceCadet -> key when it is pressed. If it is then released before timing out, it sends the -> alternate "tap" value, replacing the modifier. +### `.activeWithoutDelay()` + +> This method returns `true` if SpaceCadet is enabled, and is in "no-delay" +> mode, as set by `.enableWithoutDelay()`. ### `Key_SpaceCadetEnable` @@ -151,10 +168,38 @@ The plugin provides the `SpaceCadet` object, with the following methods: > behavior. This is only triggered on initial press, and does not > trigger again if held down or when the key is released. +## Focus commands + +When using the `SpaceCadetConfig` plugin, the following Focus commands become +available: + +### `spacecadet.mode` + +> Without arguments, returns the mode SpaceCadet is currently in, as a number. +> When `SpaceCadet` is enabled in normal mode, this returns 0. When it is turned +> off, it returns 1. When it is active in no-delay mode, it returns 2. +> +> When an argument is supplied, it must be one of the above, and will set the +> SpaceCadet mode appropriately. Giving a numeric argument other than the +> allowed ones will disable SpaceCadet. + +### `spacecadet.timeout` + +> Without arguments, prints the global timeout used by SpaceCadet. +> +> When an argument is given, it sets the global timeout. + ## Dependencies * [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) +### Optional dependencies, if using the `SpaceCadetConfig` object + +* [Kaleidoscope-EEPROM-Settings](Kaleidoscope-EEPROM-Settings.md) +* [Kaleidoscope-FocusSerial][focus] + + [focus]: Kaleidoscope-FocusSerial.md + ## Further reading Starting from the [example][plugin:example] is the recommended way of getting diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index 4105e0ec..0bb0a7f9 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -102,7 +102,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { } // Do nothing if disabled, but keep the event tracker current. - if (mode_ == Mode::OFF) + if (settings_.mode == Mode::OFF) return EventHandlerResult::OK; if (!event_queue_.isEmpty()) { @@ -133,7 +133,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { // A SpaceCadet key has just toggled on. First, if we're in no-delay mode, // we need to send the event unchanged (with the primary `Key` value), // bypassing other `onKeyswitchEvent()` handlers. - if (mode_ == Mode::NO_DELAY) + if (settings_.mode == Mode::NO_DELAY) Runtime.handleKeyEvent(event); // Queue the press event and abort; this press event will be resolved // later. @@ -151,8 +151,8 @@ EventHandlerResult SpaceCadet::afterEachCycle() { if (event_queue_.isEmpty()) return EventHandlerResult::OK; - // Get timeout value for the pending key. - uint16_t pending_timeout = timeout_; + // Get timeout value for the pending key. + uint16_t pending_timeout = settings_.timeout; if (map_[pending_map_index_].timeout != 0) pending_timeout = map_[pending_map_index_].timeout; uint16_t start_time = event_queue_.timestamp(0); @@ -187,7 +187,7 @@ void SpaceCadet::flushEvent(bool is_tap) { if (is_tap && pending_map_index_ >= 0) { // If we're in no-delay mode, we should first send the release of the // modifier key as a courtesy before sending the tap event. - if (mode_ == Mode::NO_DELAY) { + if (settings_.mode == Mode::NO_DELAY) { Runtime.handleKeyEvent(KeyEvent(event.addr, WAS_PRESSED)); } event.key = map_[pending_map_index_].output; diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 45958dea..60e49439 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -1,7 +1,7 @@ /* -*- mode: c++ -*- * Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc, Ben Gemperline - * Copyright (C) 2019-2021 Keyboard.io, Inc + * Copyright (C) 2019-2022 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 @@ -39,7 +39,11 @@ constexpr Key Key_SpaceCadetDisable = Key(kaleidoscope::ranges::SC_LAST); namespace kaleidoscope { namespace plugin { +class SpaceCadetConfig; + class SpaceCadet : public kaleidoscope::Plugin { + friend class SpaceCadetConfig; + public: // Internal Class // Declarations for the modifier key mapping @@ -67,20 +71,27 @@ class SpaceCadet : public kaleidoscope::Plugin { // Methods void enable() { - mode_ = Mode::ON; + settings_.mode = Mode::ON; } void disable() { - mode_ = Mode::OFF; + settings_.mode = Mode::OFF; } void enableWithoutDelay() { - mode_ = Mode::NO_DELAY; + settings_.mode = Mode::NO_DELAY; } bool active() { - return (mode_ == Mode::ON || mode_ == Mode::NO_DELAY); + return (settings_.mode == Mode::ON || settings_.mode == Mode::NO_DELAY); + } + bool activeWithoutDelay() { + return settings_.mode == Mode::NO_DELAY; } void setTimeout(uint16_t timeout) { - timeout_ = timeout; + settings_.timeout = timeout; + } + + uint16_t getTimeout() { + return settings_.timeout; } void setMap(KeyBinding *bindings) { @@ -91,17 +102,20 @@ class SpaceCadet : public kaleidoscope::Plugin { EventHandlerResult onKeyswitchEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); - private: + protected: enum Mode : uint8_t { ON, OFF, NO_DELAY, }; - uint8_t mode_; + struct { + Mode mode; - // Global timeout in milliseconds - uint16_t timeout_ = 200; + // Global timeout in milliseconds + uint16_t timeout = 200; + } settings_; + private: // The map of keybindings KeyBinding *map_ = nullptr; @@ -125,7 +139,23 @@ class SpaceCadet : public kaleidoscope::Plugin { void flushQueue(); }; +class SpaceCadetConfig : public kaleidoscope::Plugin { + public: + EventHandlerResult onSetup(); + EventHandlerResult onFocusEvent(const char *command); + + void disableSpaceCadetIfUnconfigured(); + + private: + struct Settings { + SpaceCadet::Mode mode; + uint16_t timeout; + }; + uint16_t settings_base_; +}; + } // namespace plugin } // namespace kaleidoscope extern kaleidoscope::plugin::SpaceCadet SpaceCadet; +extern kaleidoscope::plugin::SpaceCadetConfig SpaceCadetConfig; diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadetConfig.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadetConfig.cpp new file mode 100644 index 00000000..ca05b454 --- /dev/null +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadetConfig.cpp @@ -0,0 +1,103 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended + * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc, Ben Gemperline + * Copyright (C) 2019-2021 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 "SpaceCadet.h" +#include "kaleidoscope/plugin/SpaceCadet.h" + +#include // for F, __FlashStringHelper +#include // for Focus, FocusSerial +#include // for EEPROMSettings +#include // for uint16_t, int8_t, uint8_t + +#include "kaleidoscope/Runtime.h" // for Runtime, Runtime_ +#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult, EventHandlerResult::OK + +namespace kaleidoscope { +namespace plugin { + +EventHandlerResult SpaceCadetConfig::onSetup() { + settings_base_ = ::EEPROMSettings.requestSlice(sizeof(SpaceCadet::settings_)); + + // If our slice is uninitialized, then return early. + if (Runtime.storage().isSliceUninitialized(settings_base_, sizeof(SpaceCadet::settings_))) + return EventHandlerResult::OK; + + Runtime.storage().get(settings_base_, ::SpaceCadet.settings_); + + return EventHandlerResult::OK; +} + +void SpaceCadetConfig::disableSpaceCadetIfUnconfigured() { + if (Runtime.storage().isSliceUninitialized(settings_base_, sizeof(SpaceCadet::settings_))) + ::SpaceCadet.disable(); +} + +EventHandlerResult SpaceCadetConfig::onFocusEvent(const char *command) { + const char *cmd_mode = PSTR("spacecadet.mode"); + const char *cmd_timeout = PSTR("spacecadet.timeout"); + + // Note: These two calls are intentionally separate, because we want the side + // effects. If we were to use `||`, only one of them would run. + bool help_handled = ::Focus.handleHelp(command, cmd_mode); + help_handled |= ::Focus.handleHelp(command, cmd_timeout); + + if (help_handled) + return EventHandlerResult::OK; + + if (strcmp_P(command, cmd_mode) == 0) { + if (::Focus.isEOL()) { + ::Focus.send(::SpaceCadet.settings_.mode); + } else { + uint8_t mode; + ::Focus.read(mode); + switch (mode) { + case SpaceCadet::Mode::ON: + ::SpaceCadet.settings_.mode = SpaceCadet::Mode::ON; + break; + case SpaceCadet::Mode::NO_DELAY: + ::SpaceCadet.settings_.mode = SpaceCadet::Mode::NO_DELAY; + break; + case SpaceCadet::Mode::OFF: + default: + ::SpaceCadet.settings_.mode = SpaceCadet::Mode::OFF; + break; + } + + Runtime.storage().put(settings_base_, ::SpaceCadet.settings_); + Runtime.storage().commit(); + } + } else if (strcmp_P(command, cmd_timeout) == 0) { + if (::Focus.isEOL()) { + ::Focus.send(::SpaceCadet.settings_.timeout); + } else { + ::Focus.read(::SpaceCadet.settings_.timeout); + + Runtime.storage().put(settings_base_, ::SpaceCadet.settings_); + Runtime.storage().commit(); + } + } else { + return EventHandlerResult::OK; + } + + return EventHandlerResult::EVENT_CONSUMED; +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::SpaceCadetConfig SpaceCadetConfig;