|
|
|
@ -19,6 +19,7 @@
|
|
|
|
|
#include <Kaleidoscope-FocusSerial.h>
|
|
|
|
|
#include "kaleidoscope/keyswitch_state.h"
|
|
|
|
|
#include "kaleidoscope/layers.h"
|
|
|
|
|
#include "kaleidoscope/KeyEventTracker.h"
|
|
|
|
|
|
|
|
|
|
namespace kaleidoscope {
|
|
|
|
|
namespace plugin {
|
|
|
|
@ -26,153 +27,124 @@ namespace plugin {
|
|
|
|
|
// --- config ---
|
|
|
|
|
|
|
|
|
|
uint16_t TapDance::time_out = 200;
|
|
|
|
|
KeyAddr TapDance::release_addr_ = KeyAddr{KeyAddr::invalid_state};
|
|
|
|
|
uint8_t TapDance::tap_count_ = 0;
|
|
|
|
|
|
|
|
|
|
KeyEventTracker TapDance::event_tracker_;
|
|
|
|
|
|
|
|
|
|
// --- api ---
|
|
|
|
|
void TapDance::actionKeys(uint8_t tap_count,
|
|
|
|
|
ActionType tap_dance_action,
|
|
|
|
|
ActionType action,
|
|
|
|
|
uint8_t max_keys,
|
|
|
|
|
const Key tap_keys[]) {
|
|
|
|
|
if (event_queue_.isEmpty())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (tap_count > max_keys)
|
|
|
|
|
tap_count = max_keys;
|
|
|
|
|
|
|
|
|
|
Key key = tap_keys[tap_count - 1].readFromProgmem();
|
|
|
|
|
KeyEvent event = event_queue_.event(0);
|
|
|
|
|
event.key = tap_keys[tap_count - 1].readFromProgmem();
|
|
|
|
|
|
|
|
|
|
switch (tap_dance_action) {
|
|
|
|
|
case Tap:
|
|
|
|
|
break;
|
|
|
|
|
case Interrupt:
|
|
|
|
|
case Timeout:
|
|
|
|
|
if (event_queue_.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
{
|
|
|
|
|
KeyAddr td_addr = event_queue_.addr(0);
|
|
|
|
|
bool key_released = (live_keys[td_addr] == Key_Transparent);
|
|
|
|
|
handleKeyswitchEvent(key, td_addr, IS_PRESSED | INJECTED);
|
|
|
|
|
if (key_released)
|
|
|
|
|
release_addr_ = td_addr;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case Hold:
|
|
|
|
|
case Release:
|
|
|
|
|
break;
|
|
|
|
|
if (action == Interrupt || action == Timeout) {
|
|
|
|
|
event_queue_.shift();
|
|
|
|
|
Runtime.handleKeyswitchEvent(event);
|
|
|
|
|
} else if (action == Tap && tap_count == max_keys) {
|
|
|
|
|
tap_count_ = 0;
|
|
|
|
|
event_queue_.clear();
|
|
|
|
|
Runtime.handleKeyswitchEvent(event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void TapDance::flushQueue(KeyAddr ignored_addr) {
|
|
|
|
|
while (! event_queue_.isEmpty()) {
|
|
|
|
|
KeyEvent queued_event = event_queue_.event(0);
|
|
|
|
|
event_queue_.shift();
|
|
|
|
|
if (queued_event.addr != ignored_addr)
|
|
|
|
|
Runtime.handleKeyswitchEvent(queued_event);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- hooks ---
|
|
|
|
|
|
|
|
|
|
EventHandlerResult TapDance::onNameQuery() {
|
|
|
|
|
return ::Focus.sendName(F("TapDance"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventHandlerResult TapDance::onKeyswitchEvent(Key &key,
|
|
|
|
|
KeyAddr key_addr,
|
|
|
|
|
uint8_t key_state) {
|
|
|
|
|
if (key_state & INJECTED)
|
|
|
|
|
EventHandlerResult TapDance::onKeyswitchEvent(KeyEvent &event) {
|
|
|
|
|
// If the plugin has already processed and released this event, ignore it.
|
|
|
|
|
// There's no need to update the event tracker explicitly.
|
|
|
|
|
if (event_tracker_.shouldIgnore(event)) {
|
|
|
|
|
// We should never get an event that's in our queue here, but just in case
|
|
|
|
|
// some other plugin sends one, abort.
|
|
|
|
|
if (event_queue_.shouldAbort(event))
|
|
|
|
|
return EventHandlerResult::ABORT;
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
// If event.addr is not a physical key, ignore it; some other plugin injected it.
|
|
|
|
|
if (! event.addr.isValid() || (event.state & INJECTED) != 0) {
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t td_count = event_queue_.length();
|
|
|
|
|
if (keyToggledOff(event.state)) {
|
|
|
|
|
if (event_queue_.isEmpty())
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
event_queue_.append(event);
|
|
|
|
|
return EventHandlerResult::ABORT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event_queue_.isEmpty() && !isTapDanceKey(event.key))
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
|
|
|
|
|
KeyAddr td_addr = event_queue_.addr(0);
|
|
|
|
|
Key td_key = Layer.lookup(td_addr);
|
|
|
|
|
Key td_key = Layer.lookupOnActiveLayer(td_addr);
|
|
|
|
|
uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST;
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (! event_queue_.isEmpty() &&
|
|
|
|
|
event.addr != event_queue_.addr(0)) {
|
|
|
|
|
// Interrupt: Call `tapDanceAction()` first, so it will have access to the
|
|
|
|
|
// TapDance key press event that needs to be sent, then flush the queue.
|
|
|
|
|
tapDanceAction(td_id, td_addr, tap_count_, Interrupt);
|
|
|
|
|
flushQueue();
|
|
|
|
|
tap_count_ = 0;
|
|
|
|
|
// If the event isn't another TapDance key, let it proceed. If it is, fall
|
|
|
|
|
// through to the next block, which handles "Tap" actions.
|
|
|
|
|
if (! isTapDanceKey(event.key))
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
// 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;
|
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
|
|
// Tap: First flush the queue, ignoring the previous press and release events
|
|
|
|
|
// for the TapDance key, then add the new tap to the queue (it becomes the
|
|
|
|
|
// first entry).
|
|
|
|
|
flushQueue(event.addr);
|
|
|
|
|
event_queue_.append(event);
|
|
|
|
|
tapDanceAction(td_id, td_addr, ++tap_count_, Tap);
|
|
|
|
|
return EventHandlerResult::ABORT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EventHandlerResult TapDance::afterEachCycle() {
|
|
|
|
|
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 there's no active TapDance sequence, there's nothing to do.
|
|
|
|
|
if (event_queue_.isEmpty())
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
Key td_key = Layer.lookupOnActiveLayer(td_addr);
|
|
|
|
|
uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST;
|
|
|
|
|
|
|
|
|
|
// Check for timeout
|
|
|
|
|
uint8_t td_count = event_queue_.length();
|
|
|
|
|
uint16_t start_time = event_queue_.timestamp(td_count - 1);
|
|
|
|
|
uint16_t start_time = event_queue_.timestamp(0);
|
|
|
|
|
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.
|
|
|
|
|
tapDanceAction(td_id, td_addr, tap_count_, Timeout);
|
|
|
|
|
flushQueue();
|
|
|
|
|
tap_count_ = 0;
|
|
|
|
|
}
|
|
|
|
|
return EventHandlerResult::OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // namespace plugin
|
|
|
|
|
} // namespace kaleidoscope
|
|
|
|
|
|
|
|
|
|
__attribute__((weak)) void tapDanceAction(uint8_t tap_dance_index, KeyAddr key_addr, uint8_t tap_count,
|
|
|
|
|
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
|
|
|
|
|