Dynamic Macros plugin

This implements a new plugin for Dynamic (EEPROM-stored) macros. Unlike the
Macros plugin, these macros are stored in EEPROM, and can't run custom code,
only the steps outlined in the Macros documentation.

The plugin provides two Focus commands (`macros.map` and `macros.trigger`) to
get or set the dynamic macros, and to trigger one without having to place them
on the keymap.

Fixes #370.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/687/head
Gergely Nagy 5 years ago
parent 0094832b12
commit 357c0c4b65
No known key found for this signature in database
GPG Key ID: AC1E90BAC433F68F

@ -115,6 +115,10 @@ To make it easier to port Kaleidoscope, we introduced the [ATMegaKeyboard](doc/p
## New plugins ## New plugins
### DynamicMacros
The [DynamicMacros](doc/plugin/DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis.
### IdleLEDs ### IdleLEDs
The [IdleLEDs](doc/plugin/IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event. The [IdleLEDs](doc/plugin/IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event.

@ -0,0 +1,100 @@
# Kaleidoscope-DynamicMacros
Dynamic macros are similar to [Macros][plugin:macros], but unlike them, they can
be re-defined without compiling and flashing new firmware: one can change
dynamic macros via [Focus][plugin:focus], using a tool like
[Chrysalis][chrysalis].
[plugin:macros]: Macros.md
[plugin:focus]: FocusSerial.md
[chrysalis]: https://github.com/keyboardio/Chrysalis
Dynamic macros come with certain limitations, however: unlike the built-in
macros, dynamic ones do not support running custom code, they can only play back
a sequence of events (keys, mousekeys, etc), and do so whenever one presses the
dynamic macro key.
You can define up to 32 dynamic macros, there is no limit on their length,
except the amount of storage available on the keyboard.
## 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 macros. This is best illustrated with
an example:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-EEPROMSettings.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-DynamicMacros.h>
KALEIDOSCOPE_INIT_PLUGINS(
EEPROMSettings,
Focus,
DynamicMacros
);
void setup() {
Kaleidoscope.setup();
DynamicMacros.reserve_storage(128);
}
```
## Keymap markup
### `DM(id)`
> Places a dynamic macro key on the keymap, with the `id` number (0 to 31) as
> identifier. Pressing the key will immediately run the associated dynamic
> macro.
## Plugin methods
The plugin provides a `DynamicMacros` object, with the following methods and properties available:
### `.reserve_storage(size)`
> Reserves `size` bytes of storage for dynamic macros. This must be called from
> the `setup()` method of your sketch, otherwise dynamic macros will not
> function.
### `.play(macro_id)`
> Plays back a macro, specified by `macro_id`.
## `MACRO` steps
The plugin supports the same [macro steps][doc:steps] as the Macros plugin,
please refer to the documentation therein.
[doc:steps]: Macros.md#macro-steps
## Focus commands
The plugin provides two Focus commands: `macros.map` and `macros.trigger`.
### `macros.map [macros...]`
> Without arguments, displays all the stored macros. Each macro is terminated by
> an end marker (`MACRO_ACTION_END`), and the last macro is followed by an
> additional marker. The plugin will send back the entire dynamic macro storage
> space, even the data after the final marker.
> With arguments, it replaces the current set of dynamic macros with the newly
> given ones. Macros are terminated by an end marker, and the last macro must be
> terminated by an additional one.
> In both cases, the data sent or expected is a sequence of 8-bit values, a
> memory dump.
### `macros.trigger macro_id`
> Runs the dynamic macro associated with `macro_id` immediately. This can be
> used to test macros without having to place them on the keymap.
## Dependencies
* [Kaleidoscope-EEPROM-Settings](EEPROM-Settings.md)
* [Kaleidoscope-FocusSerial](FocusSerial.md)

@ -0,0 +1,61 @@
/* -*- mode: c++ -*-
* DynamicMacros - Dynamic macro support for Kaleidoscope.
* Copyright (C) 2019 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-EEPROM-Settings.h>
#include <Kaleidoscope-EEPROM-Keymap.h>
#include <Kaleidoscope-DynamicMacros.h>
#include <Kaleidoscope-FocusSerial.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,
Key_skip,
DM(0), 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_skip),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(
EEPROMSettings,
EEPROMKeymap,
DynamicMacros,
Focus
);
void setup() {
Kaleidoscope.setup();
EEPROMKeymap.setup(1);
DynamicMacros.reserve_storage(128);
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,19 @@
/* DynamicMacros - Dynamic macro support for Kaleidoscope.
* Copyright (C) 2019 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/DynamicMacros.h"

@ -49,6 +49,8 @@ enum : uint16_t {
SC_LAST, SC_LAST,
REDIAL, REDIAL,
TURBO, TURBO,
DYNAMIC_MACRO_FIRST,
DYNAMIC_MACRO_LAST = DYNAMIC_MACRO_FIRST + 31,
SAFE_START, SAFE_START,
KALEIDOSCOPE_SAFE_START = SAFE_START KALEIDOSCOPE_SAFE_START = SAFE_START

@ -0,0 +1,259 @@
/* DynamicMacros - Dynamic macro support for Kaleidoscope.
* Copyright (C) 2019 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-DynamicMacros.h"
#include "kaleidoscope/hid.h"
#include "Kaleidoscope-FocusSerial.h"
using namespace kaleidoscope::ranges;
namespace kaleidoscope {
namespace plugin {
uint16_t DynamicMacros::storage_base_;
uint16_t DynamicMacros::storage_size_;
uint16_t DynamicMacros::map_[];
static void playMacroKeyswitchEvent(Key key, uint8_t keyswitch_state, bool explicit_report) {
handleKeyswitchEvent(key, UnknownKeyswitchLocation, keyswitch_state | INJECTED);
if (explicit_report)
return;
kaleidoscope::hid::sendKeyboardReport();
kaleidoscope::hid::sendMouseReport();
}
static void playKeyCode(Key key, uint8_t keyStates, bool explicit_report) {
if (keyIsPressed(keyStates)) {
playMacroKeyswitchEvent(key, IS_PRESSED, explicit_report);
}
if (keyWasPressed(keyStates)) {
playMacroKeyswitchEvent(key, WAS_PRESSED, explicit_report);
}
}
static void readKeyCodeAndPlay(uint16_t pos, uint8_t flags, uint8_t keyStates, bool explicit_report) {
Key key;
key.flags = flags;
key.keyCode = EEPROM.read(pos++);
playKeyCode(key, keyStates, explicit_report);
}
void DynamicMacros::updateDynamicMacroCache(void) {
uint16_t pos = storage_base_;
uint8_t current_id = 0;
macro_t macro = MACRO_ACTION_END;
bool previous_macro_ended = false;
map_[0] = 0;
while (pos < storage_base_ + storage_size_) {
macro = EEPROM.read(pos++);
switch (macro) {
case MACRO_ACTION_STEP_EXPLICIT_REPORT:
case MACRO_ACTION_STEP_IMPLICIT_REPORT:
case MACRO_ACTION_STEP_SEND_REPORT:
previous_macro_ended = false;
break;
case MACRO_ACTION_STEP_INTERVAL:
case MACRO_ACTION_STEP_WAIT:
case MACRO_ACTION_STEP_KEYCODEDOWN:
case MACRO_ACTION_STEP_KEYCODEUP:
case MACRO_ACTION_STEP_TAPCODE:
previous_macro_ended = false;
pos++;
break;
case MACRO_ACTION_STEP_KEYDOWN:
case MACRO_ACTION_STEP_KEYUP:
case MACRO_ACTION_STEP_TAP:
previous_macro_ended = false;
pos += 2;
break;
case MACRO_ACTION_STEP_TAP_SEQUENCE: {
previous_macro_ended = false;
uint8_t keyCode, flags;
do {
flags = EEPROM.read(pos++);
keyCode = EEPROM.read(pos++);
} while (!(flags == 0 && keyCode == 0));
break;
}
case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: {
previous_macro_ended = false;
uint8_t keyCode, flags;
do {
keyCode = EEPROM.read(pos++);
} while (keyCode != 0);
break;
}
case MACRO_ACTION_END:
map_[++current_id] = pos - storage_base_;
if (previous_macro_ended)
return;
previous_macro_ended = true;
break;
}
}
}
void DynamicMacros::play(uint8_t macro_id) {
macro_t macro = MACRO_ACTION_END;
uint8_t interval = 0;
uint8_t flags;
bool explicit_report = false;
uint16_t pos;
pos = storage_base_ + map_[macro_id];
while (true) {
switch (macro = EEPROM.read(pos++)) {
case MACRO_ACTION_STEP_EXPLICIT_REPORT:
explicit_report = true;
break;
case MACRO_ACTION_STEP_IMPLICIT_REPORT:
explicit_report = false;
break;
case MACRO_ACTION_STEP_SEND_REPORT:
kaleidoscope::hid::sendKeyboardReport();
kaleidoscope::hid::sendMouseReport();
break;
case MACRO_ACTION_STEP_INTERVAL:
interval = EEPROM.read(pos++);
break;
case MACRO_ACTION_STEP_WAIT: {
uint8_t wait = EEPROM.read(pos++);
delay(wait);
break;
}
case MACRO_ACTION_STEP_KEYDOWN:
flags = EEPROM.read(pos++);
readKeyCodeAndPlay(pos++, flags, IS_PRESSED, explicit_report);
break;
case MACRO_ACTION_STEP_KEYUP:
flags = EEPROM.read(pos++);
readKeyCodeAndPlay(pos++, flags, WAS_PRESSED, explicit_report);
break;
case MACRO_ACTION_STEP_TAP:
flags = EEPROM.read(pos++);
readKeyCodeAndPlay(pos++, flags, IS_PRESSED | WAS_PRESSED, false);
break;
case MACRO_ACTION_STEP_KEYCODEDOWN:
readKeyCodeAndPlay(pos++, 0, IS_PRESSED, explicit_report);
break;
case MACRO_ACTION_STEP_KEYCODEUP:
readKeyCodeAndPlay(pos++, 0, WAS_PRESSED, explicit_report);
break;
case MACRO_ACTION_STEP_TAPCODE:
readKeyCodeAndPlay(pos++, 0, IS_PRESSED | WAS_PRESSED, false);
break;
case MACRO_ACTION_STEP_TAP_SEQUENCE: {
uint8_t keyCode;
do {
flags = EEPROM.read(pos++);
keyCode = EEPROM.read(pos++);
playKeyCode(Key(keyCode, flags), IS_PRESSED | WAS_PRESSED, false);
delay(interval);
} while (!(flags == 0 && keyCode == 0));
break;
}
case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: {
uint8_t keyCode;
do {
keyCode = EEPROM.read(pos++);
playKeyCode(Key(keyCode, 0), IS_PRESSED | WAS_PRESSED, false);
delay(interval);
} while (keyCode != 0);
break;
}
case MACRO_ACTION_END:
default:
return;
}
delay(interval);
}
}
EventHandlerResult DynamicMacros::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) {
if (mappedKey.raw < DYNAMIC_MACRO_FIRST || mappedKey.raw > DYNAMIC_MACRO_LAST)
return EventHandlerResult::OK;
if (keyToggledOn(keyState)) {
play(mappedKey.raw - DYNAMIC_MACRO_FIRST);
}
return EventHandlerResult::EVENT_CONSUMED;
}
EventHandlerResult DynamicMacros::onFocusEvent(const char *command) {
if (::Focus.handleHelp(command, PSTR("macros.map\nmacros.trigger")))
return EventHandlerResult::OK;
if (strncmp_P(command, PSTR("macros."), 7) != 0)
return EventHandlerResult::OK;
if (strcmp_P(command + 7, PSTR("map")) == 0) {
if (::Focus.isEOL()) {
for (uint16_t i = 0; i < storage_size_; i++) {
uint8_t b;
b = EEPROM.read(storage_base_ + i);
::Focus.send(b);
}
} else {
uint16_t pos = 0;
while (!::Focus.isEOL()) {
uint8_t b;
::Focus.read(b);
EEPROM.update(storage_base_ + pos++, b);
}
updateDynamicMacroCache();
}
}
if (strcmp_P(command + 7, PSTR("trigger")) == 0) {
uint8_t id = 0;
::Focus.read(id);
play(id);
}
return EventHandlerResult::EVENT_CONSUMED;
}
void DynamicMacros::reserve_storage(uint16_t size) {
storage_base_ = ::EEPROMSettings.requestSlice(size);
storage_size_ = size;
updateDynamicMacroCache();
}
}
}
kaleidoscope::plugin::DynamicMacros DynamicMacros;

@ -0,0 +1,51 @@
/* DynamicMacros - Dynamic macro support for Kaleidoscope.
* Copyright (C) 2019 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-EEPROM-Settings.h>
#include <Kaleidoscope-Ranges.h>
#include "kaleidoscope/plugin/Macros/MacroSteps.h"
#define DM(n) Key(kaleidoscope::ranges::DYNAMIC_MACRO_FIRST + n)
namespace kaleidoscope {
namespace plugin {
class DynamicMacros : public kaleidoscope::Plugin {
public:
DynamicMacros(void) {}
EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState);
EventHandlerResult onFocusEvent(const char *command);
static void reserve_storage(uint16_t size);
void play(uint8_t seq_id);
private:
static uint16_t storage_base_;
static uint16_t storage_size_;
static uint16_t map_[31];
static void updateDynamicMacroCache(void);
};
}
}
extern kaleidoscope::plugin::DynamicMacros DynamicMacros;
Loading…
Cancel
Save