Adapt Macros plugin to KeyEvent handlers

Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
pull/1024/head
Michael Richters 4 years ago
parent ba383a18ad
commit 619edaa299
No known key found for this signature in database
GPG Key ID: 1288FD13E4EEF0C0

@ -34,8 +34,8 @@ enum {
M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL)
// later in the Sketch: // later in the Sketch:
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
switch (macroIndex) { switch (macro_id) {
case MACRO_MODEL01: case MACRO_MODEL01:
return MACRODOWN(I(25), return MACRODOWN(I(25),
D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L),
@ -43,12 +43,12 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
W(100), W(100),
T(0), T(1) ); T(0), T(1) );
case MACRO_HELLO: case MACRO_HELLO:
if (keyToggledOn(keyState)) { if (keyToggledOn(event.state)) {
return Macros.type(PSTR("Hello "), PSTR("world!")); return Macros.type(PSTR("Hello "), PSTR("world!"));
} }
break; break;
case MACRO_SPECIAL: case MACRO_SPECIAL:
if (keyToggledOn(keyState)) { if (keyToggledOn(event.state)) {
// Do something special // Do something special
} }
break; break;
@ -101,15 +101,28 @@ The plugin provides a `Macros` object, with the following methods and properties
> easiest way to do that is to wrap the string in a `PSTR()` helper. See 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! > program code at the beginning of this documentation for an example!
### `.row`, `.col` ### `.press(key)`/`.release(key)`
> The `row` and `col` properties describe the physical position a macro was > Used in `Macros.play()`, these methods press virtual keys in a small
> triggered from if it was triggered by a key. The playback functions > supplemental `Key` array for the purpose of keeping keys active for complex
> do not use these properties, but they are available, would one want to create > macro sequences where it's important to have overlapping key presses.
> 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 > `Macros.press(key)` sends a key press event, and will keep that virtual key
> unspecified. > active until either `Macros.release(key)` is called, or a Macros key is
> released. If you use `Macros.press(key)` in a macro, but also change the value
> of `event.key`, you will need to make sure to also call `Macros.release(key)`
> at some point to prevent that key from getting "stuck" on.
### `.clear()`
> Releases all virtual keys held by macros. This both empties the supplemental
> `Key` array (see above) and sends a release event for each key stored there.
### `.tap(key)`
> Sends an immediate press and release event for `key` with no delay, using an
> invalid key address.
## Macro helpers ## Macro helpers
@ -164,7 +177,11 @@ 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 `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. 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, The "raw" variants (`Dr`/`Ur`/`Tr`) use the full name of the `Key` object,
without adding the `Key_` prefix to the argument given. `Tr(Key_X)` is the same
as `T(X)`.
The "compact" variants (`Dc`/`Uc`/`Tc`) prefix the argument with `Key_` too,
but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and 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 as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are
not supported by this compact representation. not supported by this compact representation.
@ -188,26 +205,9 @@ them in order.
with `Key_`, and they ignore the `flags` component of a key, and as such, are with `Key_`, and they ignore the `flags` component of a key, and as such, are
limited to ordinary keys. limited to ordinary keys.
### Controlling when to send reports ## Overrideable functions
While the plugin will - by default - send a report after every step, that is not
always desirable. For this reason, we allow turning this implicit reporting off,
and switching to explicit reporting instead. Note that the tap steps (`T()`,
`Tr()`, and `Tc()`) will always send an implicit report, and so will
`Macros.type()`.
To control when to send reports, the following steps can be used:
* `WITH_EXPLICIT_REPORT`: Prevents the plugin from sending an implicit report
after every step. To send a report, one needs to have a `SEND_REPORT` step
too.
* `WITH_IMPLICIT_REPORT`: Enables sending an implicit report after every step
(the default).
* `SEND_REPORT`: Send a report.
## Overrideable methods
### `macroAction(macroIndex, keyState)` ### `macroAction(uint8_t macro_id, KeyEvent &event)`
> The `macroAction` method is the brain of the macro support in Kaleidoscope: > 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 > this function tells the plugin what sequence to play when given a macro index

@ -0,0 +1,215 @@
# Upgrading Macros code
This is a guide to upgrading existing Macros code to use the new version of
Kaleidoscope and the Macros plugin.
## New `macroAction()` function
There is a new version of the `macroAction()` function, which is the entry point
for user-defined Macros code. The old version takes two integer parameters, with
the following call signature:
```c++
const macro_t* macroAction(uint8_t macro_id, uint8_t key_state)
```
If your sketch has this function, with a `key_state` bitfield parameter, it
might still work as expected, but depending on the specifics of the code that
gets called from it, your macros might not work as expected. Either way, you
should update that function to the new version, which takes a `KeyEvent`
reference as its second parameter:
```c++
const macro_t* macroAction(uint8_t macro_id, KeyEvent &event)
```
For simple macros, it is a simple matter of replacing `key_state` in the body of
the `macroAction()` code with `event.state`. This covers most cases where all
that's done is a call to `Macros.type()`, or a `MACRO()` or `MACRODOWN()`
sequence is returned.
## Code that calls `handleKeyswitchEvent()` or `pressKey()`
It is very likely that if you have custom code that calls
`handleKeyswitchEvent()` or `pressKey()` directly, it will no longer function
properly after upgrading. To adapt this code to the new `KeyEvent` system
requires a deeper understanding of the changes to Kaleidoscope, but likely
results in much simpler Macros code.
The first thing that is important to understand is that the `macroAction()`
function will now only be called when a Macros `Key` toggles on or off, not once
per cycle while the key is held. This is because the new event handling code in
Kaleidoscope only calls plugin handlers in those cases, dealing with one event
at a time, in a single pass through the plugin event handlers (rather than one
pass per active key)--and only sends a keyboard HID report in response to those
events, not once per scan cycle.
This means that any Macros code that is meant to keep keycodes in the keyboard
HID report while the Macros key is held needs to be changed. For example, if a
macro contained the following code:
```c++
if (keyIsPressed(key_state)) {
Runtime.hid().keyboard().pressKey(Key_LeftShift);
}
```
...that wouldn't work quite as expected, because as soon as the next key is
pressed, a new report would be generated without ever calling `macroAction()`,
and therefore that change to the HID report would not take place, effectively
turning off the `shift` modifier immediately before sending the report with the
keycode that it was intended to modify.
Furthermore, that `shift` modifier would never even get sent in the first place,
because the HID report no longer gets cleared at the beginning of every
cycle. Now it doesn't get cleared until _after_ the plugin event handlers get
called (in the case of Macros, that's `onKeyEvent()`, which calls the
user-defined `macroAction()` function), so any changes made to the HID report
from that function will be discarded before it's sent.
Instead of the above, there are two new mechanisms for keeping keys active while
a Macros key is pressed:
### Alter the `event.key` value
If your macro only needs to keep a single `Key` value active after running some
code, and doesn't need to run any custom code when the key is released, the
simplest thing to do is to override the event's `Key` value:
```c++
if (keyToggledOn(event.state)) {
// do some macro action(s)
event.key = Key_LeftShift;
}
```
This will (temporarily) replace the Macros key with the value assigned (in this
case, `Key_LeftShift`), starting immediately after the `macroAction()` function
returns, and lasting until the key is released. This key value can include
modifier flags, or it can be a layer-shift, or any other valid `Key` value
(though it won't get processed by plugins that are initialized before Macros in
`KALEIDOSCOPE_INIT_PLUGINS()`, and Macros itself won't act on the value, if it
gets replaced by a different Macros key).
### Use the supplemental Macros `Key` array
The Macros plugin now contains a small array of `Key` values that will be
included when building HID reports triggered by subsequent, non-Macros
events. To use it, just call one (or more) of the following methods:
```c++
Macros.press(key);
Macros.release(key);
Macros.tap(key)
```
Each one of these functions generates a new artificial key event, and processes
it (including sending a HID report, if necessary). For `press()` and
`release()`, it also stores the specified key's value in the Macros supplemental
`Key` array. In the case of the `tap()` function, it generates matching press
and release events, but skips storing them, assuming that no plugin will
generate an intervening event. All of the events generated by these functions
will be marked `INJECTED`, which will cause Macros itself (and many other
plugins) to ignore them.
This will allow you to keep multiple `Key` values active while a Macros key is
held, while leaving the Macros key itself active, enabling more custom code to
be called on its release. Note that whenever a Macros key is released, the
supplemental key array is cleared to minimize the chances of keycodes getting
"stuck". It is still possible to write a macro that will cause values to persist
in this array, however, by combining both a sequence that uses key presses
without matched releases _and_ replacing `event.key` (see above) in the same
macro.
### Borrow an idle key (not recommended)
It's also possible to "borrow" one (or more) idle keys on the keyboard by
searching the `live_keys[]` array for an empty entry, and generating a new event
with the address of that key. This is not recommended because surprising things
can happen if that key is then pressed and released, but it's still an option
for people who like to live dangerously.
## Code that calls `sendReport()`
Calling `sendReport()` directly from a macro is now almost always unnecessary.
Instead, a call to `Runtime.handleKeyEvent()` will result in a keyboard HID
report being sent in response to the generated event without needing to make it
explicit.
## Code that uses `Macros.key_addr`
This variable is deprecated. Instead, using the new `macroAction(id, event)`
function, the address of the Macros key is available via the `event.addr`
variable.
## Working with other plugins
### Plugin-specific `Key` values
When the the Macros plugin generates events, it marks the event state as
`INJECTED` in order to prevent unbounded recursion (Macros ignores injected
events). This causes most other plugins to ignore the event, as well.
Therefore, including a plugin-specific key (e.g. a OneShot modifier such as
`OSM(LeftAlt)`) will most likely be ignored by the target plugin, and will
therefore not have the desired effect. This applies to any calls to
`Macros.play()` (including returning `MACRO()` from `macroAction()`),
`Macros.tap()`, `Macros.press()`, and `Macros.release()`.
### Physical event plugins
Macros cannot usefully produce events handled by plugins that implement the
`onKeyswitchEvent()` handler, such as Qukeys, TapDance, and Leader. To make
those plugins work with Macros, it's necessary to have the other plugin produce
a Macros key, not the other way around. A `macroAction()` function must not call
`Runtime.handleKeyswitchEvent()`.
### OneShot
This is one plugin that you might specifically want to use with a macro,
generally at the end of a sequence. For example, a macro for ending one
sentence and beginning the next one might print a period followed by a space
(`. `), then a OneShot shift key tap, so that the next character will be
automatically capitalized. The problem, as mentioned before is that the
following won't work:
```c++
MACRO(Tc(Period), Tc(Spacebar), Tr(OSM(LeftShift)))
```
...because OneShot will ignore the `INJECTED` event. One solution is to change
the value of `event.key`, turning the pressed Macros key into a OneShot
modifier. This will only work if Macros is registered before OneShot in
`KALEIDOSCOPE_INIT_PLUGINS()`:
```c++
const macro_t* macroNewSentence(KeyEvent &event) {
if (keyToggledOn(event.state)) {
event.key = OSM(LeftShift);
return MACRO(Tc(Period), Tc(Spacebar));
}
return MACRO_NONE;
}
```
A more robust solution is to explicitly call `Runtime.handleKeyEvent()`, but
this is more complex, because you'll need to prevent the Macros key from
clobbering the OneShot key in the `live_keys[]` array:
```c++
void macroNewSentence(KeyEvent &event) {
if (keyToggledOn(event.state)) {
Macros.tap(Key_Period);
Macros.tap(Key_Spacebar);
event.key = OSM(LeftShift);
kaleidoscope::Runtime.handleKeyEvent(event);
// Last, we invalidate the current event's key address to prevent the Macros
// key value from clobbering the OneShot shift.
event.key = Key_NoKey;
event.addr.clear();
}
}
```

@ -19,65 +19,106 @@
#include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/key_events.h" #include "kaleidoscope/key_events.h"
// =============================================================================
// Default `macroAction()` function definitions
__attribute__((weak)) __attribute__((weak))
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
return MACRO_NONE; return MACRO_NONE;
} }
#ifndef NDEPRECATED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
__attribute__((weak))
const macro_t *macroAction(uint8_t macro_id, uint8_t key_state) {
return MACRO_NONE;
}
#pragma GCC diagnostic pop
#endif
// =============================================================================
// `Macros` plugin code
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
MacroKeyEvent Macros_::active_macros[]; constexpr uint8_t press_state = IS_PRESSED | INJECTED;
byte Macros_::active_macro_count; constexpr uint8_t release_state = WAS_PRESSED | INJECTED;
KeyAddr Macros_::key_addr;
// Initialized to zeroes (i.e. `Key_NoKey`)
void playMacroKeyswitchEvent(Key key, uint8_t keyswitch_state, bool explicit_report) { Key Macros::active_macro_keys_[];
handleKeyswitchEvent(key, UnknownKeyswitchLocation, keyswitch_state | INJECTED);
#ifndef NDEPRECATED
if (explicit_report) #pragma GCC diagnostic push
return; #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
MacroKeyEvent Macros::active_macros[];
kaleidoscope::Runtime.hid().keyboard().sendReport(); byte Macros::active_macro_count;
kaleidoscope::Runtime.hid().mouse().sendReport(); KeyAddr Macros::key_addr = KeyAddr::none();
#pragma GCC diagnostic pop
#endif
// -----------------------------------------------------------------------------
// Public helper functions
void Macros::press(Key key) {
Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), press_state, key});
// This key may remain active for several subsequent events, so we need to
// store it in the active macro keys array.
for (Key &macro_key : active_macro_keys_) {
if (macro_key == Key_NoKey) {
macro_key = key;
break;
}
}
} }
static void playKeyCode(Key key, uint8_t keyStates, bool explicit_report) { void Macros::release(Key key) {
if (keyIsPressed(keyStates)) { // Before sending the release event, we need to remove the key from the active
playMacroKeyswitchEvent(key, IS_PRESSED, explicit_report); // macro keys array, or it will get inserted into the report anyway.
for (Key &macro_key : active_macro_keys_) {
if (macro_key == key) {
macro_key = Key_NoKey;
} }
if (keyWasPressed(keyStates)) {
playMacroKeyswitchEvent(key, WAS_PRESSED, explicit_report);
} }
Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, key});
} }
static void readKeyCodeAndPlay(const macro_t *macro_p, uint8_t flags, uint8_t keyStates, bool explicit_report) { void Macros::clear() {
Key key(pgm_read_byte(macro_p++), // key_code // Clear the active macro keys array.
flags); for (Key &macro_key : active_macro_keys_) {
if (macro_key == Key_NoKey)
continue;
Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, macro_key});
macro_key = Key_NoKey;
}
}
playKeyCode(key, keyStates, explicit_report); void Macros::tap(Key key) const {
// No need to call `press()` & `release()`, because we're immediately
// releasing the key after pressing it. It is possible for some other plugin
// to insert an event in between, but very unlikely.
Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), press_state, key});
Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, key});
} }
void Macros_::play(const macro_t *macro_p) { void Macros::play(const macro_t *macro_p) {
macro_t macro = MACRO_ACTION_END; macro_t macro = MACRO_ACTION_END;
uint8_t interval = 0; uint8_t interval = 0;
uint8_t flags; Key key;
bool explicit_report = false;
if (!macro_p) if (macro_p == MACRO_NONE)
return; return;
while (true) { while (true) {
switch (macro = pgm_read_byte(macro_p++)) { switch (macro = pgm_read_byte(macro_p++)) {
// These are unlikely to be useful now that we have KeyEvent. I think the
// whole `explicit_report` came about as a result of scan-order bugs.
case MACRO_ACTION_STEP_EXPLICIT_REPORT: case MACRO_ACTION_STEP_EXPLICIT_REPORT:
explicit_report = true;
break;
case MACRO_ACTION_STEP_IMPLICIT_REPORT: case MACRO_ACTION_STEP_IMPLICIT_REPORT:
explicit_report = false;
break;
case MACRO_ACTION_STEP_SEND_REPORT: case MACRO_ACTION_STEP_SEND_REPORT:
kaleidoscope::Runtime.hid().keyboard().sendReport();
kaleidoscope::Runtime.hid().mouse().sendReport();
break; break;
// End legacy macro step commands
// Timing
case MACRO_ACTION_STEP_INTERVAL: case MACRO_ACTION_STEP_INTERVAL:
interval = pgm_read_byte(macro_p++); interval = pgm_read_byte(macro_p++);
break; break;
@ -86,46 +127,59 @@ void Macros_::play(const macro_t *macro_p) {
delay(wait); delay(wait);
break; break;
} }
case MACRO_ACTION_STEP_KEYDOWN: case MACRO_ACTION_STEP_KEYDOWN:
flags = pgm_read_byte(macro_p++); key.setFlags(pgm_read_byte(macro_p++));
readKeyCodeAndPlay(macro_p++, flags, IS_PRESSED, explicit_report); key.setKeyCode(pgm_read_byte(macro_p++));
press(key);
break; break;
case MACRO_ACTION_STEP_KEYUP: case MACRO_ACTION_STEP_KEYUP:
flags = pgm_read_byte(macro_p++); key.setFlags(pgm_read_byte(macro_p++));
readKeyCodeAndPlay(macro_p++, flags, WAS_PRESSED, explicit_report); key.setKeyCode(pgm_read_byte(macro_p++));
release(key);
break; break;
case MACRO_ACTION_STEP_TAP: case MACRO_ACTION_STEP_TAP:
flags = pgm_read_byte(macro_p++); key.setFlags(pgm_read_byte(macro_p++));
readKeyCodeAndPlay(macro_p++, flags, IS_PRESSED | WAS_PRESSED, false); key.setKeyCode(pgm_read_byte(macro_p++));
tap(key);
break; break;
case MACRO_ACTION_STEP_KEYCODEDOWN: case MACRO_ACTION_STEP_KEYCODEDOWN:
readKeyCodeAndPlay(macro_p++, 0, IS_PRESSED, explicit_report); key.setFlags(0);
key.setKeyCode(pgm_read_byte(macro_p++));
press(key);
break; break;
case MACRO_ACTION_STEP_KEYCODEUP: case MACRO_ACTION_STEP_KEYCODEUP:
readKeyCodeAndPlay(macro_p++, 0, WAS_PRESSED, explicit_report); key.setFlags(0);
key.setKeyCode(pgm_read_byte(macro_p++));
release(key);
break; break;
case MACRO_ACTION_STEP_TAPCODE: case MACRO_ACTION_STEP_TAPCODE:
readKeyCodeAndPlay(macro_p++, 0, IS_PRESSED | WAS_PRESSED, false); key.setFlags(0);
key.setKeyCode(pgm_read_byte(macro_p++));
tap(key);
break; break;
case MACRO_ACTION_STEP_TAP_SEQUENCE: { case MACRO_ACTION_STEP_TAP_SEQUENCE: {
uint8_t keyCode; while (true) {
do { key.setFlags(0);
flags = pgm_read_byte(macro_p++); key.setKeyCode(pgm_read_byte(macro_p++));
keyCode = pgm_read_byte(macro_p++); if (key == Key_NoKey)
playKeyCode(Key(keyCode, flags), IS_PRESSED | WAS_PRESSED, false); break;
tap(key);
delay(interval); delay(interval);
} while (!(flags == 0 && keyCode == 0)); }
break; break;
} }
case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: { case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: {
uint8_t keyCode; while (true) {
do { key.setFlags(0);
keyCode = pgm_read_byte(macro_p++); key.setKeyCode(pgm_read_byte(macro_p++));
playKeyCode(Key(keyCode, 0), IS_PRESSED | WAS_PRESSED, false); if (key.getKeyCode() == 0)
break;
tap(key);
delay(interval); delay(interval);
} while (keyCode != 0); }
break; break;
} }
@ -138,6 +192,26 @@ void Macros_::play(const macro_t *macro_p) {
} }
} }
const macro_t *Macros::type(const char *string) const {
while (true) {
uint8_t ascii_code = pgm_read_byte(string++);
if (ascii_code == 0)
break;
Key key = lookupAsciiCode(ascii_code);
if (key == Key_NoKey)
continue;
tap(key);
}
return MACRO_NONE;
}
// -----------------------------------------------------------------------------
// Translation from ASCII to keycodes
static const Key ascii_to_key_map[] PROGMEM = { static const Key ascii_to_key_map[] PROGMEM = {
// 0x21 - 0x30 // 0x21 - 0x30
LSHIFT(Key_1), LSHIFT(Key_1),
@ -181,8 +255,7 @@ static const Key ascii_to_key_map[] PROGMEM = {
LSHIFT(Key_Backtick), LSHIFT(Key_Backtick),
}; };
Key Macros::lookupAsciiCode(uint8_t ascii_code) const {
Key Macros_::lookupAsciiCode(uint8_t ascii_code) {
Key key = Key_NoKey; Key key = Key_NoKey;
switch (ascii_code) { switch (ascii_code) {
@ -224,67 +297,89 @@ Key Macros_::lookupAsciiCode(uint8_t ascii_code) {
return key; return key;
} }
const macro_t *Macros_::type(const char *string) { // -----------------------------------------------------------------------------
while (true) { // Event handlers
uint8_t ascii_code = pgm_read_byte(string++);
if (!ascii_code)
break;
Key key = lookupAsciiCode(ascii_code);
if (key == Key_NoKey)
continue;
playMacroKeyswitchEvent(key, IS_PRESSED, false); EventHandlerResult Macros::onKeyEvent(KeyEvent &event) {
playMacroKeyswitchEvent(key, WAS_PRESSED, false); // Ignore everything except Macros keys
if (!isMacrosKey(event.key))
return EventHandlerResult::OK;
#ifndef NDEPRECATED
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// Set `Macros.key_addr` so that code in the `macroAction()` function can have
// access to it. This is not such a good solution, but it's done this way for
// backwards compatability. At some point, we should introduce a new
// `macroAction(KeyEvent)` function.
if (event.addr.isValid()) {
key_addr = event.addr;
} else {
key_addr = KeyAddr::none();
} }
#pragma GCC diagnostic pop
return MACRO_NONE; #endif
}
// Decode the macro ID from the Macros `Key` value.
bool Macros_::isMacroKey(Key key) { uint8_t macro_id = event.key.getRaw() - ranges::MACRO_FIRST;
if (key >= ranges::MACRO_FIRST && key <= ranges::MACRO_LAST)
return true; // Call the new `macroAction(event)` function.
return false; const macro_t* macro_ptr = macroAction(macro_id, event);
}
#ifndef NDEPRECATED
EventHandlerResult Macros_::onNameQuery() { #pragma GCC diagnostic push
return ::Focus.sendName(F("Macros")); #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
} // If the new `macroAction()` returned nothing, try the legacy version.
if (macro_ptr == MACRO_NONE)
EventHandlerResult Macros_::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { macro_ptr = macroAction(macro_id, event.state);
if (! isMacroKey(mappedKey)) #pragma GCC diagnostic pop
#endif
// Play back the macro pointed to by `macroAction()`
play(macro_ptr);
if (keyToggledOff(event.state) || !isMacrosKey(event.key)) {
// If we toggled off or if the value of `event.key` has changed, send
// release events for any active macro keys. Simply clearing
// `active_macro_keys_` might be sufficient, but it's probably better to
// send the toggle off events so that other plugins get a chance to act on
// them.
clear();
// Return `OK` to let Kaleidoscope finish processing this event as
// normal. This is so that, if the user-defined `macroAction(id, &event)`
// function changes the value of `event.key`, it will take effect properly.
return EventHandlerResult::OK; return EventHandlerResult::OK;
}
uint8_t macro_index = mappedKey.getRaw() - ranges::MACRO_FIRST; // No other plugin should be handling Macros keys, and there's nothing more
addActiveMacroKey(macro_index, key_addr.toInt(), keyState); // for Kaleidoscope to do with a key press of a Macros key, so we return
// `EVENT_CONSUMED`, causing Kaleidoscope to update `live_keys[]` correctly,
return EventHandlerResult::EVENT_CONSUMED; // ensuring that the above block will clear `active_macro_keys_` on release,
} // but not allowing any other plugins to change the `event.key` value, which
// would interfere.
EventHandlerResult Macros_::afterEachCycle() { //return EventHandlerResult::EVENT_CONSUMED;
active_macro_count = 0;
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
EventHandlerResult Macros_::beforeReportingState() { EventHandlerResult Macros::beforeReportingState(const KeyEvent &event) {
for (byte i = 0; i < active_macro_count; ++i) { // Do this in beforeReportingState(), instead of `onAddToReport()` because
if (active_macros[i].key_id == 0xFF) { // `live_keys` won't get updated until after the macro sequence is played from
key_addr = UnknownKeyswitchLocation; // the keypress. This could be changed by either updating `live_keys` manually
} else { // ahead of time, or by executing the macro sequence on key release instead of
key_addr = KeyAddr(active_macros[i].key_id); // key press. This is probably the simplest solution.
} for (Key key : active_macro_keys_) {
const macro_t *m = macroAction(active_macros[i].key_code, if (key != Key_NoKey)
active_macros[i].key_state); Runtime.addToReport(key);
Macros.play(m);
} }
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
EventHandlerResult Macros::onNameQuery() {
return ::Focus.sendName(F("Macros"));
} }
}
kaleidoscope::plugin::Macros_ Macros; } // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::Macros Macros;

@ -23,68 +23,151 @@
#include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/key_events.h" #include "kaleidoscope/key_events.h"
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState); // =============================================================================
// Deprecated Macros code
#ifndef NDEPRECATED
#define _DEPRECATED_MESSAGE_MACROS_ACTIVE_MACRO_COUNT __NL__ \
"The `Macros.active_macro_count` variable is deprecated. It no longer has\n" __NL__ \
"any functional purpose, and can be safely removed from your code."
#define _DEPRECATED_MESSAGE_MACROS_ACTIVE_MACROS __NL__ \
"The `Macros.active_macros` array is deprecated. It no longer serves any\n" __NL__ \
"functional purpose, and can be safely removed from your code."
#define _DEPRECATED_MESSAGE_MACROS_ADD_ACTIVE_MACRO_KEY __NL__ \
"The `Macros.addActiveMacroKey()` function is deprecated. It no longer\n" __NL__ \
"has any functional purpose, and can be safely removed from your code."
#define _DEPRECATED_MESSAGE_MACRO_ACTION_FUNCTION_V1 __NL__ \
"The old `macroAction(macro_id, key_state)` is deprecated.\n" __NL__ \
"Please define the new `macroAction()` function instead:\n" __NL__ \
"\n" __NL__ \
"const macro_t* macroAction(uint8_t macro_id, KeyEvent &event);\n" __NL__ \
"\n" __NL__ \
"In the body of the new function, replace the `key_state` value with\n" __NL__ \
"`event.state`. Also, note that the new function gives you access to the\n" __NL__ \
"`KeyAddr` of the Macros key event (`event.addr`), and the `Key` value\n" __NL__ \
"(`event.key`). Because the event is passed by reference, it is now\n" __NL__ \
"possible to assign to `event.key` on a toggled-on event, causing that\n" __NL__ \
"`Key` value to persist after the macro finishes playing, leaving that\n" __NL__ \
"value active until the key is released."
#define _DEPRECATED_MESSAGE_MACROS_KEY_ADDR __NL__ \
"The `Macros.key_addr` public variable is deprecated.\n" __NL__ \
"Instead of using this to get the `KeyAddr` of the current macro from\n" __NL__ \
"`macroAction()`, please use the new version of `macroAction()`, which\n" __NL__ \
"uses a `KeyEvent` as its second parameter, giving access to the address\n" __NL__ \
"of the event in the `event.addr` member variable."
#if !defined(MAX_CONCURRENT_MACROS)
#define MAX_CONCURRENT_MACROS 8
#endif #endif
// =============================================================================
// Define this function in a Kaleidoscope sketch in order to trigger Macros.
const macro_t* macroAction(uint8_t macro_id, KeyEvent &event);
#ifndef NDEPRECATED
DEPRECATED(MACRO_ACTION_FUNCTION_V1)
const macro_t* macroAction(uint8_t macro_id, uint8_t key_state);
struct MacroKeyEvent { struct MacroKeyEvent {
byte key_code; byte key_code;
byte key_id; byte key_id;
byte key_state; byte key_state;
}; };
#endif
// The number of simultaneously-active `Key` values that a macro can have
// running during a call to `Macros.play()`. I don't know if it's actually
// possible to override this by defining it in a sketch before including
// "Kaleidoscope-Macros.h", but probably not.
#if !defined(MAX_CONCURRENT_MACRO_KEYS)
#define MAX_CONCURRENT_MACRO_KEYS 8
#endif
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class Macros_ : public kaleidoscope::Plugin { class Macros : public kaleidoscope::Plugin {
public: 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 onNameQuery();
EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, 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 /// Send a key press event from a Macro
Macros.type() with any number of arguments, without having to use a ///
sentinel. See the comments on Runtime.use() for more details - this is /// Generates a new `KeyEvent` and calls `Runtime.handleKeyEvent()` with the
the same trick. /// specified `key`, then stores that `key` in an array of active macro key
*/ /// values. This allows the macro to press one key and keep it active when a
inline const macro_t *type() { /// subsequent key event is sent as part of the same macro sequence.
void press(Key key);
/// Send a key release event from a Macro
///
/// Generates a new `KeyEvent` and calls `Runtime.handleKeyEvent()` with the
/// specified `key`, then removes that key from the array of active macro
/// keys (see `Macros.press()`).
void release(Key key);
/// Clear all virtual keys held by Macros
///
/// This function clears the active macro keys array, sending a release event
/// for each key stored there.
void clear();
/// Send a key "tap event" from a Macro
///
/// Generates two new `KeyEvent` objects, one each to press and release the
/// specified `key`, passing both in sequence to `Runtime.handleKeyEvent()`.
void tap(Key key) const;
/// Play a macro sequence of key events
void play(const macro_t* macro_ptr);
// Templates provide a `type()` function that takes a variable number of
// `char*` (string) arguments, in the form of a list of strings stored in
// PROGMEM, of the form `Macros.type(PSTR("Hello "), PSTR("world!"))`.
inline const macro_t* type() const {
return MACRO_NONE; return MACRO_NONE;
} }
const macro_t *type(const char *string); const macro_t* type(const char* string) const;
template <typename... Strings> template <typename... Strings>
const macro_t *type(const char *first, Strings&&... strings) { const macro_t* type(const char* first, Strings&&... strings) const {
type(first); type(first);
return type(strings...); return type(strings...);
} }
static KeyAddr key_addr; // ---------------------------------------------------------------------------
// Event handlers
EventHandlerResult onNameQuery();
EventHandlerResult onKeyEvent(KeyEvent &event);
EventHandlerResult beforeReportingState(const KeyEvent &event);
private: private:
Key lookupAsciiCode(uint8_t ascii_code); // An array of key values that are active while a macro sequence is playing
bool isMacroKey(Key key); static Key active_macro_keys_[MAX_CONCURRENT_MACRO_KEYS];
// Translate and ASCII character value to a corresponding `Key`
Key lookupAsciiCode(uint8_t ascii_code) const;
// Test for a key that encodes a macro ID
bool isMacrosKey(Key key) const {
if (key >= ranges::MACRO_FIRST && key <= ranges::MACRO_LAST)
return true;
return false;
}
#ifndef NDEPRECATED
public:
DEPRECATED(MACROS_ACTIVE_MACROS)
static MacroKeyEvent active_macros[0];
DEPRECATED(MACROS_ACTIVE_MACRO_COUNT)
static uint8_t active_macro_count;
DEPRECATED(MACROS_ADD_ACTIVE_MACRO_KEY)
static void addActiveMacroKey(uint8_t macro_id, KeyAddr key_addr, uint8_t key_state) {}
DEPRECATED(MACROS_KEY_ADDR)
static KeyAddr key_addr;
#endif
}; };
} } // namespace plugin
} } // namespace kaleidosocpe
extern kaleidoscope::plugin::Macros_ Macros; extern kaleidoscope::plugin::Macros Macros;

@ -1,3 +1,4 @@
// -*- mode: c++ -*-
/* Kaleidoscope-Macros - Macro keys for Kaleidoscope. /* Kaleidoscope-Macros - Macro keys for Kaleidoscope.
* Copyright (C) 2017-2018 Keyboard.io, Inc. * Copyright (C) 2017-2018 Keyboard.io, Inc.
* *

@ -1,3 +1,4 @@
// -*- mode: c++ -*-
/* Kaleidoscope-Macros - Macro keys for Kaleidoscope. /* Kaleidoscope-Macros - Macro keys for Kaleidoscope.
* Copyright (C) 2017-2019 Keyboard.io, Inc. * Copyright (C) 2017-2019 Keyboard.io, Inc.
* *
@ -41,7 +42,16 @@ typedef enum {
typedef uint8_t macro_t; typedef uint8_t macro_t;
#define MACRO_NONE 0 #define MACRO_NONE 0
#define MACRO(...) ({static const macro_t __m[] PROGMEM = { __VA_ARGS__, MACRO_ACTION_END }; &__m[0]; })
#define MACRO(...) ( \
{ \
static const macro_t __m[] PROGMEM = { \
__VA_ARGS__, \
MACRO_ACTION_END \
}; \
&__m[0]; \
})
#define MACRODOWN(...) (keyToggledOn(keyState) ? MACRO(__VA_ARGS__) : MACRO_NONE) #define MACRODOWN(...) (keyToggledOn(keyState) ? MACRO(__VA_ARGS__) : MACRO_NONE)
#define I(n) MACRO_ACTION_STEP_INTERVAL, n #define I(n) MACRO_ACTION_STEP_INTERVAL, n

Loading…
Cancel
Save