Complete redesign of the plugin

Instead of using a list of left/right-hand states and an overrideable global
callback, use a map of action and key-list pairs. This makes the plugin much
more portable, does not require any hardware-specific knowledge within the
plugin, and does not require us to treat the hands separately.

This in turn, results in a friendlier user interface, at the cost of limiting
the maximum length of a combination to five keys. A small price to pay.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/365/head
Gergely Nagy 7 years ago
parent 61d70b5cac
commit 5a6e0fa12a

@ -18,88 +18,54 @@ This can be used to tie complex actions to key chords.
## Using the extension ## Using the extension
To use the extension, we must include the header, create an array of combos we To use the extension, we must include the header, create actions for the magic
want to work with, let the plugin know we want to work with those, and then use combos we want to trigger, and set up a mapping:
a special function to handle the combos:
```c++ ```c++
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
#include <Kaleidoscope-Macros.h> #include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-MagicCombo.h> #include <Kaleidoscope-MagicCombo.h>
void magicComboActions(uint8_t combo_index, uint32_t left_hand, uint32_t right_hand) { enum { KIND_OF_MAGIC };
switch (combo_index) {
case 0: void kindOfMagic(uint8_t combo_index) {
Macros.type(PSTR("It's a kind of magic!")); Macros.type(PSTR("It's a kind of magic!"));
break;
}
} }
static const kaleidoscope::MagicCombo::combo_t magic_combos[] PROGMEM = { USE_MAGIC_COMBOS(
{ [KIND_OF_MAGIC] = {
R3C6, // left palm key .action = kindOfMagic,
R3C9 // right palm key .keys = {R3C6, R3C9} // Left Fn + Right Fn
}, });
{0, 0}
};
KALEIDOSCOPE_INIT_PLUGINS(MagicCombo, Macros); KALEIDOSCOPE_INIT_PLUGINS(MagicCombo, Macros);
void setup() { void setup() {
Kaleidoscope.setup(); Kaleidoscope.setup();
MagicCombo.magic_combos = magic_combos;
} }
``` ```
The combo list **must** reside in `PROGMEM`, and is a list of tuples. Each It is recommended to use the `RxCy` macros of the core firmware to set the keys
element in the array has two fields: the left hand state, and the right hand that are part of a combination.
state upon which to trigger the custom action. Both of these are bit fields,
each bit set tells the extension that the key with that index must be held for
the action to trigger. It is recommended to use the `RxCy` macros of the core
`KaleidoscopeFirmware`, and *or* them together to form a bitfield.
To see how the `RxCy` coordinates correspond to the physical keys of your
keyboard, you'll have to consult the documentation for the keyboard.
Below, you can find a diagram showing the layout for the Keyboardio Model 01.
The combo list **must** end with an element containing zero values for both the ## Plugin properties
left and the right halves.
## Extension methods
The extension provides a `MagicCombo` singleton object, with the following The extension provides a `MagicCombo` singleton object, with the following
methods and properties: property:
### `.magic_combos`
> Setting this property lets the plugin know which combinations of key presses
> we are interested in. If any of these are found active, the
> `magicComboActions()` function will be called.
### `.min_interval` ### `.min_interval`
> Restrict the magic action to fire at most once every `minInterval` > Restrict the magic action to fire at most once every `min_interval`
> milliseconds. > milliseconds.
> >
> Defaults to 500. > Defaults to 500.
## Overrideable methods ## Plugin callbacks
Whenever an combination is found to be held, the extension will trigger an
action, in each scan cycle until the keys remain held. This is done by calling
the overrideable `magicComboActions` function:
### `magicComboActions(combo_index, left_hand, right_hand)`
> Called whenever a combination is found to be held. The function by default Whenever a combination is found to be held, the plugin will trigger the
> does nothing, and it is recommended to override it from within the Sketch. specified action, which is just a regular method with a single `uint8_t`
> argument: the index of the magic combo. This function will be called repeatedly
> The first argument will be the index in the combo list, the other two are the (every `min_interval` milliseconds) while the combination is held.
> key states on the left and right halves, respectively.
>
> Plugins that build upon this extensions *should not* override this function,
> but provide helpers that can be called from it. An override should only happen
> in the Sketch.
## Further reading ## Further reading

