Compare commits

...

20 Commits

Author SHA1 Message Date
Jesse Vincent a98074ae47
Move the live composite keymap cache back into the Layer object
4 years ago
Michael Richters 9165475da1
Adapt WinKeyToggle to active keys cache
4 years ago
Michael Richters 03a8872a95
Adapt Redial to active keys cache
4 years ago
Michael Richters e5ba44a6da
Adapt Escape-OneShot to active keys cache
4 years ago
Michael Richters 23d8c6577e
Adapt ShapeShifter to active keys cache
4 years ago
Michael Richters 2e4110d171
Adapt TopsyTurvy to active keys cache
4 years ago
Michael Richters b06afda760
Adapt TapDance to active keys cache
4 years ago
Michael Richters b13aaba529
Adapt Qukeys to active keys cache
4 years ago
Michael Richters 77546706a3
Update documentation for active keys cache changes
4 years ago
Michael Richters 5434f82a0f
Remove unused `EPHEMERAL` keyswitch state bit
4 years ago
Michael Richters 60d1e2faaf
Deprecate `Layer.eventHandler()`
4 years ago
Michael Richters 38c18cdcc7
Convert keymap cache to a representation of active keyboard state
4 years ago
Michael Richters 3b1500a526
Add `EventHandlerResult::ABORT`
4 years ago
Michael Richters 62d0ec70ea
Abort hook functions on any result other than `OK`
4 years ago
Michael Richters c9d6a04335
Add testcase for active keys cache using Macros
4 years ago
Michael Richters b0c9c18004
Add testcase for the active keys cache
4 years ago
Michael Richters 4fdff6c9ec
Add testcase for TapDance issue #980
4 years ago
Michael Richters dc4cdc28e0
Add testcase for TapDance issue #922
4 years ago
Michael Richters cc9d531f8e
Add testcase for TapDance issue #806
4 years ago
Michael Richters 7efc9743ef
Add to basic TapDance testcases
4 years ago

@ -33,6 +33,35 @@ any API we've included in a release. Typically, this means that any code that us
## New features
### Keymap cache changes
The keymap cache (`Layer_::live_composite_keymap_[]`) has been replaced by an "active keys" cache (`Runtime_::active_keys_[]`). The top-level function that handles keyswitch events has been updated to make the new cache a representation of the current state of the keyboard, with corresponding `Key` values for any keys that are active (physically held or activated by a plugin).
#### For end-users
There should be no user-visible changes for anyone who simply uses core plugins. A few functions have been deprecated (`Layer.eventHandler()` & `Layer.updateLiveCompositeKeymap()`), but there are straightforward replacements for both.
#### For developers
The major changes are to the `handleKeyswitchEvent()` function, which has been reorganized in order to update the new active keys cache with correct values at the appropriate times. In addition to that, two new facilities are available:
##### `EventHandlerResult::ABORT`
This is a new return value available to plugin event handlers, which is similar to `EVENT_CONSUMED` in that it causes the calling hook function to return early (stopping any subsequent handlers from seeing the event), but is treated differently by `handleKeyswitchEvent()`. If a handler returns `EVENT_CONSUMED`, the active keys cache will still be updated by `handleKeyswitchEvent()`, but if it returns `ABORT`, it will not. In both cases, no further event processing will be done by the built-in event handler.
##### `Runtime.activeKey(key_addr)`
This is a new function that can be used to check the value of an entry in the active keys cache. For example, it could be used in a range-based `for` loop to check for values of interest:
```c++
for (KeyAddr key_addr : KeyAddr::all()) {
Key key = Runtime.activeKey(key_addr);
if (key == Key_LeftShift || key == Key_RightShift) {
// do something special...
}
}
```
### New build system
In this release, we replace kaleidoscope-builder with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation.
@ -570,6 +599,14 @@ The following headers and names have changed:
- [Syster](plugins/Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`.
- [TapDance](plugins/TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`.
### Live Composite Keymap Cache
The live composite keymap, which contained a lazily-updated version of the current keymap, has been replaced. The `Layer.updateLiveCompositeKeymap()` functions have been deprecated, and depending on the purpose of the caller, it might be appropriate to use `Runtime.updateActiveKey()` instead.
When `handleKeyswitchEvent()` is looking up a `Key` value for an event, it first checks the value in the active keys cache before calling `Layer.lookup()` to get the value from the keymap. In the vast majority of cases, it won't be necessary to call `Runtime.updateActiveKey()` manually, however, because simply changing the value of the `Key` parameter of an `onKeyswitchEvent()` handler will have the same effect.
Second, the `Layer.eventHandler()` function has been deprecated. There wasn't much need for this to be available to plugins, and it's possible to call `Layer.handleKeymapKeyswitchEvent()` directly instead.
# Removed APIs
### Removed on 2020-10-10

@ -42,6 +42,7 @@ Runtime_::setup(void) {
device().setup();
Layer.setup();
}

@ -18,9 +18,33 @@
namespace kaleidoscope {
// This is the set of return values for event handlers. Event handlers for
// plugins are called in sequence by the corresponding hook function, in plugin
// initialization order. The interpretation of these return values can vary
// based on the needs of the hook function, but should be as follows:
//
// - OK: Continue processing the event. The calling hook function should
// continue calling next event handler in the sequence. If all event
// handlers return `OK`, finish processing the event.
//
// - EVENT_CONSUMED: Stop processing event handlers. The calling hook function
// should not call any further handlers, but may continue to take some
// actions to finish processing the event. This should be used to indicate
// that the event has been successfully handled.
//
// - ABORT: Ignore the event. The calling hook function should not call any
// further handlers, and should treat the event as if it didn't
// happen. This should be used by plugin handlers that need to either
// suppress an event or queue the event in order to delay it.
//
// - ERROR: Undefined error. The calling hook function should not call any
// further handlers. There is currently no specification for what should
// happen if this is returned.
enum class EventHandlerResult {
OK,
EVENT_CONSUMED,
ABORT,
ERROR,
};

@ -40,7 +40,7 @@ static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) {
} else if (mappedKey.getFlags() & IS_INTERNAL) {
return false;
} else if (mappedKey.getFlags() & SWITCH_TO_KEYMAP) {
// Should not happen, handled elsewhere.
Layer.handleKeymapKeyswitchEvent(mappedKey, keyState);
}
return true;
@ -86,16 +86,6 @@ void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) {
*/
if (key_addr.isValid()) {
/* If a key had an on event, we update the live composite keymap. See
* layers.h for an explanation about the different caches we have. */
if (keyToggledOn(keyState)) {
if (mappedKey == Key_NoKey || keyState & EPHEMERAL) {
Layer.updateLiveCompositeKeymap(key_addr);
} else {
Layer.updateLiveCompositeKeymap(key_addr, mappedKey);
}
}
/* If the key we are dealing with is masked, ignore it until it is released.
* When releasing it, clear the mask, so future key events can be handled
* appropriately.
@ -111,23 +101,43 @@ void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) {
}
}
/* Convert key_addr to the correct mappedKey
* The condition here means that if mappedKey and key_addr are both valid,
* the mappedKey wins - we don't re-look-up the mappedKey
*/
// If the caller did not supply a `Key` value, get it from the keymap
// cache. If that value is transparent, look it up from the active layer for
// that key address.
if (mappedKey == Key_NoKey) {
// Note: If the next line returns `Key_NoKey`, that will effectively mask
// the key.
mappedKey = Layer.lookup(key_addr);
}
} // key_addr valid
// Keypresses with out-of-bounds key_addr start here in the processing chain
auto result = kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState);
if (kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState) != kaleidoscope::EventHandlerResult::OK)
// If any plugin returns `ABORT`, stop here and don't update the active keys
// cache entry.
if (result == kaleidoscope::EventHandlerResult::ABORT)
return;
mappedKey = Layer.eventHandler(mappedKey, key_addr, keyState);
if (mappedKey == Key_NoKey)
// Update the active keys cache if the key toggled on or off.
if (key_addr.isValid()) {
if (keyToggledOn(keyState)) {
Layer.updateLiveKeymap(key_addr, mappedKey);
} else if (keyToggledOff(keyState)) {
Layer.updateLiveKeymap(key_addr, Key_Transparent);
}
}
// Only continue if all plugin handlers returned `OK`.
if (result != kaleidoscope::EventHandlerResult::OK)
return;
// If the key has been masked (i.e. `Key_NoKey`), or it's a plugin-specific
// key (`RESERVED`), don't bother continuing.
if (mappedKey == Key_NoKey || (mappedKey.getFlags() & RESERVED) != 0)
return;
// Handle all built-in key types.
handleKeyswitchEventDefault(mappedKey, key_addr, keyState);
}

