SpaceCadet: Implement a SpaceCadetConfig plugin

The new plugin enables configuring some aspects of SpaceCadet through Focus: the
current mode, and the global timeout. This is makes it possible to ship firmware
with SpaceCadet included, disabled by default, but still allow one to enable it
without having to map and tap the enable key.

The settings are also persisted into storage.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/1234/head
Gergely Nagy 2 years ago
parent 7f4090f126
commit cd82afd50a
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -81,7 +81,11 @@ void setup() {
## Plugin methods ## 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)` ### `.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 > 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. > 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()` ### `.enable()`
@ -120,6 +131,13 @@ The plugin provides the `SpaceCadet` object, with the following methods:
> >
> The default behavior is `enabled`. > 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()` ### `.disable()`
> This method disables the SpaceCadet behavior. This is useful for interfacing > 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, > is disabled. This is useful for interfacing with other plugins or macros,
> especially where SpaceCadet functionality isn't always desired. > especially where SpaceCadet functionality isn't always desired.
### `.enableWithoutDelay()`
> This method enables the SpaceCadet plugin in "no-delay" mode. In this mode, ### `.activeWithoutDelay()`
> 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 > This method returns `true` if SpaceCadet is enabled, and is in "no-delay"
> alternate "tap" value, replacing the modifier. > mode, as set by `.enableWithoutDelay()`.
### `Key_SpaceCadetEnable` ### `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 > behavior. This is only triggered on initial press, and does not
> trigger again if held down or when the key is released. > 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 ## Dependencies
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md) * [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 ## 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

@ -102,7 +102,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
} }
// Do nothing if disabled, but keep the event tracker current. // Do nothing if disabled, but keep the event tracker current.
if (mode_ == Mode::OFF) if (settings_.mode == Mode::OFF)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (!event_queue_.isEmpty()) { 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, // 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), // we need to send the event unchanged (with the primary `Key` value),
// bypassing other `onKeyswitchEvent()` handlers. // bypassing other `onKeyswitchEvent()` handlers.
if (mode_ == Mode::NO_DELAY) if (settings_.mode == Mode::NO_DELAY)
Runtime.handleKeyEvent(event); Runtime.handleKeyEvent(event);
// Queue the press event and abort; this press event will be resolved // Queue the press event and abort; this press event will be resolved
// later. // later.
@ -151,8 +151,8 @@ EventHandlerResult SpaceCadet::afterEachCycle() {
if (event_queue_.isEmpty()) if (event_queue_.isEmpty())
return EventHandlerResult::OK; return EventHandlerResult::OK;
// Get timeout value for the pending key. // Get timeout value for the pending key.
uint16_t pending_timeout = timeout_; uint16_t pending_timeout = settings_.timeout;
if (map_[pending_map_index_].timeout != 0) if (map_[pending_map_index_].timeout != 0)
pending_timeout = map_[pending_map_index_].timeout; pending_timeout = map_[pending_map_index_].timeout;
uint16_t start_time = event_queue_.timestamp(0); 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 (is_tap && pending_map_index_ >= 0) {
// If we're in no-delay mode, we should first send the release of the // 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. // 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)); Runtime.handleKeyEvent(KeyEvent(event.addr, WAS_PRESSED));
} }
event.key = map_[pending_map_index_].output; event.key = map_[pending_map_index_].output;

@ -1,7 +1,7 @@
/* -*- mode: c++ -*- /* -*- mode: c++ -*-
* Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended * Kaleidoscope-SpaceCadet -- Space Cadet Shift Extended
* Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc, Ben Gemperline * 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 * 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 * 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 kaleidoscope {
namespace plugin { namespace plugin {
class SpaceCadetConfig;
class SpaceCadet : public kaleidoscope::Plugin { class SpaceCadet : public kaleidoscope::Plugin {
friend class SpaceCadetConfig;
public: public:
// Internal Class // Internal Class
// Declarations for the modifier key mapping // Declarations for the modifier key mapping
@ -67,20 +71,27 @@ class SpaceCadet : public kaleidoscope::Plugin {
// Methods // Methods
void enable() { void enable() {
mode_ = Mode::ON; settings_.mode = Mode::ON;
} }
void disable() { void disable() {
mode_ = Mode::OFF; settings_.mode = Mode::OFF;
} }
void enableWithoutDelay() { void enableWithoutDelay() {
mode_ = Mode::NO_DELAY; settings_.mode = Mode::NO_DELAY;
} }
bool active() { 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) { void setTimeout(uint16_t timeout) {
timeout_ = timeout; settings_.timeout = timeout;
}
uint16_t getTimeout() {
return settings_.timeout;
} }
void setMap(KeyBinding *bindings) { void setMap(KeyBinding *bindings) {
@ -91,17 +102,20 @@ class SpaceCadet : public kaleidoscope::Plugin {
EventHandlerResult onKeyswitchEvent(KeyEvent &event); EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult afterEachCycle(); EventHandlerResult afterEachCycle();
private: protected:
enum Mode : uint8_t { enum Mode : uint8_t {
ON, ON,
OFF, OFF,
NO_DELAY, NO_DELAY,
}; };
uint8_t mode_; struct {
Mode mode;
// Global timeout in milliseconds // Global timeout in milliseconds
uint16_t timeout_ = 200; uint16_t timeout = 200;
} settings_;
private:
// The map of keybindings // The map of keybindings
KeyBinding *map_ = nullptr; KeyBinding *map_ = nullptr;
@ -125,7 +139,23 @@ class SpaceCadet : public kaleidoscope::Plugin {
void flushQueue(); 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 plugin
} // namespace kaleidoscope } // namespace kaleidoscope
extern kaleidoscope::plugin::SpaceCadet SpaceCadet; extern kaleidoscope::plugin::SpaceCadet SpaceCadet;
extern kaleidoscope::plugin::SpaceCadetConfig SpaceCadetConfig;

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "SpaceCadet.h"
#include "kaleidoscope/plugin/SpaceCadet.h"
#include <Arduino.h> // for F, __FlashStringHelper
#include <Kaleidoscope-FocusSerial.h> // for Focus, FocusSerial
#include <Kaleidoscope-EEPROM-Settings.h> // for EEPROMSettings
#include <stdint.h> // 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;
Loading…
Cancel
Save