@ -20,21 +20,15 @@
#include <Kaleidoscope-Macros.h> #include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-MagicCombo.h> #include <Kaleidoscope-MagicCombo.h>
void magicComboActions(uint8_t combo_index, uint32_t left_hand, uint32_t right_hand) { enum {
switch (combo_index) { KIND_OF_MAGIC
case 0: };
void kindOfMagic(uint8_t combo_index) {
Macros.type(PSTR("It's a kind of magic!")); Macros.type(PSTR("It's a kind of magic!"));
break;
}
} }
static const kaleidoscope::MagicCombo::combo_t magic_combos[] PROGMEM = { USE_MAGIC_COMBOS([KIND_OF_MAGIC] = {.action = kindOfMagic, .keys = {R3C6, R3C9}});
{
R3C6, // left palm key
R3C9 // right palm key
},
{0, 0}
};
// *INDENT-OFF* // *INDENT-OFF*
const Key keymaps[][ROWS][COLS] PROGMEM = { const Key keymaps[][ROWS][COLS] PROGMEM = {
@ -62,8 +56,6 @@ KALEIDOSCOPE_INIT_PLUGINS(MagicCombo, Macros);
void setup() { void setup() {
Kaleidoscope.setup(); Kaleidoscope.setup();
MagicCombo.magic_combos = magic_combos;
} }
void loop() { void loop() {

@ -18,43 +18,36 @@
#include <Kaleidoscope-MagicCombo.h> #include <Kaleidoscope-MagicCombo.h>
#if defined(ARDUINO_AVR_MODEL01)
#define LEFTHANDSTATE KeyboardHardware.leftHandState
#define RIGHTHANDSTATE KeyboardHardware.rightHandState
#endif
#if defined(ARDUINO_AVR_SHORTCUT)
#define LEFTHANDSTATE KeyboardHardware.scanner.leftHandState
#define RIGHTHANDSTATE KeyboardHardware.scanner.rightHandState
#endif
namespace kaleidoscope { namespace kaleidoscope {
const MagicCombo::combo_t *MagicCombo::magic_combos;
uint16_t MagicCombo::min_interval = 500; uint16_t MagicCombo::min_interval = 500;
uint32_t MagicCombo::end_time_; uint32_t MagicCombo::end_time_;
EventHandlerResult MagicCombo::beforeReportingState() { EventHandlerResult MagicCombo::beforeReportingState() {
if (!magic_combos) for (byte i = 0; i < magiccombo::combos_length; i++) {
return EventHandlerResult::OK; bool match = true;
byte j;
for (byte i = 0;; i++) { for (j = 0; j < MAX_COMBO_LENGTH; j++) {
combo_t combo; int8_t comboKey = pgm_read_byte(&(magiccombo::combos[i].keys[j]));
combo.left_hand = pgm_read_dword(&(magic_combos[i].left_hand)); if (comboKey == 0)
combo.right_hand = pgm_read_dword(&(magic_combos[i].right_hand)); break;
if (combo.left_hand == 0 && combo.right_hand == 0) match &= KeyboardHardware.isKeyswitchPressed(comboKey);
if (!match)
break; break;
}
if (LEFTHANDSTATE.all == combo.left_hand && if (j != KeyboardHardware.pressedKeyswitchCount())
RIGHTHANDSTATE.all == combo.right_hand) { match = false;
if (millis() >= end_time_) {
magicComboActions(i, combo.left_hand, combo.right_hand); if (match && (millis() >= end_time_)) {
ComboAction action = (ComboAction) pgm_read_ptr(&(magiccombo::combos[i].action));
(*action)(i);
end_time_ = millis() + min_interval; end_time_ = millis() + min_interval;
} }
break;
}
} }
return EventHandlerResult::OK; return EventHandlerResult::OK;
@ -75,7 +68,4 @@ void MagicCombo::legacyLoopHook(bool is_post_clear) {
}; };
__attribute__((weak)) void magicComboActions(uint8_t comboIndex, uint32_t left_hand, uint32_t right_hand) {
}
kaleidoscope::MagicCombo MagicCombo; kaleidoscope::MagicCombo MagicCombo;

@ -20,17 +20,29 @@
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
#define MAX_COMBO_LENGTH 5
#define USE_MAGIC_COMBOS(...) \
namespace kaleidoscope { \
namespace magiccombo { \
const kaleidoscope::MagicCombo::Combo combos[] PROGMEM = {__VA_ARGS__}; \
\
const uint8_t combos_length = sizeof(combos) / sizeof(*combos); \
} \
}
namespace kaleidoscope { namespace kaleidoscope {
class MagicCombo : public kaleidoscope::Plugin { class MagicCombo : public kaleidoscope::Plugin {
public: public:
typedef void (*ComboAction)(uint8_t combo_index);
typedef struct { typedef struct {
uint32_t left_hand, right_hand; ComboAction action;
} combo_t; int8_t keys[MAX_COMBO_LENGTH + 1];
} Combo;
MagicCombo(void) {} MagicCombo(void) {}
static const combo_t *magic_combos;
static uint16_t min_interval; static uint16_t min_interval;
EventHandlerResult beforeReportingState(); EventHandlerResult beforeReportingState();
@ -45,8 +57,11 @@ class MagicCombo : public kaleidoscope::Plugin {
static uint32_t end_time_; static uint32_t end_time_;
}; };
namespace magiccombo {
extern const MagicCombo::Combo combos[];
extern const uint8_t combos_length;
} }
void magicComboActions(uint8_t combo_index, uint32_t left_hand, uint32_t right_hand); }
extern kaleidoscope::MagicCombo MagicCombo; extern kaleidoscope::MagicCombo MagicCombo;

Loading…
Cancel
Save