@ -20,7 +20,6 @@
#include <Arduino.h>
#define INJECTED B10000000
#define EPHEMERAL B01000000
#define IS_PRESSED B00000010
#define WAS_PRESSED B00000001

@ -42,21 +42,27 @@ uint32_t Layer_::layer_state_;
uint8_t Layer_::active_layer_count_ = 1;
int8_t Layer_::active_layers_[31];
Key Layer_::live_composite_keymap_[Runtime.device().numKeys()];
Key Layer_::live_keymap_[Runtime.device().numKeys()];
uint8_t Layer_::active_layer_keymap_[Runtime.device().numKeys()];
Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM;
void Layer_::setup() {
// Explicitly set layer 0's state to 1
bitSet(layer_state_, 0);
// Update the keymap cache, so we start with a non-empty state.
// Update the active layer cache (every entry will be `0` to start)
Layer.updateActiveLayers();
for (auto key_addr : KeyAddr::all()) {
Layer.updateLiveCompositeKeymap(key_addr);
}
Layer.setupLiveKeymap();
}
void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) {
if (keymapEntry.getKeyCode() >= LAYER_MOVE_OFFSET) {
if (keyToggledOn(keyState)) {
@ -113,21 +119,17 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) {
}
Key Layer_::eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState) {
if (mappedKey.getFlags() != (SYNTHETIC | SWITCH_TO_KEYMAP))
return mappedKey;
handleKeymapKeyswitchEvent(mappedKey, keyState);
return Key_NoKey;
if (mappedKey.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))
handleKeymapKeyswitchEvent(mappedKey, keyState);
return mappedKey;
}
Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) {
return keyFromKeymap(layer, key_addr);
}
void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) {
int8_t layer = active_layer_keymap_[key_addr.toInt()];
live_composite_keymap_[key_addr.toInt()] = (*getKey)(layer, key_addr);
}
// Deprecated
void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) { }
void Layer_::updateActiveLayers(void) {
memset(active_layer_keymap_, 0, Runtime.device().numKeys());
@ -233,6 +235,25 @@ void Layer_::forEachActiveLayer(forEachHandler h) {
}
}
void Layer_::setupLiveKeymap() {
// Clear the active keys array (all keys idle at start)
for (auto key_addr : KeyAddr::all()) {
Layer.updateLiveKeymap(key_addr, Key_Transparent);
}
}
Key Layer_::lookupOnLiveKeymap(KeyAddr key_addr) {
return live_keymap_[key_addr.toInt()];
}
void Layer_::updateLiveKeymap(KeyAddr key_addr, Key key) {
live_keymap_[key_addr.toInt()] = key;
}
}
kaleidoscope::Layer_ Layer;

