New plugin: DynamicTapDance

This implements an extension to the `TapDance` plugin, allowing us to store
`tapDanceActionKeys()`-esque lists in Storage. The core idea here is very
similar to that of `DynamicMacros`: we dump/restore the full list via Focus,
build a cached index on setup and any updates, and play back the selected key
when need be.

Unlike `DynamicMacros`,this plugin is built on top of `TapDance` and cannot
function without it.

Fixes #730.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/734/head
Gergely Nagy 5 years ago
parent 11a749b5f5
commit b415a85950
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -0,0 +1,101 @@
# Kaleidoscope-DynamicTapDance
The `DynamicTapDance` plugin allows one to set up [TapDance][plugin:tapdance] keys
without the need to compile and flash new firmware: one can change dynamic
dances via [Focus][plugin:focus], using a tool like [Chrysalis][chrysalis].
[plugin:tapdance]: TapDance.md
[plugin:focus]: FocusSerial.md
[chrysalis]: https://github.com/keyboardio/Chrysalis
Dynamic dances come with certain limitations, however: unlike the built-in ones,
dynamic ones do not support running custom code. They can only choose a key from
a list of possibilities. Given a list of keys, the plugin will choose the one
corresponding to the number of taps on the key, just like `TapDance` itself does.
Basically, this plugin allows us to store `tapDanceActionKeys` key lists in the
on-board memory of our keyboard.
You can define up to 16 dynamic dances, there is no limit on their length,
except the amount of storage available on the keyboard. You can even mix them
with built-in dances! But the total number of tap-dances is 16.
## Using the plugin
To use the plugin, we need to include the header, tell the firmware to `use` the
plugin, and reserve storage space for the dances. This is best illustrated with
an example:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-EEPROMSettings.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-TapDance.h>
#include <Kaleidoscope-DynamicTapDance.h>
KALEIDOSCOPE_INIT_PLUGINS(
EEPROMSettings,
Focus,
TapDance,
DynamicTapDance
);
void tapDanceAction(uint8_t tap_dance_index, KeyAddr key_addr, uint8_t tap_count, kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
DynamicTapDance.dance(tap_dance_index, key_addr, tap_count, tap_dance_action);
}
void setup() {
Kaleidoscope.setup();
// 0 is the amount of built-in dances we have.
// 128 is how much space (in bytes) we reserve for dances.
DynamicTapDance.setup(0, 128);
}
```
## Plugin methods
The plugin provides a `DynamicTapDance` object, with the following methods and properties available:
### `.setup(builtin_dances, size)`
> Reserves `size` bytes of storage for dynamic dances. This must be called from
> the `setup()` method of your sketch, otherwise dynamic tap-dances will not
> function.
>
> The `builtin_dances` argument tells the plugin how many built-in dances there
> are.
### `.dance(index, key_addr, tap_count, tap_dance_action)`
> Performs a given dance (`index`) made on the key at `key_addr` address, which
> has been tapped `tap_count` times, and the action to perform is
> `tap_dance_action`.
>
> This mirrors the overrideable `tapDanceAction()` method of
> [TapDance][plugin:tapdance], and is intended to be called from therein.
## Focus commands
The plugin provides one Focus command: `tapdance.ap`.
### `tapdance.map [dances...]`
> Without arguments, displays all the stored dances. Each dance is terminated by
> an end marker (`0`, aka `Key_NoKey`), and the last dance is followed by an
> additional marker. The plugin will send back the entire dynamic tap-dance
> storage space, even data after the final marker.
>
> With arguments, it replaces the current set of dynamic dances with the newly
> given ones. Dances are terminated by an end marker, and the last macro must be
> terminated by an additional one. It is up to the caller to make sure these
> rules are obeyed.
>
> In both cases, the data sent or expected is a sequence of 16-bit values, a
> memory dump.
## Dependencies
* [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](FocusSerial.md)
* [Kaleidoscope-TapDance](TapDance.md)

@ -0,0 +1,73 @@
/* -*- mode: c++ -*-
* DynamicTapDance -- Dynamic TapDance support for Kaleidoscope
* Copyright (C) 2019 Keyboard.io, Inc
* Copyright (C) 2019 Dygma Lab S.L.
*
* 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-EEPROM-Settings.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-TapDance.h>
#include <Kaleidoscope-DynamicTapDance.h>
// *INDENT-OFF*
KEYMAPS(
[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, TD(2),
TD(1)),
)
// *INDENT-ON*
enum {
TD_TAB_ESC,
TD_LAST
};
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 TD_TAB_ESC:
return tapDanceActionKeys(tap_count, tap_dance_action, Key_A, Key_B);
default:
DynamicTapDance.dance(tap_dance_index, key_addr, tap_count, tap_dance_action);
}
}
KALEIDOSCOPE_INIT_PLUGINS(EEPROMSettings,
Focus,
TapDance,
DynamicTapDance);
void setup() {
Kaleidoscope.setup();
DynamicTapDance.setup(TD_LAST, 32);
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,20 @@
/* DynamicTapDance -- Dynamic TapDance support for Kaleidoscope
* Copyright (C) 2019 Keyboard.io, Inc
* Copyright (C) 2019 Dygma Lab S.L.
*
* 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/DynamicTapDance.h"

@ -0,0 +1,132 @@
/* DynamicTapDance -- Dynamic TapDance support for Kaleidoscope
* Copyright (C) 2019 Keyboard.io, Inc
* Copyright (C) 2019 Dygma Lab S.L.
*
* 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-DynamicTapDance.h"
#include "kaleidoscope/hid.h"
#include <Kaleidoscope-EEPROM-Settings.h>
#include "Kaleidoscope-FocusSerial.h"
namespace kaleidoscope {
namespace plugin {
uint16_t DynamicTapDance::storage_base_;
uint16_t DynamicTapDance::storage_size_;
uint16_t DynamicTapDance::map_[];
uint8_t DynamicTapDance::offset_;
uint8_t DynamicTapDance::dance_count_;
constexpr uint8_t DynamicTapDance::reserved_tap_dance_key_count_;
void DynamicTapDance::updateDynamicTapDanceCache() {
uint16_t pos = storage_base_;
uint8_t current_id = 0;
bool previous_dance_ended = false;
dance_count_ = 0;
map_[0] = 0;
while (pos < storage_base_ + storage_size_) {
uint16_t raw_key = Kaleidoscope.storage().read(pos);
pos += 2;
Key key(raw_key);
if (key == Key_NoKey) {
map_[++current_id] = pos - storage_base_;
if (previous_dance_ended)
return;
dance_count_++;
previous_dance_ended = true;
} else {
previous_dance_ended = false;
}
}
}
bool DynamicTapDance::dance(uint8_t tap_dance_index, KeyAddr key_addr,
uint8_t tap_count, TapDance::ActionType tap_dance_action) {
uint16_t pos = map_[tap_dance_index - offset_] + ((tap_count - 1) * 2);
uint16_t next_pos = map_[tap_dance_index - offset_ + 1];
if (next_pos <= pos || (tap_dance_index > offset_ + dance_count_))
return false;
Key key;
Kaleidoscope.storage().get(storage_base_ + pos, key);
switch (tap_dance_action) {
case TapDance::Tap:
break;
case TapDance::Interrupt:
case TapDance::Timeout:
handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED);
break;
case TapDance::Hold:
handleKeyswitchEvent(key, key_addr, IS_PRESSED | WAS_PRESSED | INJECTED);
break;
case TapDance::Release:
hid::sendKeyboardReport();
handleKeyswitchEvent(key, key_addr, WAS_PRESSED | INJECTED);
break;
}
return true;
}
EventHandlerResult DynamicTapDance::onFocusEvent(const char *command) {
if (::Focus.handleHelp(command, PSTR("tapdance.map")))
return EventHandlerResult::OK;
if (strncmp_P(command, PSTR("tapdance."), 9) != 0)
return EventHandlerResult::OK;
if (strcmp_P(command + 9, PSTR("map")) == 0) {
if (::Focus.isEOL()) {
for (uint16_t i = 0; i < storage_size_; i += 2) {
Key k;
Kaleidoscope.storage().get(storage_base_ + i, k);
::Focus.send(k);
}
} else {
uint16_t pos = 0;
while (!::Focus.isEOL()) {
Key k;
::Focus.read(k);
Kaleidoscope.storage().put(storage_base_ + pos, k);
pos += 2;
}
Kaleidoscope.storage().commit();
updateDynamicTapDanceCache();
}
}
return EventHandlerResult::EVENT_CONSUMED;
}
void DynamicTapDance::setup(uint8_t dynamic_offset, uint16_t size) {
storage_base_ = ::EEPROMSettings.requestSlice(size);
storage_size_ = size;
offset_ = dynamic_offset;
updateDynamicTapDanceCache();
}
}
}
kaleidoscope::plugin::DynamicTapDance DynamicTapDance;

@ -0,0 +1,50 @@
/* DynamicTapDance -- Dynamic TapDance support for Kaleidoscope
* Copyright (C) 2019 Keyboard.io, Inc
* Copyright (C) 2019 Dygma Lab S.L.
*
* 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-TapDance.h>
namespace kaleidoscope {
namespace plugin {
class DynamicTapDance: public kaleidoscope::Plugin {
public:
DynamicTapDance() {}
EventHandlerResult onFocusEvent(const char *command);
static void setup(uint8_t dynamic_offset, uint16_t size);
static bool dance(uint8_t tap_dance_index, KeyAddr key_addr, uint8_t tap_count,
TapDance::ActionType tap_dance_action);
private:
static uint16_t storage_base_;
static uint16_t storage_size_;
static constexpr uint8_t reserved_tap_dance_key_count_ = ranges::TD_LAST - ranges::TD_FIRST + 1;
static uint16_t map_[reserved_tap_dance_key_count_];
static uint8_t dance_count_;
static uint8_t offset_;
static void updateDynamicTapDanceCache();
};
}
}
extern kaleidoscope::plugin::DynamicTapDance DynamicTapDance;
Loading…
Cancel
Save