diff --git a/doc/model01_coordinates.png b/doc/model01_coordinates.png new file mode 100644 index 00000000..00cfb979 Binary files /dev/null and b/doc/model01_coordinates.png differ diff --git a/doc/plugin/MagicCombo.md b/doc/plugin/MagicCombo.md new file mode 100644 index 00000000..2e240222 --- /dev/null +++ b/doc/plugin/MagicCombo.md @@ -0,0 +1,182 @@ +# Kaleidoscope-MagicCombo + +The `MagicCombo` extension provides a way to perform custom actions when a +particular set of keys are held down together. The functionality assigned to +these keys are not changed, and the custom action triggers as long as all keys +within the set are pressed. The order in which they were pressed do not matter. + +This can be used to tie complex actions to key chords. + +## Using the extension + +To use the extension, we must include the header, create actions for the magic +combos we want to trigger, and set up a mapping: + +```c++ +#include +#include +#include + +enum { KIND_OF_MAGIC }; + +void kindOfMagic(uint8_t combo_index) { + Macros.type(PSTR("It's a kind of magic!")); +} + +USE_MAGIC_COMBOS( +[KIND_OF_MAGIC] = { + .action = kindOfMagic, + .keys = {R3C6, R3C9} // Left Fn + Right Fn +}); + +KALEIDOSCOPE_INIT_PLUGINS(MagicCombo, Macros); + +void setup() { + Kaleidoscope.setup(); +} +``` + +It is recommended to use the `RxCy` macros of the core firmware to set the keys +that are part of a combination. + +## Plugin properties + +The extension provides a `MagicCombo` singleton object, with the following +property: + +### `.min_interval` + +> Restrict the magic action to fire at most once every `min_interval` +> milliseconds. +> +> Defaults to 500. + +## Plugin callbacks + +Whenever a combination is found to be held, the plugin will trigger the +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 +(every `min_interval` milliseconds) while the combination is held. + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + +`RxCy` coordinates for a Model01: + +![rxcy layout](../model01_coordinates.png) + + [plugin:example]: ../../examples/MagicCombo/MagicCombo.ino + +## Upgrading + +To make `MagicCombo` more portable, and easier to use, we had to break the API +previously provided, there was no way to maintain backwards compatibility. This +document is an attempt at guiding you through the process of migrating from the +earlier API to the current one. + +Migration should be a straightforward process, but if you get stuck, please feel +free to [open an issue][gh:issues], or start a thread on the [forums][forums], +and we'll help you with it. + + [gh:issues]: https://github.com/keyboardio/Kaleidoscope/issues + [forums]: https://community.keyboard.io/ + +## The old API + +```c++ +void magicComboActions(uint8_t combo_index, uint32_t left_hand, uint32_t right_hand) { + switch (combo_index) { + case 0: + Macros.type(PSTR("It's a kind of magic!")); + break; + } +} + +static const kaleidoscope::MagicCombo::combo_t magic_combos[] PROGMEM = { + { + R3C6, // left palm key + R3C9 // right palm key + }, + {0, 0} +}; + +void setup() { + Kaleidoscope.setup(); + + MagicCombo.magic_combos = magic_combos; +} +``` + +Previously, we used a global, overrideable function (`magicComboActions`) to run +the actions of all magic combos, similar to how macros are set up to work. +Unlike macros, magic combos can't be defined in the keymap, due to technical +reasons, so we had to use a separate list - `magic_combos` in our example. We +also needed to tell `MagicCombo` to use this list, which is what we've done in +`setup()`. + +## The new API + +```c++ +void kindOfMagic(uint8_t combo_index) { + Macros.type(PSTR("It's a kind of magic!")); +} + +USE_MAGIC_COMBOS({ + .action = kindOfMagic, + .keys = {R3C6, R3C9} // Left Fn + Right Fn +}); +``` + +The new API is much shorter, and is inspired by the way the [Leader][leader] +plugin works: instead of having a list, and a dispatching function like +`magicComboActions`, we include the action method in the list too! + + [leader]: Leader.md + +We also don't make a difference between left- and right-hand anymore, you can +just list keys for either in the same list. This will be very handy for +non-split keyboards. + +## Migration + +First of all, we'll need to split up `magicComboActions` into separate +functions. Each function should have a unique name, but their shape is always +the same: + +```c++ +void someFunction(uint8_t combo_index) { + // Do some action here +} +``` + +Copy the body of each `case` statement of `magicComboActions`, and copy them one +by one into appropriately named functions of the above shape. You can name your +functions anything you want, the only constraint is that they need to be valid +C++ function names. The plugin itself does nothing with the name, we'll +reference them later in the `USE_MAGIC_COMBOS` helper macro. + +Once `magicComboActions` is split up, we need to migrate the `magic_combos` list +to the new format. That list had to be terminated by a `{0, 0}` entry, the new +method does not require such a sentinel at the end. + +For each entry in `magic_combos`, add an entry to `USE_MAGIC_COMBOS`, with the +following structure: + +```c++ +{.action = theActionFunction, + .keys = { /* list of keys */ }} +``` + +The list of keys are the same `RxCy` constants you used for `magic_combos`, with +the left- and right hands combined. The action, `theActionFunction`, is the +function you extracted the magic combo action to. It's the function that has the +same body as the `case` statement in `magicComboActions` had. + +And this is all there is to it. + +If your actions made use of the `left_hand` or `right_hand` arguments of +`magicComboActions`, the same information is still available. But that's a bit +more involved to get to, out of scope for this simple migration guide. Please +open an issue, or ask for help on the forums, and we'll help you. diff --git a/examples/MagicCombo/MagicCombo.ino b/examples/MagicCombo/MagicCombo.ino new file mode 100644 index 00000000..7a45ccbb --- /dev/null +++ b/examples/MagicCombo/MagicCombo.ino @@ -0,0 +1,62 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-MagicCombo -- Magic combo framework + * 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 . + */ + +#include +#include +#include + +enum { + KIND_OF_MAGIC +}; + +void kindOfMagic(uint8_t combo_index) { + Macros.type(PSTR("It's a kind of magic!")); +} + +USE_MAGIC_COMBOS([KIND_OF_MAGIC] = {.action = kindOfMagic, .keys = {R3C6, R3C9}}); + +// *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, + Key_NoKey, + + 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, + Key_NoKey), +}; +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(MagicCombo, Macros); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/src/Kaleidoscope-MagicCombo.h b/src/Kaleidoscope-MagicCombo.h new file mode 100644 index 00000000..ed5c3699 --- /dev/null +++ b/src/Kaleidoscope-MagicCombo.h @@ -0,0 +1,20 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-MagicCombo -- Magic combo framework + * 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 . + */ + +#pragma once + +#include diff --git a/src/kaleidoscope/plugin/MagicCombo.cpp b/src/kaleidoscope/plugin/MagicCombo.cpp new file mode 100644 index 00000000..3dcd3ac5 --- /dev/null +++ b/src/kaleidoscope/plugin/MagicCombo.cpp @@ -0,0 +1,59 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-MagicCombo -- Magic combo framework + * 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 . + */ + +#include + +namespace kaleidoscope { +namespace plugin { + +uint16_t MagicCombo::min_interval = 500; +uint32_t MagicCombo::end_time_; + +EventHandlerResult MagicCombo::beforeReportingState() { + for (byte i = 0; i < magiccombo::combos_length; i++) { + bool match = true; + byte j; + + for (j = 0; j < MAX_COMBO_LENGTH; j++) { + int8_t comboKey = pgm_read_byte(&(magiccombo::combos[i].keys[j])); + + if (comboKey == 0) + break; + + match &= KeyboardHardware.isKeyswitchPressed(comboKey); + if (!match) + break; + } + + if (j != KeyboardHardware.pressedKeyswitchCount()) + match = false; + + if (match && (millis() >= end_time_)) { + ComboAction action = (ComboAction) pgm_read_ptr(&(magiccombo::combos[i].action)); + + (*action)(i); + end_time_ = millis() + min_interval; + } + } + + return EventHandlerResult::OK; +} + +} +} + +kaleidoscope::plugin::MagicCombo MagicCombo; diff --git a/src/kaleidoscope/plugin/MagicCombo.h b/src/kaleidoscope/plugin/MagicCombo.h new file mode 100644 index 00000000..35706c26 --- /dev/null +++ b/src/kaleidoscope/plugin/MagicCombo.h @@ -0,0 +1,84 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-MagicCombo -- Magic combo framework + * 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 . + */ + +#pragma once + +#include + +#define MAX_COMBO_LENGTH 5 + +#define USE_MAGIC_COMBOS(...) \ + namespace kaleidoscope { \ + namespace plugin { \ + namespace magiccombo { \ + const kaleidoscope::plugin::MagicCombo::Combo combos[] PROGMEM = \ + {__VA_ARGS__}; \ + \ + const uint8_t combos_length = sizeof(combos) / sizeof(*combos); \ + } \ + } \ + } + +#define _MAGICCOMBO_API_CHANGE \ + "The MagicCombo API changed in an incompatible way, you will need to\n" \ + "upgrade.\n" \ + "\n" \ + "Please see the `UPGRADING.md` document shipped with the source:\n" \ + " https://github.com/keyboardio/Kaleidoscope-MagicCombo/blob/master/UPGRADING.md" + +namespace kaleidoscope { +namespace plugin { + +class MagicCombo : public kaleidoscope::Plugin { + public: + typedef void (*ComboAction)(uint8_t combo_index); + typedef struct { + ComboAction action; + int8_t keys[MAX_COMBO_LENGTH + 1]; + } Combo; + typedef struct combo_t { + uint32_t left_hand, right_hand; + + template + combo_t(T l, T r) { + static_assert(sizeof(T) < 0, _DEPRECATE(_MAGICCOMBO_API_CHANGE)); + } + } combo_t; + + MagicCombo(void) {} + + static const combo_t *magic_combos; + static uint16_t min_interval; + + EventHandlerResult beforeReportingState(); + + private: + static uint32_t end_time_; +}; + +namespace magiccombo { +extern const MagicCombo::Combo combos[]; +extern const uint8_t combos_length; +} + +} + +// Backward compatibility +typedef plugin::MagicCombo MagicCombo; +} + +extern kaleidoscope::plugin::MagicCombo MagicCombo;