@ -52,7 +52,7 @@ class Layer_ {
Layer_() {}
void setup();
/* There are two lookup functions, because we have two caches, and different
* parts of the firmware will want to use either this or that (or perhaps
* both, in rare cases).
@ -83,12 +83,23 @@ class Layer_ {
* `lookupOnActiveLayer`.
*/
static Key lookup(KeyAddr key_addr) {
return live_composite_keymap_[key_addr.toInt()];
// First check the active keys array
Key key = lookupOnLiveKeymap(key_addr);
// If that entry is clear, look up the entry from the active keymap layers
if (key == Key_Transparent) {
key = lookupOnActiveLayer(key_addr);
}
return key;
}
static Key lookupOnLiveKeymap(KeyAddr key_addr);
static Key lookupOnActiveLayer(KeyAddr key_addr) {
uint8_t layer = active_layer_keymap_[key_addr.toInt()];
return (*getKey)(layer, key_addr);
}
static uint8_t lookupActiveLayer(KeyAddr key_addr) {
return active_layer_keymap_[key_addr.toInt()];
}
@ -114,6 +125,9 @@ class Layer_ {
return layer_state_;
}
static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState);
DEPRECATED(LAYER_EVENTHANDLER)
static Key eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState);
typedef Key(*GetKeyFunction)(uint8_t layer, KeyAddr key_addr);
@ -121,13 +135,21 @@ class Layer_ {
static Key getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr);
DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP)
static void updateLiveCompositeKeymap(KeyAddr key_addr, Key mappedKey) {
live_composite_keymap_[key_addr.toInt()] = mappedKey;
updateLiveKeymap(key_addr, mappedKey);
}
DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP)
static void updateLiveCompositeKeymap(KeyAddr key_addr);
static void updateActiveLayers(void);
static void updateLiveKeymap(KeyAddr key_addr, Key key);
private:
static void setupLiveKeymap();
using forEachHandler = void(*)(uint8_t index, uint8_t layer);
public:
@ -137,10 +159,10 @@ class Layer_ {
static uint32_t layer_state_;
static uint8_t active_layer_count_;
static int8_t active_layers_[31];
static Key live_composite_keymap_[kaleidoscope_internal::device.numKeys()];
static Key live_keymap_[kaleidoscope_internal::device.numKeys()];
static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()];
static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState);
};
}

@ -23,21 +23,19 @@
namespace kaleidoscope {
namespace plugin {
bool EscapeOneShot::did_escape_;
EventHandlerResult EscapeOneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) {
if (mapped_key != Key_Escape || (keyState & INJECTED))
return EventHandlerResult::OK;
if (did_escape_)
mapped_key = Key_NoKey;
did_escape_ = !keyToggledOff(keyState);
if (!keyToggledOn(keyState))
return EventHandlerResult::OK;
if ((!::OneShot.isActive() || ::OneShot.isPressed()) && !::OneShot.isSticky()) {
return EventHandlerResult::OK;
}
::OneShot.cancel(true);
mapped_key = Key_NoKey;
return EventHandlerResult::EVENT_CONSUMED;
}

@ -27,8 +27,6 @@ class EscapeOneShot : public kaleidoscope::Plugin {
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState);
private:
static bool did_escape_;
};
}
}

@ -67,7 +67,7 @@ EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_sta
}
// Any event that gets added to the queue gets re-processed later, so we
// need to abort processing now.
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::ABORT;
}
// The key is being held. We need to determine if we should block it because
@ -84,7 +84,7 @@ EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_sta
}
// Otherwise, the first matching event was a key press, so we need to
// suppress it for now.
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::ABORT;
}
}

@ -105,6 +105,19 @@ class KeyAddrEventQueue {
release_event_bits_ >>= 1;
}
void shift(uint8_t n) {
if (n >= length_) {
clear();
return;
}
length_ -= n;
for (uint8_t i{0}; i < length_; ++i) {
addrs_[i] = addrs_[i + n];
timestamps_[i] = timestamps_[i + n];
}
release_event_bits_ >>= n;
}
// Empty the queue entirely.
void clear() {
length_ = 0;

@ -21,27 +21,16 @@
namespace kaleidoscope {
namespace plugin {
Key Redial::key_to_redial_;
Key Redial::last_key_;
bool Redial::redial_held_ = false;
EventHandlerResult Redial::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) {
if (mapped_key == Key_Redial) {
if (keyToggledOff(key_state))
key_to_redial_ = last_key_;
mapped_key = key_to_redial_;
redial_held_ = keyIsPressed(key_state);
return EventHandlerResult::OK;
if (keyToggledOn(key_state)) {
if (mapped_key == Key_Redial) {
mapped_key = last_key_;
} else if (shouldRemember(mapped_key)) {
last_key_ = mapped_key;
}
}
if (keyToggledOn(key_state) && shouldRemember(mapped_key)) {
last_key_ = mapped_key;
if (!redial_held_)
key_to_redial_ = mapped_key;
}
return EventHandlerResult::OK;
}

@ -34,9 +34,7 @@ class Redial : public kaleidoscope::Plugin {
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state);
private:
static Key key_to_redial_;
static Key last_key_;
static bool redial_held_;
};
}

@ -16,25 +16,21 @@
*/
#include <Kaleidoscope-ShapeShifter.h>
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/layers.h"
namespace kaleidoscope {
namespace plugin {
const ShapeShifter::dictionary_t *ShapeShifter::dictionary = NULL;
bool ShapeShifter::mod_active_;
EventHandlerResult ShapeShifter::beforeReportingState() {
mod_active_ = kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_LeftShift) ||
kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_RightShift);
return EventHandlerResult::OK;
}
EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) {
if (!dictionary)
// Only act on keys that toggle on to prevent cycles (if the dictionary has
// two keys mapped to each other).
if (!keyToggledOn(key_state))
return EventHandlerResult::OK;
// If Shift is not active, bail out early.
if (!mod_active_)
if (!dictionary)
return EventHandlerResult::OK;
Key orig, repl;
@ -52,6 +48,15 @@ EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_a
if (orig == Key_NoKey)
return EventHandlerResult::OK;
bool shift_detected = false;
for (KeyAddr k : KeyAddr::all()) {
if (Layer.lookupOnLiveKeymap(k).isKeyboardShift())
shift_detected = true;
}
if (! shift_detected)
return EventHandlerResult::OK;
repl = dictionary[i].replacement.readFromProgmem();
// If found, handle the alternate key instead

@ -33,10 +33,6 @@ class ShapeShifter : public kaleidoscope::Plugin {
static const dictionary_t *dictionary;
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state);
EventHandlerResult beforeReportingState();
private:
static bool mod_active_;
};
}

