Merge remote-tracking branch 'plugin/TapDance/f/monorepo' into f/monorepo-stage2

pull/389/head
Gergely Nagy 6 years ago
commit 4e2f697a52
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -0,0 +1,153 @@
# Kaleidoscope-TapDance
Tap-dance keys are general purpose, multi-use keys, which trigger a different
action based on the number of times they were tapped in sequence. As an example
to make this clearer, one can have a key that inputs `A` when tapped once,
inputs `B` when tapped twice, and lights up the keyboard in Christmas colors
when tapped a third time.
This behaviour is most useful in cases where we have a number of things we
perform rarely, where tapping a single key repeatedly is not counter-productive.
Such cases include - for example - multimedia forward / backward keys: forward
on single tap, backward on double. Of course, one could use modifiers to achieve
a similar effect, but that's two keys to use, this is only one. We can also hide
some destructive functionality behind a number of taps: reset the keyboard after
4 taps, and light up LEDs in increasingly frightful colors until then.
## How does it work?
To not interfere with normal typing, tap-dance keys have two ways to decide when
to call an action: they either get interrupted, or they time out. Every time a
tap-dance key is pressed, the timer resets, so one does not have to finish the
whole tapping sequence within a short time limit. The tap-dance counter
continues incrementing until one of these cases happen.
When a tap-dance key is pressed and released, and nothing is pressed on the
keyboard until the timeout is reached, then the key will time out, and trigger
an action. Which action, depends on the number of times it has been tapped up
until this point.
When a tap-dance key is pressed and released, and another key is hit before the
timer expires, then the tap-dance key will trigger an action first, perform it,
and only then will the firmware continue handling the interrupting key press.
This is to preserve the order of keys pressed.
In both of these cases, the [`tapDanceAction`][tdaction] will be called, with
`tapDanceIndex` set to the index of the tap-dance action (as set in the keymap),
the `tapCount`, and `tapDanceAction` set to either
`kaleidoscope::plugin::TapDance::Interrupt`, or
`kaleidoscope::plugin::TapDance::Timeout`. If we continue holding the key, then
as long as it is held, the same function will be called with `tapDanceAction`
set to `kaleidoscope::plugin::TapDance::Hold`. When the key is released, after
either an `Interrupt` or `Timeout` action was triggered, the function will be
called with `tapDanceAction` set to `kaleidoscope::plugin::TapDance::Release`.
These actions allow us to create sophisticated tap-dance setups, where one can
tap a key twice and hold it, and have it repeat, for example.
There is one additional value the `tapDanceAction` parameter can take:
`kaleidoscope::plugin::TapDance::Tap`. It is called with this argument for each
and every tap, even if no action is to be triggered yet. This is so that we can
have a way to do some side-effects, like light up LEDs to show progress, and so
on.
## Using the plugin
To use the plugin, we need to include the header, and declare the behaviour
used. Then, we need to place tap-dance keys on the keymap. And finally, we need
to implement the [`tapDanceAction`][tdaction] function that gets called each
time an action is to be performed.
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-TapDance.h>
// Somewhere in the keymap:
TD(0)
// later in the Sketch:
void tapDanceAction(uint8_t tap_dance_index, byte row, byte col, uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action,
Consumer_ScanNextTrack, Consumer_ScanPreviousTrack);
}
}
KALEIDOSCOPE_INIT_PLUGINS(TapDance);
void setup() {
Kaleidoscope.setup ();
}
```
## Keymap markup
### `TD(id)`
> A key that acts as a tap-dance key. The actions performed depend on the
> implementation for the `id` index within the [`tapDanceActions`][tdactions]
> function.
>
> The `id` parameter here is what will be used as `tap_dance_index` in the
> handler function.
[tdaction]: #tapdanceactiontapdanceindex-tapcount-tapdanceaction
## Plugin methods
The plugin provides a `TapDance` object, but to implement the actions, we need
to define a function ([`tapDanceAction`][tdaction]) outside of the object. A
handler, of sorts. Nevertheless, the plugin provides one macro that is
particularly useful: `tapDanceActionKeys`. Apart from that, it provides one
property only:
### `.time_out`
> The number of loop iterations to wait before a tap-dance sequence times out.
> Once the sequence timed out, the action for it will trigger, even without an
> interruptor. Defaults to 5, and the timer resets with every tap of the same
### `tapDanceActionKeys(tap_count, tap_dance_action, keys...)`
> Sets up an action where for each subsequent tap, a different key will be
> chosen from the list of keys supplied in the `keys...` argument.
>
> If we have `Key_A` and `Key_B` in the list, then, if tapped once, this
> function will input `A`, but when tapped twice, will input `B`.
>
> When all our actions are just different keys, this is a very handy macro to
> use.
>
> The `tap_count` and `tap_dance_actions` parameters should be the same as the
> similarly named parameters of the `tapDanceAction` function.
### `tapDanceAction(tap_dance_index, row, col, tap_count, tap_dance_action)`
> The heart of the tap-dance plugin is the handler method. This is called every
> time any kind of tap-dance action is to be performed. See the
> *[How does it work?](#how-does-it-work)* section for details about when and
> how this function is called.
>
> The `tap_dance_index` and `tap_count` parameters help us choose which action
> to perform. The `row` and `col` parameters tell us where the tap-dance key is
> on the keyboard.
## Dependencies
* [Kaleidoscope-Ranges](Ranges.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: ../../examples/TapDance/TapDance.ino
## Upgrading
Previous versions of `TapDance` used `kaleidoscope::TapDance::ActionType` as the
type of TapDance actions. In newer versions, this is
`kaleidoscope::plugin::TapDance::ActionType`. The old name still works, but will
be removed by 2019-01-14.

@ -0,0 +1,64 @@
/* -*- mode: c++ -*-
* Kaleidoscope-TapDance -- Tap-dance keys
* Copyright (C) 2016, 2017, 2018 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>
// *INDENT-OFF*
const Key keymaps[][ROWS][COLS] PROGMEM = {
[0] = KEYMAP_STACKED
(
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
TD(0),
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
TD(1)),
};
// *INDENT-ON*
static void tapDanceEsc(uint8_t tap_dance_index, uint8_t tap_count, kaleidoscope::TapDance::ActionType tap_dance_action) {
tapDanceActionKeys(tap_count, tap_dance_action, Key_Escape, Key_Tab);
}
void tapDanceAction(uint8_t tap_dance_index, byte row, byte col, uint8_t tap_count, kaleidoscope::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action, Key_Tab, Key_Escape);
case 1:
return tapDanceEsc(tap_dance_index, tap_count, tap_dance_action);
}
}
KALEIDOSCOPE_INIT_PLUGINS(TapDance);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,20 @@
/* -*- mode: c++ -*-
* Kaleidoscope-TapDance -- Tap-dance keys
* Copyright (C) 2016, 2017 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 <kaleidoscope/plugin/TapDance.h>

@ -0,0 +1,232 @@
/* -*- mode: c++ -*-
* Kaleidoscope-TapDance -- Tap-dance keys
* Copyright (C) 2016, 2017, 2018 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-TapDance.h>
#include <kaleidoscope/hid.h>
namespace kaleidoscope {
namespace plugin {
// --- state ---
uint32_t TapDance::end_time_;
uint16_t TapDance::time_out = 200;
uint8_t TapDance::tap_count_[16];
uint16_t TapDance::pressed_state_;
uint16_t TapDance::triggered_state_;
uint16_t TapDance::release_next_state_;
Key TapDance::last_tap_dance_key_;
byte TapDance::last_tap_dance_row_;
byte TapDance::last_tap_dance_col_;
// --- helpers ---
#define isTapDance(k) (k.raw >= ranges::TD_FIRST && k.raw <= ranges::TD_LAST)
#define isInSeq(k) (last_tap_dance_key_.raw == k.raw)
#define stillHeld(idx) (tap_count_[idx])
#define isActive() (last_tap_dance_key_.raw != Key_NoKey.raw)
// --- actions ---
void TapDance::interrupt(byte row, byte col) {
uint8_t idx = last_tap_dance_key_.raw - ranges::TD_FIRST;
tapDanceAction(idx, last_tap_dance_row_, last_tap_dance_col_, tap_count_[idx], Interrupt);
bitWrite(triggered_state_, idx, 1);
end_time_ = 0;
KeyboardHardware.maskKey(row, col);
kaleidoscope::hid::sendKeyboardReport();
kaleidoscope::hid::releaseAllKeys();
if (bitRead(pressed_state_, idx))
return;
release(idx);
}
void TapDance::timeout(void) {
uint8_t idx = last_tap_dance_key_.raw - ranges::TD_FIRST;
tapDanceAction(idx, last_tap_dance_row_, last_tap_dance_col_, tap_count_[idx], Timeout);
bitWrite(triggered_state_, idx, 1);
if (bitRead(pressed_state_, idx))
return;
last_tap_dance_key_.raw = Key_NoKey.raw;
release(idx);
}
void TapDance::release(uint8_t tap_dance_index) {
end_time_ = 0;
last_tap_dance_key_.raw = Key_NoKey.raw;
bitClear(pressed_state_, tap_dance_index);
bitClear(triggered_state_, tap_dance_index);
bitWrite(release_next_state_, tap_dance_index, 1);
}
void TapDance::tap(void) {
uint8_t idx = last_tap_dance_key_.raw - ranges::TD_FIRST;
tap_count_[idx]++;
end_time_ = millis() + time_out;
tapDanceAction(idx, last_tap_dance_row_, last_tap_dance_col_, tap_count_[idx], Tap);
}
// --- api ---
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;
Key key;
key.raw = pgm_read_word(&(tap_keys[tap_count - 1].raw));
switch (tap_dance_action) {
case Tap:
break;
case Interrupt:
case Timeout:
handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, IS_PRESSED | INJECTED);
break;
case Hold:
handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, IS_PRESSED | WAS_PRESSED | INJECTED);
break;
case Release:
hid::sendKeyboardReport();
handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, WAS_PRESSED | INJECTED);
break;
}
}
// --- hooks ---
EventHandlerResult TapDance::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) {
if (keyState & INJECTED)
return EventHandlerResult::OK;
if (!keyIsPressed(keyState) && !keyWasPressed(keyState)) {
if (isTapDance(mapped_key)) {
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}
if (!isTapDance(mapped_key)) {
if (!isActive())
return EventHandlerResult::OK;
if (keyToggledOn(keyState))
interrupt(row, col);
if (KeyboardHardware.isKeyMasked(row, col)) {
KeyboardHardware.unMaskKey(row, col);
return EventHandlerResult::EVENT_CONSUMED;
}
return EventHandlerResult::OK;
}
uint8_t tap_dance_index = mapped_key.raw - ranges::TD_FIRST;
if (keyToggledOff(keyState))
bitClear(pressed_state_, tap_dance_index);
if (!isInSeq(mapped_key)) {
if (!isActive()) {
if (bitRead(triggered_state_, tap_dance_index)) {
if (keyToggledOff(keyState)) {
release(tap_dance_index);
}
return EventHandlerResult::EVENT_CONSUMED;
}
last_tap_dance_key_.raw = mapped_key.raw;
last_tap_dance_row_ = row;
last_tap_dance_col_ = col;
tap();
return EventHandlerResult::EVENT_CONSUMED;
} else {
if (keyToggledOff(keyState) && stillHeld(tap_dance_index)) {
release(tap_dance_index);
return EventHandlerResult::EVENT_CONSUMED;
}
if (!keyToggledOn(keyState)) {
return EventHandlerResult::EVENT_CONSUMED;
}
interrupt(row, col);
}
}
// in sequence
if (keyToggledOff(keyState)) {
return EventHandlerResult::EVENT_CONSUMED;
}
last_tap_dance_key_.raw = mapped_key.raw;
last_tap_dance_row_ = row;
last_tap_dance_col_ = col;
bitSet(pressed_state_, tap_dance_index);
if (keyToggledOn(keyState)) {
tap();
return EventHandlerResult::EVENT_CONSUMED;
}
if (bitRead(triggered_state_, tap_dance_index))
tapDanceAction(tap_dance_index, row, col, tap_count_[tap_dance_index], Hold);
return EventHandlerResult::EVENT_CONSUMED;
}
EventHandlerResult TapDance::afterEachCycle() {
for (uint8_t i = 0; i < 16; i++) {
if (!bitRead(release_next_state_, i))
continue;
tapDanceAction(i, last_tap_dance_row_, last_tap_dance_col_, tap_count_[i], Release);
tap_count_[i] = 0;
bitClear(release_next_state_, i);
}
if (!isActive())
return EventHandlerResult::OK;
if (end_time_ && millis() > end_time_)
timeout();
return EventHandlerResult::OK;
}
}
}
__attribute__((weak)) void tapDanceAction(uint8_t tap_dance_index, byte row, byte col, uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
}
kaleidoscope::plugin::TapDance TapDance;

@ -0,0 +1,77 @@
/* -*- mode: c++ -*-
* Kaleidoscope-TapDance -- Tap-dance keys
* Copyright (C) 2016, 2017, 2018 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 <Kaleidoscope.h>
#include <Kaleidoscope-Ranges.h>
#define TD(n) (Key) {.raw = kaleidoscope::ranges::TD_FIRST + n }
#define tapDanceActionKeys(tap_count, tap_dance_action, ...) ({ \
static const Key __k[] PROGMEM = { __VA_ARGS__ }; \
TapDance.actionKeys(tap_count, tap_dance_action, \
sizeof (__k) / sizeof (Key), &__k[0]); \
})
namespace kaleidoscope {
namespace plugin {
class TapDance : public kaleidoscope::Plugin {
public:
typedef enum {
Tap,
Hold,
Interrupt,
Timeout,
Release,
} ActionType;
TapDance(void) {}
static uint16_t time_out;
void actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_t max_keys, const Key tap_keys[]);
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState);
EventHandlerResult afterEachCycle();
private:
static uint32_t end_time_;
static uint8_t tap_count_[16];
static uint16_t pressed_state_;
static uint16_t triggered_state_;
static uint16_t release_next_state_;
static Key last_tap_dance_key_;
static byte last_tap_dance_row_;
static byte last_tap_dance_col_;
static void tap(void);
static void interrupt(byte row, byte col);
static void timeout(void);
static void release(uint8_t tap_dance_index);
};
}
// Backwards compatibility
typedef plugin::TapDance TapDance;
}
void tapDanceAction(uint8_t tap_dance_index, byte row, byte col, uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action);
extern kaleidoscope::plugin::TapDance TapDance;
Loading…
Cancel
Save