Merge pull request #1234 from keyboardio/f/spacecadet-config

SpaceCadet: Implement a SpaceCadetConfig plugin
pull/1039/merge
Jesse Vincent 2 years ago committed by GitHub
commit 62a0fae04d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -42,17 +42,6 @@ SpaceCadet::KeyBinding::KeyBinding(Key input, Key output)
SpaceCadet::KeyBinding::KeyBinding(Key input, Key output, uint16_t timeout)
: input(input), output(output), timeout(timeout) {}
// =============================================================================
// Space Cadet class variables
// -----------------------------------------------------------------------------
// Plugin configuration variables
#ifndef NDEPRECATED
SpaceCadet::KeyBinding *SpaceCadet::map;
uint16_t SpaceCadet::time_out = 200;
#endif
// =============================================================================
// SpaceCadet functions
@ -113,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()) {
@ -144,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.
@ -162,17 +151,10 @@ EventHandlerResult SpaceCadet::afterEachCycle() {
if (event_queue_.isEmpty())
return EventHandlerResult::OK;
// Get timeout value for the pending key.
#ifndef NDEPRECATED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
uint16_t pending_timeout = time_out;
#pragma GCC diagnostic pop
#else
uint16_t pending_timeout = timeout_;
#endif
if (map[pending_map_index_].timeout != 0)
pending_timeout = map[pending_map_index_].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);
if (Runtime.hasTimeExpired(start_time, pending_timeout)) {
@ -186,8 +168,8 @@ EventHandlerResult SpaceCadet::afterEachCycle() {
// Private helper function(s)
int8_t SpaceCadet::getSpaceCadetKeyIndex(Key key) const {
for (uint8_t i = 0; !map[i].isEmpty(); ++i) {
if (map[i].input == key) {
for (uint8_t i = 0; !map_[i].isEmpty(); ++i) {
if (map_[i].input == key) {
return i;
}
}
@ -205,10 +187,10 @@ 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;
event.key = map_[pending_map_index_].output;
}
event_queue_.shift();
Runtime.handleKeyswitchEvent(event);

@ -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
@ -27,15 +27,6 @@
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/key_defs.h" // for Key, Key_NoKey
#include "kaleidoscope/plugin.h" // for Plugin
// -----------------------------------------------------------------------------
// Deprecation warning messages
#include "kaleidoscope_internal/deprecations.h" // for DEPRECATED
#define _DEPRECATED_MESSAGE_SPACECADET_TIME_OUT \
"The `SpaceCadet.time_out` variable is deprecated. Please use the\n" \
"`SpaceCadet.setTimeout()` function instead.\n" \
"This variable will be removed after 2022-09-01."
// -----------------------------------------------------------------------------
#ifndef SPACECADET_MAP_END
#define SPACECADET_MAP_END \
@ -48,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
@ -76,61 +71,53 @@ 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;
}
#ifndef NDEPRECATED
// Publically accessible variables
DEPRECATED(SPACECADET_TIME_OUT)
static uint16_t time_out; // The global timeout in milliseconds
static SpaceCadet::KeyBinding *map; // The map of key bindings
#endif
void setTimeout(uint16_t timeout) {
#ifndef NDEPRECATED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
time_out = timeout;
#pragma GCC diagnostic pop
#else
timeout_ = timeout;
#endif
settings_.timeout = timeout;
}
uint16_t getTimeout() {
return settings_.timeout;
}
void setMap(KeyBinding *bindings) {
map = bindings;
map_ = bindings;
}
EventHandlerResult onNameQuery();
EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult afterEachCycle();
private:
protected:
enum Mode : uint8_t {
ON,
OFF,
NO_DELAY,
};
uint8_t mode_;
struct {
Mode mode;
#ifdef NDEPRECATED
// 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;
// When DEPRECATED public `map[]` variable is removed, this variable name
// should be given a trailing underscore to conform to code style guide.
#endif
KeyBinding *map_ = nullptr;
KeyEventTracker event_tracker_;
@ -152,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;

@ -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;

@ -59,8 +59,8 @@ void setup() {
SPACECADET_MAP_END
};
//Set the map.
SpaceCadet.map = spacecadetmap;
SpaceCadet.time_out = 20;
SpaceCadet.setMap(spacecadetmap);
SpaceCadet.setTimeout(20);
}
void loop() {

@ -57,8 +57,8 @@ void setup() {
SPACECADET_MAP_END
};
//Set the map.
SpaceCadet.map = spacecadetmap;
SpaceCadet.time_out = 20;
SpaceCadet.setMap(spacecadetmap);
SpaceCadet.setTimeout(20);
SpaceCadet.enableWithoutDelay();
}

Loading…
Cancel
Save