@ -17,70 +17,21 @@
#include <Kaleidoscope-TapDance.h>
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/layers.h"
namespace kaleidoscope {
namespace plugin {
// --- state ---
uint16_t TapDance::start_time_;
uint16_t TapDance::time_out = 200;
TapDance::TapDanceState TapDance::state_[TapDance::TAPDANCE_KEY_COUNT];
Key TapDance::last_tap_dance_key_ = Key_NoKey;
KeyAddr TapDance::last_tap_dance_addr_;
// --- actions ---
void TapDance::interrupt(KeyAddr key_addr) {
uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST;
tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Interrupt);
state_[idx].triggered = true;
last_tap_dance_key_ = Key_NoKey;
Runtime.hid().keyboard().sendReport();
Runtime.hid().keyboard().releaseAllKeys();
if (state_[idx].pressed)
return;
release(idx);
}
void TapDance::timeout(void) {
uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST;
tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Timeout);
state_[idx].triggered = true;
if (state_[idx].pressed)
return;
last_tap_dance_key_ = Key_NoKey;
release(idx);
}
void TapDance::release(uint8_t tap_dance_index) {
last_tap_dance_key_ = Key_NoKey;
state_[tap_dance_index].pressed = false;
state_[tap_dance_index].triggered = false;
state_[tap_dance_index].release_next = true;
}
// --- config ---
void TapDance::tap(void) {
uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST;
state_[idx].count++;
start_time_ = Runtime.millisAtCycleStart();
tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Tap);
}
uint16_t TapDance::time_out = 200;
KeyAddr TapDance::release_addr_ = KeyAddr{KeyAddr::invalid_state};
// --- api ---
void TapDance::actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_t max_keys, const Key tap_keys[]) {
void TapDance::actionKeys(uint8_t tap_count,
ActionType tap_dance_action,
uint8_t max_keys,
const Key tap_keys[]) {
if (tap_count > max_keys)
tap_count = max_keys;
@ -91,108 +42,127 @@ void TapDance::actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_
break;
case Interrupt:
case Timeout:
handleKeyswitchEvent(key, last_tap_dance_addr_, IS_PRESSED | INJECTED);
if (event_queue_.isEmpty())
break;
{
KeyAddr td_addr = event_queue_.addr(0);
bool key_released = (Layer.lookupOnLiveKeymap(td_addr) == Key_Transparent);
handleKeyswitchEvent(key, td_addr, IS_PRESSED | INJECTED);
if (key_released)
release_addr_ = td_addr;
}
break;
case Hold:
handleKeyswitchEvent(key, last_tap_dance_addr_, IS_PRESSED | WAS_PRESSED | INJECTED);
break;
case Release:
kaleidoscope::Runtime.hid().keyboard().sendReport();
handleKeyswitchEvent(key, last_tap_dance_addr_, WAS_PRESSED | INJECTED);
break;
}
}
// --- hooks ---
EventHandlerResult TapDance::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) {
if (keyState & INJECTED)
EventHandlerResult TapDance::onKeyswitchEvent(Key &key,
KeyAddr key_addr,
uint8_t key_state) {
if (key_state & INJECTED)
return EventHandlerResult::OK;
if (mapped_key.getRaw() < ranges::TD_FIRST || mapped_key.getRaw() > ranges::TD_LAST) {
if (last_tap_dance_key_ == Key_NoKey)
return EventHandlerResult::OK;
if (keyToggledOn(keyState)) {
interrupt(key_addr);
mapped_key = Key_NoKey;
if (event_queue_.isEmpty()) {
if (keyToggledOn(key_state) && isTapDanceKey(key)) {
// Begin a new TapDance sequence:
uint8_t td_id = key.getRaw() - ranges::TD_FIRST;
tapDanceAction(td_id, key_addr, 1, Tap);
event_queue_.append(key_addr, key_state);
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}
uint8_t tap_dance_index = mapped_key.getRaw() - ranges::TD_FIRST;
if (keyToggledOff(keyState))
state_[tap_dance_index].pressed = false;
if (last_tap_dance_key_ != mapped_key) {
if (last_tap_dance_key_ == Key_NoKey) {
if (state_[tap_dance_index].triggered) {
if (keyToggledOff(keyState)) {
release(tap_dance_index);
}
return EventHandlerResult::EVENT_CONSUMED;
}
last_tap_dance_key_ = mapped_key;
last_tap_dance_addr_ = key_addr;
tap();
uint8_t td_count = event_queue_.length();
KeyAddr td_addr = event_queue_.addr(0);
Key td_key = Layer.lookup(td_addr);
uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST;
return EventHandlerResult::EVENT_CONSUMED;
if (keyToggledOn(key_state)) {
if (key_addr == td_addr) {
// The same TapDance key was pressed again; continue the sequence:
tapDanceAction(td_id, td_addr, ++td_count, Tap);
} else {
if (keyToggledOff(keyState) && state_[tap_dance_index].count) {
release(tap_dance_index);
return EventHandlerResult::EVENT_CONSUMED;
// A different key was pressed; interrupt the sequeunce:
tapDanceAction(td_id, td_addr, td_count, Interrupt);
event_queue_.clear();
// If the sequence was interrupted by another TapDance key, start the new
// sequence:
if (isTapDanceKey(Layer.lookup(key_addr))) {
td_id = key.getRaw() - ranges::TD_FIRST;
tapDanceAction(td_id, key_addr, 1, Tap);
}
if (!keyToggledOn(keyState)) {
return EventHandlerResult::EVENT_CONSUMED;
}
// Any key that toggles on while a TapDance sequence is live gets added to
// the queue. If it interrupted the queue, we need to hold off on processing
// that event until the next cycle to guarantee that the events appear in
// order on the host.
event_queue_.append(key_addr, key_state);
if (isTapDanceKey(key))
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::ABORT;
} else if (keyIsPressed(key_state)) {
// Until a key press event has been released from the queue, its "hold
// event" must be suppressed every cycle.
for (uint8_t i{0}; i < event_queue_.length(); ++i) {
if (event_queue_.addr(i) == key_addr) {
return EventHandlerResult::ABORT;
}
interrupt(key_addr);
}
}
// in sequence
if (keyToggledOff(keyState)) {
// We always indicate that other plugins don't need to handle TapDance keys,
// but we do allow them to show up as active keys when they're held. This way,
// when one times out, if it's not being held any longer, we can send the
// release event.
if (isTapDanceKey(key))
return EventHandlerResult::EVENT_CONSUMED;
}
last_tap_dance_key_ = mapped_key;
last_tap_dance_addr_ = key_addr;
state_[tap_dance_index].pressed = true;
if (keyToggledOn(keyState)) {
tap();
return EventHandlerResult::EVENT_CONSUMED;
}
if (state_[tap_dance_index].triggered)
tapDanceAction(tap_dance_index, key_addr, state_[tap_dance_index].count, Hold);
return EventHandlerResult::EVENT_CONSUMED;
// This key is being held, but is not in the queue, or it toggled off, but is
// not (currently) a TapDance key.
return EventHandlerResult::OK;
}
EventHandlerResult TapDance::afterEachCycle() {
for (uint8_t i = 0; i < TAPDANCE_KEY_COUNT; i++) {
if (!state_[i].release_next)
continue;
tapDanceAction(i, last_tap_dance_addr_, state_[i].count, Release);
state_[i].count = 0;
state_[i].release_next = false;
if (release_addr_.isValid()) {
handleKeyswitchEvent(Key_NoKey, release_addr_, WAS_PRESSED | INJECTED);
release_addr_ = KeyAddr{KeyAddr::invalid_state};
}
Key event_key;
// Purge any non-TapDance key events from the front of the queue.
while (! event_queue_.isEmpty()) {
KeyAddr event_addr = event_queue_.addr(0);
event_key = Layer.lookup(event_addr);
if (isTapDanceKey(event_key)) {
break;
}
handleKeyswitchEvent(event_key, event_addr, IS_PRESSED | INJECTED);
event_queue_.shift();
}
if (last_tap_dance_key_ == Key_NoKey)
if (event_queue_.isEmpty())
return EventHandlerResult::OK;
if (Runtime.hasTimeExpired(start_time_, time_out))
timeout();
// The first event in the queue is now guaranteed to be a TapDance key.
uint8_t td_id = event_key.getRaw() - ranges::TD_FIRST;
KeyAddr td_addr = event_queue_.addr(0);
// Check for timeout
uint8_t td_count = event_queue_.length();
uint16_t start_time = event_queue_.timestamp(td_count - 1);
if (Runtime.hasTimeExpired(start_time, time_out)) {
tapDanceAction(td_id, td_addr, td_count, Timeout);
event_queue_.clear();
// There's still a race condition here, but it's a minor one. If a TapDance
// sequence times out in the `afterEachCycle()` handler, then another key
// toggles on in the following scan cycle, and that key is handled first,
// the two events could show up out of order on the host. The probability of
// this happening is low, and event-driven Kaleidoscope will fix it
// completely, so I'm willing to accept the risk for now.
}
return EventHandlerResult::OK;
}

@ -19,6 +19,7 @@
#include "kaleidoscope/Runtime.h"
#include <Kaleidoscope-Ranges.h>
#include "kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h"
#define TD(n) Key(kaleidoscope::ranges::TD_FIRST + n)
@ -49,24 +50,20 @@ class TapDance : public kaleidoscope::Plugin {
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState);
EventHandlerResult afterEachCycle();
static constexpr bool isTapDanceKey(Key key) {
return (key.getRaw() >= ranges::TD_FIRST &&
key.getRaw() <= ranges::TD_LAST);
}
private:
static constexpr uint8_t TAPDANCE_KEY_COUNT = 16;
struct TapDanceState {
bool pressed: 1;
bool triggered: 1;
bool release_next: 1;
uint8_t count;
};
static TapDanceState state_[TAPDANCE_KEY_COUNT];
static uint16_t start_time_;
static Key last_tap_dance_key_;
static KeyAddr last_tap_dance_addr_;
static void tap(void);
static void interrupt(KeyAddr key_addr);
static void timeout(void);
static void release(uint8_t tap_dance_index);
// The maximum number of events in the queue at a time.
static constexpr uint8_t queue_capacity_{8};
// The event queue stores a series of press and release events.
KeyAddrEventQueue<queue_capacity_> event_queue_;
static KeyAddr release_addr_;
};
}

@ -21,53 +21,61 @@
namespace kaleidoscope {
namespace plugin {
uint8_t TopsyTurvy::last_pressed_position_;
bool TopsyTurvy::is_shifted_;
bool TopsyTurvy::is_active_;
KeyAddr TopsyTurvy::tt_addr_ = KeyAddr::none();
bool TopsyTurvy::shift_detected_ = false;
EventHandlerResult TopsyTurvy::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) {
if (mapped_key == Key_LeftShift ||
mapped_key == Key_RightShift) {
is_shifted_ = keyIsPressed(key_state);
if (is_active_)
return EventHandlerResult::EVENT_CONSUMED;
}
if (mapped_key < ranges::TT_FIRST || mapped_key > ranges::TT_LAST) {
if (keyToggledOn(key_state) && (mapped_key < Key_LeftControl || mapped_key > Key_RightGui)) {
last_pressed_position_ = key_addr.toInt();
}
EventHandlerResult TopsyTurvy::beforeEachCycle() {
// Clear the shift detection state before each scan cycle.
shift_detected_ = false;
return EventHandlerResult::OK;
}
EventHandlerResult TopsyTurvy::beforeReportingState() {
// If no TopsyTurvy key is active, there's nothing to do.
if (! tt_addr_.isValid())
return EventHandlerResult::OK;
}
is_active_ = keyIsPressed(key_state);
if (keyToggledOn(key_state)) {
last_pressed_position_ = key_addr.toInt();
// A TopsyTurvy key is active. That means we need to reverse the shift state,
// whether it was on or off.
if (shift_detected_) {
kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_LeftShift);
kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_RightShift);
} else {
if (last_pressed_position_ != key_addr.toInt()) {
return EventHandlerResult::EVENT_CONSUMED;
}
kaleidoscope::Runtime.hid().keyboard().pressKey(Key_LeftShift);
}
return EventHandlerResult::OK;
}
mapped_key.setRaw(mapped_key.getRaw() - ranges::TT_FIRST);
// invert the shift state
if (!is_shifted_) {
mapped_key.setFlags(mapped_key.getFlags() | SHIFT_HELD);
EventHandlerResult TopsyTurvy::onKeyswitchEvent(Key &key,
KeyAddr key_addr,
uint8_t key_state) {
// If a modifer key (including combo modifiers, but not non-modifier keys with
// mod flags) is active, and it includes `shift` (either from its keycode or a
// mod flag), record that we detected an "intentional shift".
if (key.isKeyboardShift() && keyIsPressed(key_state))
shift_detected_ = true;
// If the active TopsyTurvy key toggles off, clear the stored address to
// record that.
if (keyToggledOff(key_state)) {
if (key_addr == tt_addr_) {
tt_addr_.clear();
}
return EventHandlerResult::OK;
}
if (keyIsPressed(key_state)) {
kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_LeftShift);
kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_RightShift);
return EventHandlerResult::OK;
if (keyToggledOn(key_state)) {
if (isTopsyTurvyKey(key)) {
// If a TopsyTurvy key toggles on, store its address to indicate that it's
// active, and decode its key value to store in the active keys cache.
tt_addr_ = key_addr;
key = Key(key.getRaw() - ranges::TT_FIRST);
} else {
// If any other key toggles on, clear the active TopsyTurvy address.
tt_addr_.clear();
}
}
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::OK;
}
}

@ -29,12 +29,18 @@ class TopsyTurvy: public kaleidoscope::Plugin {
public:
TopsyTurvy(void) {}
EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state);
EventHandlerResult beforeEachCycle();
EventHandlerResult beforeReportingState();
EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state);
static bool isTopsyTurvyKey(Key key) {
return (key >= ranges::TT_FIRST &&
key <= ranges::TT_LAST);
}
private:
static uint8_t last_pressed_position_;
static bool is_shifted_;
static bool is_active_;
static KeyAddr tt_addr_;
static bool shift_detected_;
};
}
}

@ -27,8 +27,10 @@ EventHandlerResult WinKeyToggle::onKeyswitchEvent(Key &key, KeyAddr key_addr, ui
if (!enabled_)
return EventHandlerResult::OK;
if (key == Key_LeftGui || key == Key_RightGui)
if (key == Key_LeftGui || key == Key_RightGui) {
key = Key_NoKey;
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}

@ -46,3 +46,14 @@
"Layers are now in activation-order, please use" __NL__ \
"`Layer.forEachActiveLayer()` instead."
#define _DEPRECATED_MESSAGE_LAYER_UPDATELIVECOMPOSITEKEYMAP __NL__ \
"`Layer.updateLiveCompositeKeymap()` is deprecated.\n" __NL__ \
"The 'live composite keymap' cache has been replaced with the\n" __NL__ \
"'active keys' cache, which now represents the state of the active\n" __NL__ \
"keys at any given time. It is probably not necessary to directly\n" __NL__ \
"update that cache from a plugin, but if you need to, please use\n" __NL__ \
"the `Runtime.updateActiveKey(key_addr, key)` function instead."
#define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \
"`Layer.eventHandler()` is deprecated.\n" __NL__ \
"Please use `Layer.handleKeymapKeyswitchEvent()` instead."

@ -80,7 +80,7 @@
#define _REGISTER_EVENT_HANDLER( \
HOOK_NAME, HOOK_VERSION, DEPRECATION_TAG, \
SHOULD_ABORT_ON_CONSUMED_EVENT, \
SHOULD_EXIT_IF_RESULT_NOT_OK, \
TMPL_PARAM_TYPE_LIST, TMPL_PARAM_LIST, TMPL_DUMMY_ARGS_LIST, \
SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
@ -116,8 +116,8 @@
MAKE_TEMPLATE_SIGNATURE(UNWRAP TMPL_PARAM_TYPE_LIST) __NL__ \
struct _NAME4(EventHandler_, HOOK_NAME, _v, HOOK_VERSION) { __NL__ \
__NL__ \
static bool shouldAbortOnConsumedEvent() { __NL__ \
return SHOULD_ABORT_ON_CONSUMED_EVENT; __NL__ \
static bool shouldExitIfResultNotOk() { __NL__ \
return SHOULD_EXIT_IF_RESULT_NOT_OK; __NL__ \
} __NL__ \
__NL__ \
template<typename Plugin__, __NL__ \
@ -166,8 +166,8 @@
__NL__ \
result = EventHandler__::call(PLUGIN, hook_args...); __NL__ \
__NL__ \
if (EventHandler__::shouldAbortOnConsumedEvent() && __NL__ \
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) { __NL__ \
if (EventHandler__::shouldExitIfResultNotOk() && __NL__ \
result != kaleidoscope::EventHandlerResult::OK) { __NL__ \
return result; __NL__ \
} __NL__

@ -0,0 +1,27 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2020 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/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,82 @@
/* -*- mode: c++ -*-
* Copyright (C) 2020 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 <Kaleidoscope.h>
#include <Kaleidoscope-Macros.h>
#include "./common.h"
#undef min
#undef max
#include <iostream>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_A, ___, ___, ___, ___, ___, ___,
ShiftToLayer(1), ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
[1] = KEYMAP_STACKED
(
M(0), ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
const macro_t *macroAction(uint8_t index, uint8_t key_state) {
if (keyToggledOn(key_state)) {
switch (index) {
case 0:
Kaleidoscope.hid().keyboard().pressKey(Key_Y);
break;
}
}
return MACRO_NONE;
}
KALEIDOSCOPE_INIT_PLUGINS(Macros);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,51 @@
VERSION 1
KEYSWITCH A 0 0
KEYSWITCH LAYER_SHIFT 1 0
# ==============================================================================
# Active Keys Cache
NAME Active keys cache cleared
RUN 10 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # Report should contain only `A`
RUN 5 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# Press and hold `ShiftToLayer(1)`, changing the `A` key to `X`
PRESS LAYER_SHIFT
RUN 5 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_Y # Report should contain only `Y`
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
RELEASE A
RUN 5 ms
RELEASE LAYER_SHIFT
RUN 5 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # Report should contain only `A`
RUN 5 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty

@ -0,0 +1,27 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2020 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/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,104 @@
/* -*- mode: c++ -*-
* Copyright (C) 2020 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 <Kaleidoscope.h>
#include "./common.h"
#undef min
#undef max
#include <iostream>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_A, ___, ___, ___, ___, ___, ___,
ShiftToLayer(1), ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
[1] = KEYMAP_STACKED
(
Key_X, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
namespace kaleidoscope {
namespace plugin {
class ConvertXtoY : public kaleidoscope::Plugin {
public:
EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) {
if (keyToggledOn(key_state)) {
if (key == Key_X)
key = Key_Y;
}
// It should be impossible to get a `Key_X` at this point, because the
// previous block changes any `Key_X` to `Key_Y`, which results in the
// active keys cache storing that value. On subsequent cycles (while the key
// is still pressed), the `key` value should be `Key_Y`.
if (keyIsPressed(key_state) && key == Key_X) {
std::cerr << "t=" << Runtime.millisAtCycleStart() << ": "
<< "Error: we shouldn't see a key with value `X`" << std::endl;
}
// When `Key_Y` toggles off, return `EVENT_CONSUMED`. Despite this, the
// active keys cache entry should be cleared. If it's not, then a subsequent
// press of the same key without the layer shift in effect will still result
// in `Key_Y` instead of `Key_A`.
if (keyToggledOff(key_state) && (key == Key_Y)) {
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}
};
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::ConvertXtoY ConvertXtoY;
KALEIDOSCOPE_INIT_PLUGINS(ConvertXtoY);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,59 @@
VERSION 1
KEYSWITCH A 0 0
KEYSWITCH LAYER_SHIFT 1 0
# ==============================================================================
# Active Keys Cache
NAME Active keys cache cleared
RUN 10 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # Report should contain only `A`
RUN 5 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# Press and hold `ShiftToLayer(1)`, changing the `A` key to `X`
PRESS LAYER_SHIFT
RUN 5 ms
# Press `X`, which gets converted to `Y` by the `ConvertXtoY` plugin defined in
# the sketch. The plugin simply changes the value of the key, which causes it to
# get set to `Key_Y` in the active keys cache.
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_Y # Report should contain only `Y`
RUN 5 ms
# Release the `X` key (on Layer 1), which has become a `Y` key in the active
# keys cache. This should clear its entry in that cache.
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# Release `ShiftToLayer(1)`. This should restore the `A` key to its base layer
# value on subsequent presses, unless the Macros key gets "stuck" in the active
# keys array because it returns `EVENT_CONSUMED`.
RELEASE LAYER_SHIFT
RUN 5 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # Report should contain only `A`
RUN 5 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty

@ -0,0 +1,85 @@
/* -*- mode: c++ -*-
* Copyright (C) 2020 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 <Kaleidoscope.h>
#include <Kaleidoscope-TapDance.h>
#include "./common.h"
#undef min
#undef max
#include <iostream>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_X, ___, ___, ___, ___, ___, ___,
TD(0), ___, ___, ___, ___, ___, ___,
LockLayer(1), ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
[1] = KEYMAP_STACKED
(
Key_Y, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
void tapDanceAction(uint8_t tap_dance_index,
KeyAddr key_addr,
uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action,
Key_A, LockLayer(1));
default:
break;
}
}
KALEIDOSCOPE_INIT_PLUGINS(TapDance);
void setup() {
Kaleidoscope.setup();
TapDance.time_out = 25;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,27 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2020 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/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,24 @@
VERSION 1
KEYSWITCH X 0 0
KEYSWITCH TD_0 1 0
KEYSWITCH LL_1 2 0
# ==============================================================================
NAME TapDance hold past timeout
RUN 5 ms
PRESS TD_0
RUN 1 cycle
RUN 25 ms # timeout is 25 ms
RUN 2 cycles
EXPECT keyboard-report Key_A # The report should contain only `A`
RUN 20 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty

@ -0,0 +1,72 @@
/* -*- mode: c++ -*-
* Copyright (C) 2020 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 <Kaleidoscope.h>
#include <Kaleidoscope-TapDance.h>
#include "./common.h"
#undef min
#undef max
#include <iostream>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
TD(0), TD(1), ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
void tapDanceAction(uint8_t tap_dance_index,
KeyAddr key_addr,
uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action,
Key_A, Key_X);
case 1:
return tapDanceActionKeys(tap_count, tap_dance_action,
Key_B, Key_Y);
default:
break;
}
}
KALEIDOSCOPE_INIT_PLUGINS(TapDance);
void setup() {
Kaleidoscope.setup();
TapDance.time_out = 25;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,27 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2020 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/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,121 @@
VERSION 1
KEYSWITCH TD_0 0 0
KEYSWITCH TD_1 0 1
# ==============================================================================
NAME TapDance to TapDance rollover left to right
RUN 5 ms
PRESS TD_0
RUN 5 ms
PRESS TD_1
RUN 1 cycle
EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A`
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_0 release
RUN 4 ms
RELEASE TD_1
RUN 18 ms
EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_1 timeout
RUN 11 ms
RUN 5 ms
PRESS TD_0
RUN 5 ms
PRESS TD_1
RUN 1 cycle
EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A`
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_0 release
RUN 4 ms
RELEASE TD_1
RUN 18 ms
EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_1 timeout
RUN 11 ms
# ==============================================================================
NAME TapDance to TapDance rollover right to left
RUN 5 ms
PRESS TD_1
RUN 5 ms
PRESS TD_0
RUN 1 cycle
EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B`
RUN 4 ms
RELEASE TD_1
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_1 release
RUN 4 ms
RELEASE TD_0
RUN 18 ms
EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_0 timeout
RUN 11 ms
RUN 5 ms
PRESS TD_1
RUN 5 ms
PRESS TD_0
RUN 1 cycle
EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B`
RUN 4 ms
RELEASE TD_1
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_1 release
RUN 4 ms
RELEASE TD_0
RUN 18 ms
EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_0 timeout
RUN 11 ms
# ==============================================================================
NAME TapDance to TapDance rollover back and forth
RUN 5 ms
PRESS TD_0
RUN 5 ms
PRESS TD_1
RUN 1 cycle
EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A`
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_0 release
RUN 4 ms
RELEASE TD_1
RUN 18 ms
EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_1 timeout
RUN 11 ms
RUN 5 ms
PRESS TD_1
RUN 5 ms
PRESS TD_0
RUN 1 cycle
EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B`
RUN 4 ms
RELEASE TD_1
RUN 1 cycle
EXPECT keyboard-report empty # Empty report on TD_1 release
RUN 4 ms
RELEASE TD_0
RUN 18 ms
EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A`
RUN 1 cycle
EXPECT keyboard-report empty # Empty report after TD_0 timeout
RUN 11 ms

@ -0,0 +1,85 @@
/* -*- mode: c++ -*-
* Copyright (C) 2020 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 <Kaleidoscope.h>
#include <Kaleidoscope-TapDance.h>
#include "./common.h"
#undef min
#undef max
#include <iostream>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_X, ___, ___, ___, ___, ___, ___,
TD(0), ___, ___, ___, ___, ___, ___,
LockLayer(1), ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
[1] = KEYMAP_STACKED
(
Key_Y, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
void tapDanceAction(uint8_t tap_dance_index,
KeyAddr key_addr,
uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action,
Key_A, LockLayer(1));
default:
break;
}
}
KALEIDOSCOPE_INIT_PLUGINS(TapDance);
void setup() {
Kaleidoscope.setup();
TapDance.time_out = 25;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,27 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2020 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/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,69 @@
VERSION 1
KEYSWITCH X 0 0
KEYSWITCH TD_0 1 0
KEYSWITCH LL_1 2 0
# ==============================================================================
NAME TapDance issue 980 no overlap
RUN 5 ms
PRESS TD_0
RUN 5 ms
RELEASE TD_0
RUN 10 ms
PRESS TD_0
RUN 5 ms
RELEASE TD_0
RUN 5 ms
PRESS X
RUN 2 cycles
EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X)
RUN 5 ms
RELEASE X
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 5 ms
PRESS LL_1
RUN 1 cycle
RELEASE LL_1
RUN 1 cycle
# ==============================================================================
NAME TapDance issue 980 rollover
RUN 5 ms
PRESS TD_0
RUN 5 ms
RELEASE TD_0
RUN 10 ms
PRESS TD_0
RUN 5 ms
PRESS X
RUN 2 cycles
EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X)
RUN 5 ms
RELEASE TD_0
RUN 5 ms
RELEASE X
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 5 ms
PRESS LL_1
RUN 1 cycle
RELEASE LL_1
RUN 1 cycle

@ -20,8 +20,8 @@ RUN 5 ms
PRESS X
RUN 1 cycle
EXPECT keyboard-report Key_B # The report should contain `B`
EXPECT keyboard-report empty # Report should be empty
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
EXPECT keyboard-report Key_X # Report should contain `X`
RUN 5 ms
@ -47,59 +47,57 @@ RUN 20 ms # Timeout = 25ms
RUN 2 ms # Extra 2 cycles for some reason
EXPECT keyboard-report Key_B # The report should contain `B`
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
# ==============================================================================
# The testcases below are commented out because they are currently failing.
NAME Tapdance interrupt with rollover
# # ==============================================================================
# NAME Tapdance interrupt with rollover
# RUN 5 ms
# PRESS TD_0
# RUN 5 ms
# RELEASE TD_0
RUN 5 ms
PRESS TD_0
RUN 5 ms
RELEASE TD_0
# RUN 10 ms
# PRESS TD_0
RUN 10 ms
PRESS TD_0
# RUN 5 ms
# PRESS X
# RUN 1 cycle
# EXPECT keyboard-report Key_B # The report should contain `B`
# RUN 1 cycle
# EXPECT keyboard-report Key_B Key_X # Report should contain `B` & `X`
RUN 5 ms
PRESS X
RUN 1 cycle
EXPECT keyboard-report Key_B # The report should contain `B`
RUN 1 cycle
EXPECT keyboard-report Key_B Key_X # Report should contain `B` & `X`
# RUN 5 ms
# RELEASE TD_0
# RUN 1 cycle
# EXPECT keyboard-report Key_X # Report should contain `X`
RUN 5 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report Key_X # Report should contain `X`
# RUN 5 ms
# RELEASE X
# RUN 1 cycle
# EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
RELEASE X
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
# # ==============================================================================
# NAME Tapdance timeout while held
# ==============================================================================
NAME Tapdance timeout while held
# RUN 5 ms
RUN 5 ms
# PRESS TD_0
# RUN 5 ms
# RELEASE TD_0
# RUN 10 ms
PRESS TD_0
RUN 5 ms
RELEASE TD_0
RUN 10 ms
# PRESS TD_0
# RUN 1 cycle
# RUN 25 ms
PRESS TD_0
RUN 1 cycle
RUN 25 ms
# RUN 2 cycles
# EXPECT keyboard-report Key_B # The report should contain `B`
RUN 2 cycles
EXPECT keyboard-report Key_B # The report should contain `B`
# RUN 10 ms
# RELEASE TD_0
# RUN 1 cycle
RUN 10 ms
RELEASE TD_0
RUN 1 cycle
# EXPECT keyboard-report empty # Report should be empty
EXPECT keyboard-report empty # Report should be empty

Loading…
Cancel
Save