diff --git a/README.md b/README.md index 9f5dc8a8..4c2b8024 100644 --- a/README.md +++ b/README.md @@ -5,184 +5,4 @@ [travis:image]: https://travis-ci.org/keyboardio/Kaleidoscope-Macros.svg?branch=master [travis:status]: https://travis-ci.org/keyboardio/Kaleidoscope-Macros -Macros are a standard feature on many keyboards and Kaleidoscope-powered ones -are no exceptions. Macros are a way to have a single key-press do a whole lot of -things under the hood: conventionally, macros play back a key sequence, but with -Kaleidoscope, there is much more we can do. Nevertheless, playing back a -sequence of events is still the primary use of macros. - -Playing back a sequence means that when we press a macro key, we can have it -play pretty much any sequence. It can type some text for us, or invoke a -complicated shortcut - the possibilities are endless! - -In Kaleidoscope, macros are implemented via this plugin. You can define upto 256 macros. - -## Using the plugin - -To use the plugin, we need to include the header, tell the firmware to `use` the -plugin, place macros on the keymap, and create a special handler function -(`macroAction`) that will tell the plugin what shall happen when macro keys are -pressed. It is best illustrated with an example: - -```c++ -#include -#include - -// Give a name to the macros! -enum { - MACRO_MODEL01, - MACRO_HELLO, - MACRO_SPECIAL, -}; - -// Somewhere in the keymap: -M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) - -// later in the Sketch: -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { - case MACRO_MODEL01: - return MACRODOWN(I(25), - D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), - T(Spacebar), - W(100), - T(0), T(1) ); - case MACRO_HELLO: - if (keyToggledOn(keyState)) { - return Macros.type(PSTR("Hello "), PSTR("world!")); - } - break; - case MACRO_SPECIAL: - if (keyToggledOn(keyState)) { - // Do something special - } - break; - } - return MACRO_NONE; -} - -KALEIDOSCOPE_INIT_PLUGINS(Macros); - -void setup() { - Kaleidoscope.setup (); -} -``` - -## Keymap markup - -### `M(id)` - -> Places a macro key on the keymap, with the `id` number (0 to 255) as identifier. Whenever this key -> has to be handled, the `macroAction` overrideable function will be called, -> with the identifier and key state as arguments. -> -> It is recommended to give a *name* to macro ids, by using an `enum`. - -## Plugin methods - -The plugin provides a `Macros` object, with the following methods and properties available: - -### `.play(macro)` - -> Plays back a macro, where a macro is a sequence created with the `MACRO()` -> helper discussed below. This method will be used by the plugin to play back -> the result of the `macroAction()` method, but is used rarely otherwise. -> -> The `macro` argument must be a sequence created with the `MACRO()` helper! - -### `.type(strings...)` - -> In cases where we only want to type some strings, it is far more convenient to -> use this method: we do not have to use the `MACRO()` helper, but just give -> this one a set of strings, and it will type them for us on the keyboard. We -> can use as many strings as we want, and all of them will be typed in order. -> -> Each string is limited to a sequence of printable ASCII characters. No -> international symbols, or unicode, or anything like it: just plain ASCII. -> -> Each of `strings` arguments must also reside in program memory, and the -> easiest way to do that is to wrap the string in a `PSTR()` helper. See the -> program code at the beginning of this documentation for an example! - -### `.row`, `.col` - -> The `row` and `col` properties describe the physical position a macro was -> triggered from if it was triggered by a key. The playback functions -> do not use these properties, but they are available, would one want to create -> a macro that needs to know which key triggered it. -> -> When the macro was not triggered by a key the value of these properties are -> unspecified. - -## Macro helpers - -Macros need to be able to simulate key down and key up events for any key - even -keys that may not be on the keymap otherwise. For this reason and others, we -need to define them in a special way, using the `MACRO` helper (or its -`MACRODOWN()` variant, see below): - -### `MACRO(steps...)` - -> Defines a macro, that is built up from `steps` (explained below). The plugin -> will iterate through the sequence, and re-play the steps in order. -> -> Note: In older versions of the Macros plugin, the sequence of steps had to end -> with a special step called END. This is no longer required. Existing macros -> that end with END will still work correctly, but new code should not use END; -> usage of END is deprecated. - -### `MACRODOWN(steps...)` - -> The same as the `MACRO()` helper above, but it will create a special sequence, -> where the steps are only played back when the triggering key was just pressed. -> That is, the macro will not be performed when the key is released, or held, or -> not pressed at all. -> -> Use this over `MACRO()` when you only want to perform an action when the key -> actuates, and no action should be taken when it is held, released, or when it -> is not pressed at all. For a lot of macros that emit a sequence without any -> other side effects, `MACRODOWN()` is usually the better choice. -> -> Can only be used from the `macroAction()` overrideable method. - -## `MACRO` steps - -Macro steps can be divided into two groups: - -### Delays - -* `I(millis)`: Sets the interval between steps to `millis`. By default, there is - no delay between steps, and they are played back as fast as possible. Useful - when we want to see the macro being typed, or need to slow it down, to allow - the host to process it. -* `W(millis)`: Waits for `millis` milliseconds. For dramatic effects. - -### Key events - -Key event steps have three variants: one that prefixes its argument with `Key_`, -one that does not, and a third that allows for a more compact - but also more -limited - representation. The first are the `D`, `U`, and `T` variants, the -second are `Dr`, `Ur`, and `Tr`, and the last variant are `Dc`, `Uc`, and `Tc`. -In most cases, one is likely use normal keys for the steps, so the `D`, `U`, and -`T` steps apply the `Key_` prefix. This allows us to write `MACRO(T(X))` instead -of `MACRO(Tr(Key_X))` - making the macro definition shorter, and more readable. - -The compact variant (`Dc`, `Uc`, and `Tc`) prefix the argument with `Key_` too, -but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and -as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are -not supported by this compact representation. - -* `D(key)`, `Dr(key)`, `Dc(key)`: Simulates a key being pressed (pushed down). -* `U(key)`, `Ur(key)`, `Uc(key)`: Simulates a key being released (going up). -* `T(key)`, `Tr(key)`, `Tc(key)`: Simulates a key being tapped (pressed first, then released). - -## Overrideable methods - -### `macroAction(macroIndex, keyState)` - -> The `macroAction` method is the brain of the macro support in Kaleidoscope: -> this function tells the plugin what sequence to play when given a macro index -> and a key state. -> -> It should return a macro sequence, or `MACRO_NONE` if nothing is to be played -> back. +See [doc/plugin/Macros.md](doc/plugin/Macros.md) for documentation. diff --git a/doc/plugin/Macros.md b/doc/plugin/Macros.md new file mode 100644 index 00000000..0fdd07b3 --- /dev/null +++ b/doc/plugin/Macros.md @@ -0,0 +1,183 @@ +# Kaleidoscope-Macros + +Macros are a standard feature on many keyboards and Kaleidoscope-powered ones +are no exceptions. Macros are a way to have a single key-press do a whole lot of +things under the hood: conventionally, macros play back a key sequence, but with +Kaleidoscope, there is much more we can do. Nevertheless, playing back a +sequence of events is still the primary use of macros. + +Playing back a sequence means that when we press a macro key, we can have it +play pretty much any sequence. It can type some text for us, or invoke a +complicated shortcut - the possibilities are endless! + +In Kaleidoscope, macros are implemented via this plugin. You can define upto 256 macros. + +## Using the plugin + +To use the plugin, we need to include the header, tell the firmware to `use` the +plugin, place macros on the keymap, and create a special handler function +(`macroAction`) that will tell the plugin what shall happen when macro keys are +pressed. It is best illustrated with an example: + +```c++ +#include +#include + +// Give a name to the macros! +enum { + MACRO_MODEL01, + MACRO_HELLO, + MACRO_SPECIAL, +}; + +// Somewhere in the keymap: +M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) + +// later in the Sketch: +const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { + switch (macroIndex) { + case MACRO_MODEL01: + return MACRODOWN(I(25), + D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), + T(Spacebar), + W(100), + T(0), T(1) ); + case MACRO_HELLO: + if (keyToggledOn(keyState)) { + return Macros.type(PSTR("Hello "), PSTR("world!")); + } + break; + case MACRO_SPECIAL: + if (keyToggledOn(keyState)) { + // Do something special + } + break; + } + return MACRO_NONE; +} + +KALEIDOSCOPE_INIT_PLUGINS(Macros); + +void setup() { + Kaleidoscope.setup (); +} +``` + +## Keymap markup + +### `M(id)` + +> Places a macro key on the keymap, with the `id` number (0 to 255) as identifier. Whenever this key +> has to be handled, the `macroAction` overrideable function will be called, +> with the identifier and key state as arguments. +> +> It is recommended to give a *name* to macro ids, by using an `enum`. + +## Plugin methods + +The plugin provides a `Macros` object, with the following methods and properties available: + +### `.play(macro)` + +> Plays back a macro, where a macro is a sequence created with the `MACRO()` +> helper discussed below. This method will be used by the plugin to play back +> the result of the `macroAction()` method, but is used rarely otherwise. +> +> The `macro` argument must be a sequence created with the `MACRO()` helper! + +### `.type(strings...)` + +> In cases where we only want to type some strings, it is far more convenient to +> use this method: we do not have to use the `MACRO()` helper, but just give +> this one a set of strings, and it will type them for us on the keyboard. We +> can use as many strings as we want, and all of them will be typed in order. +> +> Each string is limited to a sequence of printable ASCII characters. No +> international symbols, or unicode, or anything like it: just plain ASCII. +> +> Each of `strings` arguments must also reside in program memory, and the +> easiest way to do that is to wrap the string in a `PSTR()` helper. See the +> program code at the beginning of this documentation for an example! + +### `.row`, `.col` + +> The `row` and `col` properties describe the physical position a macro was +> triggered from if it was triggered by a key. The playback functions +> do not use these properties, but they are available, would one want to create +> a macro that needs to know which key triggered it. +> +> When the macro was not triggered by a key the value of these properties are +> unspecified. + +## Macro helpers + +Macros need to be able to simulate key down and key up events for any key - even +keys that may not be on the keymap otherwise. For this reason and others, we +need to define them in a special way, using the `MACRO` helper (or its +`MACRODOWN()` variant, see below): + +### `MACRO(steps...)` + +> Defines a macro, that is built up from `steps` (explained below). The plugin +> will iterate through the sequence, and re-play the steps in order. +> +> Note: In older versions of the Macros plugin, the sequence of steps had to end +> with a special step called END. This is no longer required. Existing macros +> that end with END will still work correctly, but new code should not use END; +> usage of END is deprecated. + +### `MACRODOWN(steps...)` + +> The same as the `MACRO()` helper above, but it will create a special sequence, +> where the steps are only played back when the triggering key was just pressed. +> That is, the macro will not be performed when the key is released, or held, or +> not pressed at all. +> +> Use this over `MACRO()` when you only want to perform an action when the key +> actuates, and no action should be taken when it is held, released, or when it +> is not pressed at all. For a lot of macros that emit a sequence without any +> other side effects, `MACRODOWN()` is usually the better choice. +> +> Can only be used from the `macroAction()` overrideable method. + +## `MACRO` steps + +Macro steps can be divided into two groups: + +### Delays + +* `I(millis)`: Sets the interval between steps to `millis`. By default, there is + no delay between steps, and they are played back as fast as possible. Useful + when we want to see the macro being typed, or need to slow it down, to allow + the host to process it. +* `W(millis)`: Waits for `millis` milliseconds. For dramatic effects. + +### Key events + +Key event steps have three variants: one that prefixes its argument with `Key_`, +one that does not, and a third that allows for a more compact - but also more +limited - representation. The first are the `D`, `U`, and `T` variants, the +second are `Dr`, `Ur`, and `Tr`, and the last variant are `Dc`, `Uc`, and `Tc`. +In most cases, one is likely use normal keys for the steps, so the `D`, `U`, and +`T` steps apply the `Key_` prefix. This allows us to write `MACRO(T(X))` instead +of `MACRO(Tr(Key_X))` - making the macro definition shorter, and more readable. + +The compact variant (`Dc`, `Uc`, and `Tc`) prefix the argument with `Key_` too, +but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and +as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are +not supported by this compact representation. + +* `D(key)`, `Dr(key)`, `Dc(key)`: Simulates a key being pressed (pushed down). +* `U(key)`, `Ur(key)`, `Uc(key)`: Simulates a key being released (going up). +* `T(key)`, `Tr(key)`, `Tc(key)`: Simulates a key being tapped (pressed first, then released). + +## Overrideable methods + +### `macroAction(macroIndex, keyState)` + +> The `macroAction` method is the brain of the macro support in Kaleidoscope: +> this function tells the plugin what sequence to play when given a macro index +> and a key state. +> +> It should return a macro sequence, or `MACRO_NONE` if nothing is to be played +> back. diff --git a/src/Kaleidoscope-Macros.h b/src/Kaleidoscope-Macros.h index a2fda6fd..0aa76885 100644 --- a/src/Kaleidoscope-Macros.h +++ b/src/Kaleidoscope-Macros.h @@ -16,65 +16,4 @@ #pragma once -#include - -#include "MacroKeyDefs.h" -#include "MacroSteps.h" - -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState); - -#if !defined(MAX_CONCURRENT_MACROS) -#define MAX_CONCURRENT_MACROS 8 -#endif - -struct MacroKeyEvent { - byte key_code; - byte key_id; - byte key_state; -}; - -class Macros_ : public kaleidoscope::Plugin { - public: - Macros_(void) {} - - static MacroKeyEvent active_macros[MAX_CONCURRENT_MACROS]; - static byte active_macro_count; - static void addActiveMacroKey(byte key_code, byte key_id, byte key_state) { - // If we've got too many active macros, give up: - if (active_macro_count >= MAX_CONCURRENT_MACROS) { - return; - } - active_macros[active_macro_count].key_code = key_code; - active_macros[active_macro_count].key_id = key_id; - active_macros[active_macro_count].key_state = key_state; - ++active_macro_count; - } - - kaleidoscope::EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState); - kaleidoscope::EventHandlerResult beforeReportingState(); - kaleidoscope::EventHandlerResult afterEachCycle(); - - void play(const macro_t *macro_p); - - /* What follows below, is a bit of template magic that allows us to use - Macros.type() with any number of arguments, without having to use a - sentinel. See the comments on Kaleidoscope.use() for more details - this is - the same trick. - */ - inline const macro_t *type() { - return MACRO_NONE; - } - const macro_t *type(const char *string); - template - const macro_t *type(const char *first, Strings&&... strings) { - type(first); - return type(strings...); - } - - static byte row, col; - - private: - Key lookupAsciiCode(uint8_t ascii_code); -}; - -extern Macros_ Macros; +#include "kaleidoscope/plugin/Macros.h" diff --git a/src/MacroKeyDefs.h b/src/kaleidoscope/plugin/MacroKeyDefs.h similarity index 100% rename from src/MacroKeyDefs.h rename to src/kaleidoscope/plugin/MacroKeyDefs.h diff --git a/src/MacroSteps.h b/src/kaleidoscope/plugin/MacroSteps.h similarity index 100% rename from src/MacroSteps.h rename to src/kaleidoscope/plugin/MacroSteps.h diff --git a/src/Kaleidoscope-Macros.cpp b/src/kaleidoscope/plugin/Macros.cpp similarity index 92% rename from src/Kaleidoscope-Macros.cpp rename to src/kaleidoscope/plugin/Macros.cpp index 985eed8b..de67e540 100644 --- a/src/Kaleidoscope-Macros.cpp +++ b/src/kaleidoscope/plugin/Macros.cpp @@ -22,6 +22,9 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { return MACRO_NONE; } +namespace kaleidoscope { +namespace plugin { + MacroKeyEvent Macros_::active_macros[]; byte Macros_::active_macro_count; byte Macros_::row, Macros_::col; @@ -95,6 +98,7 @@ void Macros_::play(const macro_t *macro_p) { delay(interval); } } + static const Key ascii_to_key_map[] PROGMEM = { // 0x21 - 0x30 LSHIFT(Key_1), @@ -201,23 +205,23 @@ const macro_t *Macros_::type(const char *string) { return MACRO_NONE; } -kaleidoscope::EventHandlerResult Macros_::onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState) { +EventHandlerResult Macros_::onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState) { if (mappedKey.flags != (SYNTHETIC | IS_MACRO)) - return kaleidoscope::EventHandlerResult::OK; + return EventHandlerResult::OK; byte key_id = (row * COLS) + col; addActiveMacroKey(mappedKey.keyCode, key_id, keyState); - return kaleidoscope::EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::EVENT_CONSUMED; } -kaleidoscope::EventHandlerResult Macros_::afterEachCycle() { +EventHandlerResult Macros_::afterEachCycle() { active_macro_count = 0; - return kaleidoscope::EventHandlerResult::OK; + return EventHandlerResult::OK; } -kaleidoscope::EventHandlerResult Macros_::beforeReportingState() { +EventHandlerResult Macros_::beforeReportingState() { for (byte i = 0; i < active_macro_count; ++i) { if (active_macros[i].key_id == 0xFF) { // i.e. UNKNOWN_KEYSWITCH_LOCATION @@ -231,7 +235,10 @@ kaleidoscope::EventHandlerResult Macros_::beforeReportingState() { active_macros[i].key_state); Macros.play(m); } - return kaleidoscope::EventHandlerResult::OK; + return EventHandlerResult::OK; +} + +} } -Macros_ Macros; +kaleidoscope::plugin::Macros_ Macros; diff --git a/src/kaleidoscope/plugin/Macros.h b/src/kaleidoscope/plugin/Macros.h new file mode 100644 index 00000000..fb514731 --- /dev/null +++ b/src/kaleidoscope/plugin/Macros.h @@ -0,0 +1,86 @@ +/* Kaleidoscope-Macros - Macro keys for Kaleidoscope. + * Copyright (C) 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 + +#include "MacroKeyDefs.h" +#include "MacroSteps.h" + +const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState); + +#if !defined(MAX_CONCURRENT_MACROS) +#define MAX_CONCURRENT_MACROS 8 +#endif + +struct MacroKeyEvent { + byte key_code; + byte key_id; + byte key_state; +}; + +namespace kaleidoscope { +namespace plugin { + +class Macros_ : public kaleidoscope::Plugin { + public: + Macros_(void) {} + + static MacroKeyEvent active_macros[MAX_CONCURRENT_MACROS]; + static byte active_macro_count; + static void addActiveMacroKey(byte key_code, byte key_id, byte key_state) { + // If we've got too many active macros, give up: + if (active_macro_count >= MAX_CONCURRENT_MACROS) { + return; + } + active_macros[active_macro_count].key_code = key_code; + active_macros[active_macro_count].key_id = key_id; + active_macros[active_macro_count].key_state = key_state; + ++active_macro_count; + } + + EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState); + EventHandlerResult beforeReportingState(); + EventHandlerResult afterEachCycle(); + + void play(const macro_t *macro_p); + + /* What follows below, is a bit of template magic that allows us to use + Macros.type() with any number of arguments, without having to use a + sentinel. See the comments on Kaleidoscope.use() for more details - this is + the same trick. + */ + inline const macro_t *type() { + return MACRO_NONE; + } + const macro_t *type(const char *string); + template + const macro_t *type(const char *first, Strings&&... strings) { + type(first); + return type(strings...); + } + + static byte row, col; + + private: + Key lookupAsciiCode(uint8_t ascii_code); +}; + +} +} + +extern kaleidoscope::plugin::Macros_ Macros;