Major redesign of the plugin and hooking interface

With this redesign, we introduce a new way to create plugins, which is easier to
extend with new hook points, provides a better interface, uses less memory, less
program space, and is a tiny bit faster too.

It all begins with `kaleidoscope::Plugin` being the base class, which provides
the hook methods plugins can implement. Plugins should be declared with
`KALEIDOSCOPE_INIT_PLUGINS` instead of `Kaleidoscope.use()`. Behind this macro
is a bit of magic (see the in-code documentation) that allows us to unroll the
hook method calls, avoid vtables, and so on. It creates an override for
`kaleidoscope::Hooks::*` methods, each of which will call the respective methods
of each initialized plugin.

With the new API come new names: all of the methods plugins can implement
received new, more descriptive names that all follow a similar pattern.

The old (dubbed V1) API still remains in place, although deprecated. One can
turn it off by setting the `KALEIDOSCOPE_ENABLE_V1_PLUGIN_API` define to zero,
while compiling the firmware.

This work is based on #276, written by @noseglasses. @obra and @algernon did
some cleaning up and applied a little naming treatment.

Signed-off-by: noseglasses <shinynoseglasses@gmail.com>
Signed-off-by: Jesse Vincent <jesse@keyboard.io>
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/316/head
noseglasses 7 years ago committed by Gergely Nagy
parent bc76032d37
commit 8130dfdf1d

@ -0,0 +1,114 @@
Deprecated APIs and their replacements
======================================
As the firmware evolves, there are - and will be - APIs that we deprecate, and
eventually remove. This document is a short guide that lists the deprecations -
along with when it happened, and when the functionality will be removed -,
suggested upgrade paths, and any other information that may be useful.
If any of this does not make sense to you, or you have trouble updating your
.ino sketch, do not hesitate to write us at help@keyboard.io, we can help you
fix it.
## Sheduled for removal by 2018-05-26
These APIs and functions have been deprecated for a long time, and as far as we
can tell, aren't used by any third party or user code anymore.
### Kaleidoscope.setup(KEYMAP_SIZE)
The `Kaleidoscope.setup()` method is still around, and is **not** deprecated,
but the variant of it that takes a keymap size is, and has been since October
2017.
Instead, one should use the argument-less `Kaleidoscope.setup()`, and the new
`KEYMAP()` macros to define a keymap.
### event_handler_hook_use, loop_hook_use, and USE_PLUGINS
Deprecated in October 2017, these are old aliases that should no longer be in
use. They were replaced by `Kaleidoscope.useEventHandlerHook`,
`Kaleidoscope.useLoopHook`, and `Kaleidoscope.use`, respectively.
The replacements themselves are also deprecated - see below -, but their removal
will come at a later date.
### MOMENTARY_OFFSET
Deprecated in October 2017, replaced by `LAYER_SHIFT_OFFSET`.
This symbol was meant to be used by plugins, not user code, and as far as we
know, no third party plugin ever used it.
### key_was_pressed, key_is_pressed, key_toggled_on, key_toggled_off
Deprecated in July 2017, replaced by `keyWasPressed`, `keyIsPressed`,
`keyToggledOn`, and `keyToggledOff`, respectively.
## Sheduled for removal by 2018-07-12
We aim at making a new release by mid-July, and APIs we deprecate now, will be
removed shortly before the major release. We may deprecate further APIs during
the next month (until mid-June), and those deprecations will share the same
removal date. We will try our best to minimize deprecations, and do them as soon
as possible, to give everyone at least a month to prepare and update.
### Kaleidoscope.use()
Deprecated in May 2018, this method is part of the old plugin API, replaced by
`KALEIDOSCOPE_INIT_PLUGINS`. To upgrade, you need to modify your .ino sketch
file, and replace the text `Kaleidoscope.use` with `KALEIDOSCOPE_INIT_PLUGINS`,
then remove the `&` from all of the plugins inside it, and finally, move it
outside of `setup()`.
If your current sketch looks like this:
```c++
void setup() {
Kaleidoscope.use(&Plugin1, &Plugin2);
Kaleidoscope.setup();
}
```
You should change it so that it looks like this instead:
```c++
KALEIDOSCOPE_INIT_PLUGINS(Plugin1, Plugin2);
void setup() {
Kaleidoscope.setup();
}
```
### The old-style (v1) plugin API
This includes using `KaleidoscopePlugin`, `Kaleidoscope.useEventHandlerHook`,
`Kaleidoscope.replaceEventHandlerHook`, `Kaleidoscope.appendEventHandlerHook`,
`Kaleidoscope.useLoopHook`, `Kaleidoscope.replaceLoopHook`,
`Kaleidoscope.appendLoopHook`. They were deprecated in May 2017.
Their replacement is the new plugin API:
```c++
namespace kaleidoscope {
enum class EventHandlerResult {
OK,
EVENT_CONSUMED,
ERROR,
};
class Plugin {
public:
EventHandlerResult onSetup();
EventHandlerResult beforeEachCycle();
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state);
EventHandlerResult beforeReportingState();
EventHandlerResult afterEachCycle();
};
}
```
Plugins are supposed to implement this new API, and then be initialised via
`KALEIDOSCOPE_INIT_PLUGINS`.

@ -0,0 +1,13 @@
# Glossary
This document is intended to name and describe the concepts, functions and data structures inside Kaleidoscope.
It is, as yet, incredibly incomplete.
Entries should be included in dictionary order. When describing an identifier of any kind from the codebase, it should be
written using identical capitalization to its use in the code and surrounded by backticks: `identifierName`
Event handler
: A function, usually provided by a `Plugin` that is run by a `Hook`

@ -0,0 +1,342 @@
# Kaleidoscope Plugin API Internals
This document attempts to explain how the plugin system works internally, behind
the scenes. We feel this is necessary, because there are some unorthodox
solutions in play, which make the code incredibly hard to untangle. This is an
unavoidable side-effect of employing a system that lets us use non-virtual
functions, and save large amounts of RAM thereby. It would be a lot simpler
without this feature, but alas, saving hundreds of bytes of RAM is something we
felt is worth the complexity.
Lets start at the top:
## `KALEIDOSCOPE_INIT_PLUGINS`
```c++
#define KALEIDOSCOPE_INIT_PLUGINS(...) _KALEIDOSCOPE_INIT_PLUGINS(__VA_ARGS__)
```
So far so good, this is pretty simple. The reason we use an indirection here is
because this is in `Kaleidoscope.h`, and we do not want the complexity of the
`_KALEIDOSCOPE_INIT_PLUGINS` macro here, nor do we want to move the macro to
another header, because it belongs to `Kaleidoscope.h`.
## `_KALEIDOSCOPE_INIT_PLUGINS`
```c++
#define _KALEIDOSCOPE_INIT_PLUGINS(...) \
namespace kaleidoscope_internal { \
struct EventDispatcher { \
\
template<typename EventHandler__, typename... Args__ > \
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) { \
\
kaleidoscope::EventHandlerResult result; \
MAP(_INLINE_EVENT_HANDLER_FOR_PLUGIN, __VA_ARGS__) \
\
return result; \
} \
}; \
\
} \
_FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER)
```
This is where things get interesting. This macro does two things:
- It creates `kaleidoscope_internal::EventDispatcher`, a class with a single
method, `apply`. This is a templated method, the template argument is the
method `apply` will call. Thus, `EventDispatcher::template apply<foo>` will
resolve to a function that calls the `foo` method of each plugin we listed
for `KALEIDOSCOPE_INIT_PLUGINS`. We'll see in a bit how this happens.
- The other part creates overrides for the `Kaleidoscope::Hooks::` family of
functions. These are wrappers around `EventDispatcher::template apply<foo>`.
We have these so higher level code would not need to care about the
implementation details, so that it can invoke the hooks as if they were
ordinary functions.
## `_FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER)`
Lets look at `_FOR_EACH_EVENT_HANDLER` and `_REGISTER_EVENT_HANDLER` first,
because that's easier to explain, and does not lead down another rabbit hole.
### `_REGISTER_EVENT_HANDLER`
```c++
#define _REGISTER_EVENT_HANDLER( \
HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST) \
\
namespace kaleidoscope_internal { \
\
struct EventHandler_##HOOK_NAME { \
\
static bool shouldAbortOnConsumedEvent() { \
return SHOULD_ABORT_ON_CONSUMED_EVENT; \
} \
\
template<typename Plugin__, typename... Args__> \
static kaleidoscope::EventHandlerResult \
call(Plugin__ &plugin, Args__&&... hook_args) { \
_VALIDATE_EVENT_HANDLER_SIGNATURE(HOOK_NAME, Plugin__) \
return plugin.HOOK_NAME(hook_args...); \
} \
}; \
\
} \
\
namespace kaleidoscope { \
\
EventHandlerResult Hooks::HOOK_NAME SIGNATURE { \
return kaleidoscope_internal::EventDispatcher::template \
apply<kaleidoscope_internal::EventHandler_ ## HOOK_NAME> \
ARGS_LIST; \
} \
\
}
```
This looks big and scary, but in practice, it isn't all that bad. Nevertheless,
this is where the magic happens!
We create two things: `EventHandler_SomeThing` and `Hooks::SomeThing`, the
latter being a wrapper around the first, that uses `EventDispatcher::template
apply<>` discussed above.
Lets take `onSetup` as an example! For that, the above expands to:
```c++
namespace kaleidoscope_internal {
struct EventHandler_onSetup {
static bool shouldAbortOnConsumedEvent() {
return false;
}
template<typename Plugin__, typename... Args__>
static kaleidoscope::EventHandlerResult
call(Plugin__ &plugin, Args__&&... hook_args) {
return plugin.onSetup(hook_args...);
}
};
}
namespace kaleidoscope {
EventHandlerResult Hooks::onSetup() {
return kaleidoscope_internal::EventDispatcher::template
apply<kaleidoscope_internal::EventHandler_onSetup>();
}
}
```
This still looks scary... but please read a bit further, and it will all make
sense!
### `_FOR_EACH_EVENT_HANDLER`
This just evaluates its argument for each event handler supported by
Kaleidoscope core. Very simple macro expansion, which we will not expand here,
because that would take up a lot of space, and they all look the same (see
above).
## `EventDispatcher`
```c++
namespace kaleidoscope_internal {
struct EventDispatcher {
template<typename EventHandler__, typename... Args__ >
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) {
kaleidoscope::EventHandlerResult result;
MAP(_INLINE_EVENT_HANDLER_FOR_PLUGIN, __VA_ARGS__)
return result;
}
};
```
This is where the other part of the magic happens, and we need to understand
this to be able to make sense of `_REGISTER_EVENT_HANDLER` above.
### `_INLINE_EVENT_HANDLER_FOR_PLUGIN`
This in isolation, is not very interesting, and is closely tied to
`EventDispatcher`. The definition is here so we can look at it while we learn
the details of `EventDispatcher` below.
```c++
#define _INLINE_EVENT_HANDLER_FOR_PLUGIN(PLUGIN) \
\
result = EventHandler__::call(PLUGIN, hook_args...); \
\
if (EventHandler__::shouldAbortOnConsumedEvent() && \
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) { \
return result; \
}
```
### Back to `EventDispatcher`...
The `EventDispatcher` structure has a single method: `apply<>`, which needs an
event handler as its template argument. What the macro does, is call the event
handler given in the template argument for each and every initialised plugin.
It's best explained with an example! Lets use two plugins, `SomePlugin` and
`ExampleEffect`:
```c++
namespace kaleidoscope_internal {
struct EventDispatcher {
template<typename EventHandler__, typename... Args__ >
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) {
kaleidoscope::EventHandlerResult result;
result = EventHandler__::call(SomePlugin);
if (EventHandler__::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
result = EventHandler__::call(ExampleEffect);
if (EventHandler__::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
return result;
}
};
```
See? It's unrolled! But how do we get from here to - say - calling the
`onSetup()` method of the plugins? Why, by way of `EventDispatcher::template
apply<EventHandler_onSetup>`! Lets see what happens when we do a call like that:
```c++
namespace kaleidoscope_internal {
struct EventDispatcher {
template<typename EventHandler_onSetup, typename... Args__ >
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) {
kaleidoscope::EventHandlerResult result;
result = EventHandler_onSetup::call(SomePlugin);
if (EventHandler_onSetup::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
result = EventHandler_onSetup::call(ExampleEffect);
if (EventHandler_onSetup::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
return result;
}
};
```
Because we call `EventHandler_onSetup::call` with the plugin as the first
argument, and because `call` is also a templated function, where the first
argument is templated, we get a method that is polimorphic on its first
argument. Meaning, for each and every plugin, we'll have a matching
`EventHandler_onSetup::call`, that is tied to that plugin. *This* is the magic
that lets us use non-virtual methods!
## Exploring what the compiler does
Because all hooks are called via `kaleidoscope::Hooks::NAME`, lets explore how
the compiler will optimize the code for `onSetup`, assuming we use two plugins,
`SomePlugin` and `ExampleEffect`.
Our entry point is this:
```c++
return kaleidoscope::Hooks::onSetup();
```
`_REGISTER_EVENT_HANDLER` created `Hooks::onSetup()` for us:
```c++
EventHandlerResult kaleidoscope::Hooks::onSetup() {
return kaleidoscope_internal::EventDispatcher::template
apply<kaleidoscope_internal::EventHandler_onSetup>();
}
```
If we inline the call to `EventDispatcher::template apply<>`, we end up with the
following:
```c++
EventHandlerResult kaleidoscope::Hooks::onSetup() {
kaleidoscope::EventHandlerResult result;
result = EventHandler_onSetup::call(SomePlugin);
if (EventHandler_onSetup::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
result = EventHandler_onSetup::call(ExampleEffect);
if (EventHandler_onSetup::shouldAbortOnConsumedEvent() &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
return result;
}
```
This is starting to look human readable, doesn't it? But we can go further,
because `EventHandler::onSetup::call` and
`EventHandler_onSetup::shouldAbortOnConsumedEvent` are evaluated at compile-time
too!
```c++
EventHandlerResult kaleidoscope::Hooks::onSetup() {
kaleidoscope::EventHandlerResult result;
result = SomePlugin.onSetup();
if (false &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
result = ExampleEffect.onSetup();
if (false &&
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) {
return result;
}
return result;
}
```
Which in turn, may be optimized further to something like the following:
```c++
EventHandlerResult kaleidoscope::Hooks::onSetup() {
kaleidoscope::EventHandlerResult result;
result = SomePlugin.onSetup();
result = ExampleEffect.onSetup();
return result;
}
```
And this, is the end of the magic. This is roughly how much the code gets
transformed *at compile time*, so that at run-time, none of this indirection is
present.
## Summary
As we can see, there is a lot going on behind the scenes, and a combination of
template meta programming and pre-processor macros is used to accomplish our
goal. But following the code path like we did above allows us to see what the
compiler sees (more or less), and inlining all the things that are done
compile-time gives us the final code, which is pretty simple by that point.

@ -1,6 +1,6 @@
/* -*- mode: c++ -*- /* -*- mode: c++ -*-
* AppSwitcher -- A Kaleidoscope Example * AppSwitcher -- A Kaleidoscope Example
* Copyright (C) 2016, 2017 Gergely Nagy * Copyright (C) 2016, 2017, 2018 Gergely Nagy
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -46,7 +46,6 @@ KEYMAPS(
) )
/* *INDENT-ON* */ /* *INDENT-ON* */
const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
switch (macroIndex) { switch (macroIndex) {
case M_APPSWITCH: case M_APPSWITCH:
@ -57,9 +56,10 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
return MACRO_NONE; return MACRO_NONE;
} }
void setup() { KALEIDOSCOPE_INIT_PLUGINS(HostOS,
Kaleidoscope.use(&HostOS, &Macros); Macros);
void setup() {
Kaleidoscope.setup(); Kaleidoscope.setup();
} }

@ -84,17 +84,16 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
return MACRO_NONE; return MACRO_NONE;
} }
KALEIDOSCOPE_INIT_PLUGINS(TestMode,
LEDControl, LEDOff,
solidRed, solidOrange, solidYellow, solidGreen, solidBlue, solidIndigo, solidViolet,
LEDBreatheEffect, LEDRainbowEffect, LEDChaseEffect, NumLock,
Macros,
MouseKeys);
void setup() { void setup() {
Kaleidoscope.setup(); Kaleidoscope.setup();
Kaleidoscope.use(&TestMode,
&LEDControl, &LEDOff,
&solidRed, &solidOrange, &solidYellow, &solidGreen, &solidBlue, &solidIndigo, &solidViolet,
&LEDBreatheEffect, &LEDRainbowEffect, &LEDChaseEffect, &NumLock,
&Macros,
&MouseKeys,
NULL);
NumLock.numPadLayer = NUMPAD_KEYMAP; NumLock.numPadLayer = NUMPAD_KEYMAP;
LEDOff.activate(); LEDOff.activate();
} }

@ -1,6 +1,8 @@
#include "Kaleidoscope.h" #include "Kaleidoscope.h"
#include <stdarg.h> #include <stdarg.h>
namespace kaleidoscope {
Kaleidoscope_::eventHandlerHook Kaleidoscope_::eventHandlers[HOOK_MAX]; Kaleidoscope_::eventHandlerHook Kaleidoscope_::eventHandlers[HOOK_MAX];
Kaleidoscope_::loopHook Kaleidoscope_::loopHooks[HOOK_MAX]; Kaleidoscope_::loopHook Kaleidoscope_::loopHooks[HOOK_MAX];
@ -9,6 +11,8 @@ Kaleidoscope_::Kaleidoscope_(void) {
void void
Kaleidoscope_::setup(void) { Kaleidoscope_::setup(void) {
kaleidoscope::Hooks::onSetup();
KeyboardHardware.setup(); KeyboardHardware.setup();
kaleidoscope::hid::initializeKeyboard(); kaleidoscope::hid::initializeKeyboard();
@ -31,115 +35,144 @@ Kaleidoscope_::setup(void) {
void void
Kaleidoscope_::loop(void) { Kaleidoscope_::loop(void) {
kaleidoscope::Hooks::beforeEachCycle();
KeyboardHardware.scanMatrix(); KeyboardHardware.scanMatrix();
kaleidoscope::Hooks::beforeReportingState();
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) { for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) {
loopHook hook = loopHooks[i]; loopHook hook = loopHooks[i];
(*hook)(false); (*hook)(false);
} }
#endif
kaleidoscope::hid::sendKeyboardReport(); kaleidoscope::hid::sendKeyboardReport();
kaleidoscope::hid::releaseAllKeys(); kaleidoscope::hid::releaseAllKeys();
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) { for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) {
loopHook hook = loopHooks[i]; loopHook hook = loopHooks[i];
(*hook)(true); (*hook)(true);
} }
#endif
kaleidoscope::Hooks::afterEachCycle();
}
bool
Kaleidoscope_::focusHook(const char *command) {
enum {
ON,
OFF,
GETSTATE,
} subCommand;
if (strncmp_P(command, PSTR("layer."), 6) != 0)
return false;
if (strcmp_P(command + 6, PSTR("on")) == 0)
subCommand = ON;
else if (strcmp_P(command + 6, PSTR("off")) == 0)
subCommand = OFF;
else if (strcmp_P(command + 6, PSTR("getState")) == 0)
subCommand = GETSTATE;
else
return false;
switch (subCommand) {
case ON: {
uint8_t layer = Serial.parseInt();
Layer.on(layer);
break;
}
case OFF: {
uint8_t layer = Serial.parseInt();
Layer.off(layer);
break;
}
case GETSTATE:
Serial.println(Layer.getLayerState(), BIN);
break;
}
return true;
} }
Kaleidoscope_ Kaleidoscope;
/* Deprecated functions */
/* Disable deprecation warnings for these, we only want to have those at
* non-internal call sites. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
void void
Kaleidoscope_::replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook) { Kaleidoscope_::replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; i < HOOK_MAX; i++) { for (byte i = 0; i < HOOK_MAX; i++) {
if (eventHandlers[i] == oldHook) { if (eventHandlers[i] == oldHook) {
eventHandlers[i] = newHook; eventHandlers[i] = newHook;
return; return;
} }
} }
#endif
} }
void void
Kaleidoscope_::appendEventHandlerHook(eventHandlerHook hook) { Kaleidoscope_::appendEventHandlerHook(eventHandlerHook hook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
replaceEventHandlerHook((eventHandlerHook)NULL, hook); replaceEventHandlerHook((eventHandlerHook)NULL, hook);
#endif
} }
void void
Kaleidoscope_::useEventHandlerHook(eventHandlerHook hook) { Kaleidoscope_::useEventHandlerHook(eventHandlerHook hook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; i < HOOK_MAX; i++) { for (byte i = 0; i < HOOK_MAX; i++) {
if (eventHandlers[i] == hook) if (eventHandlers[i] == hook)
return; return;
} }
appendEventHandlerHook(hook); appendEventHandlerHook(hook);
#endif
} }
void void
Kaleidoscope_::replaceLoopHook(loopHook oldHook, loopHook newHook) { Kaleidoscope_::replaceLoopHook(loopHook oldHook, loopHook newHook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; i < HOOK_MAX; i++) { for (byte i = 0; i < HOOK_MAX; i++) {
if (loopHooks[i] == oldHook) { if (loopHooks[i] == oldHook) {
loopHooks[i] = newHook; loopHooks[i] = newHook;
return; return;
} }
} }
#endif
} }
void void
Kaleidoscope_::appendLoopHook(loopHook hook) { Kaleidoscope_::appendLoopHook(loopHook hook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
replaceLoopHook((loopHook)NULL, hook); replaceLoopHook((loopHook)NULL, hook);
#endif
} }
void void
Kaleidoscope_::useLoopHook(loopHook hook) { Kaleidoscope_::useLoopHook(loopHook hook) {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
for (byte i = 0; i < HOOK_MAX; i++) { for (byte i = 0; i < HOOK_MAX; i++) {
if (loopHooks[i] == hook) if (loopHooks[i] == hook)
return; return;
} }
appendLoopHook(hook); appendLoopHook(hook);
#endif
} }
bool #if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
Kaleidoscope_::focusHook(const char *command) {
enum {
ON,
OFF,
GETSTATE,
} subCommand;
if (strncmp_P(command, PSTR("layer."), 6) != 0)
return false;
if (strcmp_P(command + 6, PSTR("on")) == 0)
subCommand = ON;
else if (strcmp_P(command + 6, PSTR("off")) == 0)
subCommand = OFF;
else if (strcmp_P(command + 6, PSTR("getState")) == 0)
subCommand = GETSTATE;
else
return false;
switch (subCommand) {
case ON: {
uint8_t layer = Serial.parseInt();
Layer.on(layer);
break;
}
case OFF: {
uint8_t layer = Serial.parseInt();
Layer.off(layer);
break;
}
case GETSTATE:
Serial.println(Layer.getLayerState(), BIN);
break;
}
return true;
}
Kaleidoscope_ Kaleidoscope;
/* Deprecated functions */
void event_handler_hook_use(Kaleidoscope_::eventHandlerHook hook) { void event_handler_hook_use(Kaleidoscope_::eventHandlerHook hook) {
Kaleidoscope.useEventHandlerHook(hook); Kaleidoscope.useEventHandlerHook(hook);
} }
@ -148,13 +181,18 @@ void loop_hook_use(Kaleidoscope_::loopHook hook) {
Kaleidoscope.useLoopHook(hook); Kaleidoscope.useLoopHook(hook);
} }
void __USE_PLUGINS(KaleidoscopePlugin *plugin, ...) { void __USE_PLUGINS(kaleidoscope::Plugin *plugin, ...) {
va_list ap; va_list ap;
Kaleidoscope.use(plugin); Kaleidoscope.use(plugin);
va_start(ap, plugin); va_start(ap, plugin);
while ((plugin = (KaleidoscopePlugin *)va_arg(ap, KaleidoscopePlugin *)) != NULL) while ((plugin = (kaleidoscope::Plugin *)va_arg(ap, kaleidoscope::Plugin *)) != NULL)
Kaleidoscope.use(plugin); Kaleidoscope.use(plugin);
va_end(ap); va_end(ap);
} }
#endif
#pragma GCC diagnostic pop // restore diagnostic options
} // namespace kaleidoscope

@ -18,12 +18,16 @@ void setup();
#include <stdio.h> #include <stdio.h>
#include <math.h> #include <math.h>
#include <stdint.h>
#include KALEIDOSCOPE_HARDWARE_H #include KALEIDOSCOPE_HARDWARE_H
#include "key_events.h" #include "key_events.h"
#include "kaleidoscope/hid.h" #include "kaleidoscope/hid.h"
#include "layers.h" #include "layers.h"
#include "macro_map.h"
#include "kaleidoscope_internal/event_dispatch.h"
#include "macro_helpers.h"
#include "plugin.h"
#define HOOK_MAX 64 #define HOOK_MAX 64
@ -66,29 +70,7 @@ static_assert(KALEIDOSCOPE_REQUIRED_API_VERSION == KALEIDOSCOPE_API_VERSION,
const uint8_t KEYMAP_SIZE const uint8_t KEYMAP_SIZE
__attribute__((deprecated("Kaleidoscope.setup() does not require KEYMAP_SIZE anymore."))) = 0; __attribute__((deprecated("Kaleidoscope.setup() does not require KEYMAP_SIZE anymore."))) = 0;
class Kaleidoscope_; namespace kaleidoscope {
class KaleidoscopePlugin {
friend class Kaleidoscope_;
protected:
/** Initial plugin setup hook.
* All plugins are supposed to provide a singleton object, statically
* initialized at compile-time (with few exceptions). Because of this, the
* order in which they are instantiated is unspecified, and cannot be relied
* upon. For this reason, one's expected to explicitly initialize, "use" the
* plugins one wishes to, by calling `Kaleidoscope.use()` with a list of plugin
* object pointers.
*
* This function will in turn call the `begin` function of each plugin,
* so that they can perform any initial setup they wish, such as registering
* event handler or loop hooks. This is the only time this function will be
* called. It is intentionally protected, and accessible by the `Kaleidoscope`
* class only.
*/
virtual void begin(void) { };
};
class Kaleidoscope_ { class Kaleidoscope_ {
public: public:
@ -103,13 +85,39 @@ class Kaleidoscope_ {
// ---- Kaleidoscope.use() ---- // ---- Kaleidoscope.use() ----
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
#define DEPRECATED_USE "\n" \
"------------------------------------------------------------------------\n" \
"Your sketch uses Kaleidoscope.use(), an old-style API to initialize\n" \
"plugins. To fix this, you need to modify your .ino sketch file, and\n" \
"replacing the text \"Kaleidoscope.use\" with \"KALEIDOSCOPE_INIT_PLUGINS\",\n" \
"then remove the & from all of the plugins inside it, and finally, move\n" \
"it outside of \"setup()\":\n" \
"\n" \
"If your current sketch looks like this: \n" \
" void setup() {\n" \
" Kaleidoscope.use(&Plugin1, &Plugin2);\n" \
" Kaleidoscope.setup();\n" \
" }\n" \
"\n" \
"You should change it so that it looks like this:\n" \
" KALEIDOSCOPE_INIT_PLUGINS(Plugin1, Plugin2);\n" \
" void setup() {\n" \
" Kaleidoscope.setup();\n" \
" }\n" \
"\n" \
"If this error doesn't make sense to you or you have any trouble, please\n" \
"send a copy of your .ino sketch file to help@keyboard.io and we can\n" \
"help fix it.\n" \
"------------------------------------------------------------------------\n"
// First, we have the zero-argument version, which will satisfy the tail case. // First, we have the zero-argument version, which will satisfy the tail case.
inline void use() { inline void use() {
} }
// Then, the one-argument version, that gives us type safety for a single // Then, the one-argument version, that gives us type safety for a single
// plugin. // plugin.
inline void use(KaleidoscopePlugin *p) { inline void __attribute__((deprecated(DEPRECATED_USE))) use(kaleidoscope::Plugin *p) {
p->begin(); p->begin();
} }
@ -124,10 +132,14 @@ class Kaleidoscope_ {
// are passed back to either ourselves, or the zero-argument version a few // are passed back to either ourselves, or the zero-argument version a few
// lines above. // lines above.
template <typename... Plugins> template <typename... Plugins>
void use(KaleidoscopePlugin *first, Plugins&&... plugins) { void __attribute__((deprecated(DEPRECATED_USE))) use(kaleidoscope::Plugin *first, Plugins&&... plugins) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
use(first); use(first);
use(plugins...); use(plugins...);
#pragma GCC diagnostic pop
} }
#endif
// ---- hooks ---- // ---- hooks ----
@ -147,21 +159,39 @@ class Kaleidoscope_ {
typedef Key(*eventHandlerHook)(Key mappedKey, byte row, byte col, uint8_t keyState); typedef Key(*eventHandlerHook)(Key mappedKey, byte row, byte col, uint8_t keyState);
static eventHandlerHook eventHandlers[HOOK_MAX]; static eventHandlerHook eventHandlers[HOOK_MAX];
static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook); static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook)
static void appendEventHandlerHook(eventHandlerHook hook); __attribute__((deprecated("Please implement kaleidoscope::Plugin.onKeyswitchEvent(...) instead.")));
static void useEventHandlerHook(eventHandlerHook hook); static void appendEventHandlerHook(eventHandlerHook hook)
__attribute__((deprecated("Please implement kaleidoscope::Plugin.onKeyswitchEvent(...) instead.")));
static void useEventHandlerHook(eventHandlerHook hook)
__attribute__((deprecated("Please implement kaleidoscope::Plugin.onKeyswitchEvent(...) instead.")));
typedef void (*loopHook)(bool postClear); typedef void (*loopHook)(bool postClear);
static loopHook loopHooks[HOOK_MAX]; static loopHook loopHooks[HOOK_MAX];
static void replaceLoopHook(loopHook oldHook, loopHook newHook); static void replaceLoopHook(loopHook oldHook, loopHook newHook)
static void appendLoopHook(loopHook hook); __attribute__((deprecated("Please implement kaleidoscope::Plugin.beforeEachCycle(), .beforeReportingState(...) or .afterEachCycle(...) instead.")));
static void useLoopHook(loopHook hook); static void appendLoopHook(loopHook hook)
__attribute__((deprecated("Please implement kaleidoscope::Plugin.beforeEachCycle(), .beforeReportingState(...) or .afterEachCycle(...) instead.")));
static void useLoopHook(loopHook hook)
__attribute__((deprecated("Please implement kaleidoscope::Plugin.beforeEachCycle(), .beforeReportingState(...) or .afterEachCycle(...) instead.")));
static bool focusHook(const char *command); static bool focusHook(const char *command);
}; };
extern Kaleidoscope_ Kaleidoscope; extern kaleidoscope::Kaleidoscope_ Kaleidoscope;
} // namespace kaleidoscope
// For compatibility reasons we enable class Kaleidoscope_ also to be available
// in global namespace.
//
typedef kaleidoscope::Kaleidoscope_ Kaleidoscope_;
// For compatibility reasons we enable the global variable Kaleidoscope
// in global namespace.
//
using kaleidoscope::Kaleidoscope;
#define FOCUS_HOOK_KALEIDOSCOPE FOCUS_HOOK(Kaleidoscope.focusHook, \ #define FOCUS_HOOK_KALEIDOSCOPE FOCUS_HOOK(Kaleidoscope.focusHook, \
"layer.on\n" \ "layer.on\n" \
@ -170,12 +200,18 @@ extern Kaleidoscope_ Kaleidoscope;
/* -- DEPRECATED aliases; remove them when there are no more users. -- */ /* -- DEPRECATED aliases; remove them when there are no more users. -- */
void event_handler_hook_use(Kaleidoscope_::eventHandlerHook hook) void event_handler_hook_use(kaleidoscope::Kaleidoscope_::eventHandlerHook hook)
__attribute__((deprecated("Use Kaleidoscope.useEventHandlerHook instead"))); __attribute__((deprecated("Use Kaleidoscope.useEventHandlerHook instead")));
void loop_hook_use(Kaleidoscope_::loopHook hook) void loop_hook_use(kaleidoscope::Kaleidoscope_::loopHook hook)
__attribute__((deprecated("Use Kaleidoscope.useLoopHook instead"))); __attribute__((deprecated("Use Kaleidoscope.useLoopHook instead")));
void __USE_PLUGINS(KaleidoscopePlugin *plugin, ...) void __USE_PLUGINS(kaleidoscope::Plugin *plugin, ...)
__attribute__((deprecated("Use Kaleidoscope.use(...) instead"))); __attribute__((deprecated("Use Kaleidoscope.use(...) instead")));
#define USE_PLUGINS(...) __USE_PLUGINS(__VA_ARGS__, NULL) #define USE_PLUGINS(...) __USE_PLUGINS(__VA_ARGS__, NULL)
// Use this function macro to register plugins with Kaleidoscope's
// hooking system. The macro accepts a list of plugin instances that
// must have been instantiated at global scope.
//
#define KALEIDOSCOPE_INIT_PLUGINS(...) _KALEIDOSCOPE_INIT_PLUGINS(__VA_ARGS__)

@ -0,0 +1,57 @@
#pragma once
// This file defines the names and argument signatures for all event handlers
// in the Kaleidoscope core.
//
// When adding a new hook or event handler to Kaleidoscope, it needs only
// to be added here and to the place in the codebase where it should be called.
//
// _FOR_EACH_EVENT_HANDLER is what's called an "X Macro". Its first argument,
// OPERATION, is the name of another macro. When called, it will execute
// OPERATION once for each event handler defined herein with the following
// parameters:
//
// HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST, ...
//
// Any additional parameters that are added to an invokation
// of _FOR_EACH_EVENT_HANDLER are passed through to OP.
#define _ABORTABLE true
#define _NOT_ABORTABLE false
#define _FOR_EACH_EVENT_HANDLER(OPERATION, ...) __NL__ \
__NL__ \
OPERATION(onSetup, __NL__ \
_NOT_ABORTABLE, __NL__ \
(),(), ##__VA_ARGS__) __NL__ \
__NL__ \
/* Called at the very start of each cycle, before gathering */ __NL__ \
/* events, before doing anything else. */ __NL__ \
OPERATION(beforeEachCycle, __NL__ \
_NOT_ABORTABLE, __NL__ \
(), (), ##__VA_ARGS__) __NL__ \
__NL__ \
/* Function called for every non-idle key, every cycle, so it */ __NL__ \
/* can decide what to do with it. It can modify the key (which is */ __NL__ \
/* passed by reference for this reason), and decide whether */ __NL__ \
/* further handles should be tried. If it returns */ __NL__ \
/* EventHandlerResult::OK, other handlers will also get a chance */ __NL__ \
/* to react to the event. If it returns anything else, Kaleidoscope */ __NL__ \
/* will stop processing there. */ __NL__ \
OPERATION(onKeyswitchEvent, __NL__ \
_ABORTABLE, __NL__ \
(Key &mappedKey, byte row, byte col, uint8_t keyState), __NL__ \
(mappedKey, row, col, keyState), ##__VA_ARGS__) __NL__ \
__NL__ \
/* Called before reporting our state to the host. This is the */ __NL__ \
/* last point in a cycle where a plugin can alter what gets */ __NL__ \
/* reported to the host. */ __NL__ \
OPERATION(beforeReportingState, __NL__ \
_NOT_ABORTABLE, __NL__ \
(),(),##__VA_ARGS__) __NL__ \
__NL__ \
/* Called at the very end of a cycle, after everything's */ __NL__ \
/* said and done. */ __NL__ \
OPERATION(afterEachCycle, __NL__ \
_NOT_ABORTABLE, __NL__ \
(),(),##__VA_ARGS__)

@ -0,0 +1,11 @@
#pragma once
namespace kaleidoscope {
enum class EventHandlerResult {
OK,
EVENT_CONSUMED,
ERROR,
};
}

@ -0,0 +1,27 @@
#include "kaleidoscope/hooks.h"
namespace kaleidoscope {
// The following weak symbols are overwritten by
// using the KALEIDOSCOPE_INIT_PLUGINS(...) macro
// in the the firmware sketch. Their only purpose is
// to provide backwards compatibility during the transition
// from the old implementation of the hooking/plugin system
// to the new one. The weak symbols ensure that there
// are no undefined symbols if KALEIDOSCOPE_INIT_PLUGINS(...)
// is not used. This allows legacy sketches to be used
// during the transition phase.
#define INSTANTIATE_WEAK_HOOK_FUNCTION( \
HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
__attribute__((weak)) __NL__ \
EventHandlerResult Hooks::HOOK_NAME SIGNATURE { __NL__ \
return EventHandlerResult::OK; __NL__ \
}
_FOR_EACH_EVENT_HANDLER(INSTANTIATE_WEAK_HOOK_FUNCTION)
#undef INSTANTIATE_WEAK_HOOK_FUNCTION
} // namespace kaleidoscope

@ -0,0 +1,56 @@
#pragma once
#include <Arduino.h>
namespace kaleidoscope {
union Key;
}
#include "plugin.h"
#include "event_handlers.h"
// Forward declaration required to enable friend declarations
// in class Hooks.
class kaleidoscope_;
extern void handleKeyswitchEvent(kaleidoscope::Key mappedKey, byte row, byte col, uint8_t keyState);
namespace kaleidoscope {
// The reason why the hook routing entry point functions live within
// class Hooks and not directly within a namespace is, that we want
// to restrict who is allowed to trigger hooks, mainly to prevent
// user code from calling hook methods.
//
// A note to maintainers: When you add new hooks that are supposed to
// be called from other places than the friend classes and functions listed
// below, just add a suitable friend declaration.
class Hooks {
// The following friend declarations restrict access to
// the hook routing system.
// Kaleidoscope_ calls Hooks::onSetup, Hooks::beforeReportingState
// and Hooks::afterEachCycle.
friend class Kaleidoscope_;
// ::handleKeyswitchEvent(...) calls Hooks::onKeyswitchEvent.
friend void ::handleKeyswitchEvent(kaleidoscope::Key mappedKey,
byte row, byte col, uint8_t keyState);
private:
// The following private functions are just to be called by classes
// and functions that are declared as friends above.
#define DEFINE_WEAK_HOOK_FUNCTION( \
HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
static EventHandlerResult HOOK_NAME SIGNATURE;
_FOR_EACH_EVENT_HANDLER(DEFINE_WEAK_HOOK_FUNCTION)
#undef DEFINE_WEAK_HOOK_FUNCTION
};
}

@ -0,0 +1,133 @@
/*
* This file contains pre-processor macros, which are not namespaced, the
* vast majority of code these macros generate will live under the
* kaleidoscope_internal namespace. That is why this header is here.
*
* The only exception to this is the _KALEIDOSCOPE_INIT_PLUGINS macro, which
* also places a few symbols under the kaleidoscope namespace. But that code is
* tightly coupled with the other parts, which are in kaleidoscope_internal.
*
* The reason we use an entirely separate namespace is that
* Some plugins' classes are in the kaleidoscope namespace, and the instances
* of those classes have the same names as the classes, but in the global
* namespace. In these cases, the macros herein would resolve to the classes,
* when we want them to resolve to instances. To avoid this, we put the
* generated code in an entirely different namespace.
*/
#pragma once
#include "macro_helpers.h"
#include "plugin.h"
#include "kaleidoscope/hooks.h"
#include "eventhandler_signature_check.h"
#include "event_handlers.h"
// Some words about the design of hook routing:
//
// The EventDispatcher class implements a compile-time loop over all plugins, which
// calls an event handler on each plugin.
//
// Each hook called by the firmware core compiles down to only a single
// static function call (in class Hooks), rather than one for each plugin.
//
// This approach is better than using virtual event handlers in classes
// derived from kaleidoscope::Plugin because it results in significantly fewer
// virtual function calls and virtual function tables (vtables).
//
// The call to event handlers through kaleidoscope::Hooks and
// kaleidoscope_internal::EventDispatcher is templated to allow for compile time
// static polymorphisms (plugins' EventHandlers aren't virtual).
//
// Technically, plugins don't need to be derived from kaleidoscope::Plugin, but
// only need to implement a set of event handlers whose call signature match the
// API definition. This approach is similar to duck typing in scripting languages:
// The compiler only cares that a method's signature matches, not that the plugin
// is derived from kaleidoscope::Plugin.
//
// Static hook functions inside the Hooks class each use the EventDispatcher
// helper class to cast an associated EventHandler on each plugin instance.
// This defines an auxiliary class 'EventHandler_Foo' for each hook 'Foo'.
// Kaleidoscope::Hooks calls the EventDispatcher class, which in turn invokes
// the event handler method 'Foo' of each registered plugin with a given
// set of arguments.
#define _REGISTER_EVENT_HANDLER( \
HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
namespace kaleidoscope_internal { __NL__ \
__NL__ \
struct EventHandler_##HOOK_NAME { __NL__ \
__NL__ \
static bool shouldAbortOnConsumedEvent() { __NL__ \
return SHOULD_ABORT_ON_CONSUMED_EVENT; __NL__ \
} __NL__ \
__NL__ \
template<typename Plugin__, typename... Args__> __NL__ \
static kaleidoscope::EventHandlerResult __NL__ \
call(Plugin__ &plugin, Args__&&... hook_args) { __NL__ \
_VALIDATE_EVENT_HANDLER_SIGNATURE(HOOK_NAME, Plugin__) __NL__ \
return plugin.HOOK_NAME(hook_args...); __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
} __NL__ \
__NL__ \
namespace kaleidoscope { __NL__ \
__NL__ \
EventHandlerResult Hooks::HOOK_NAME SIGNATURE { __NL__ \
return kaleidoscope_internal::EventDispatcher::template __NL__ \
apply<kaleidoscope_internal::EventHandler_ ## HOOK_NAME> __NL__ \
ARGS_LIST; __NL__ \
} __NL__ \
__NL__ \
}
#define _INLINE_EVENT_HANDLER_FOR_PLUGIN(PLUGIN) \
__NL__ \
result = EventHandler__::call(PLUGIN, hook_args...); __NL__ \
__NL__ \
if (EventHandler__::shouldAbortOnConsumedEvent() && __NL__ \
result == kaleidoscope::EventHandlerResult::EVENT_CONSUMED) { __NL__ \
return result; __NL__ \
} __NL__
// _KALEIDOSCOPE_INIT_PLUGINS builds the loops that execute the plugins'
// implementations of the various event handlers.
//
// Its arguments are a list of references to plugin instances that have been
// instantiated in the global scope.
// EventDispatcher::apply() implements a compile time for-each loop over all
// plugins. The compiler automatically optimizes away calls to any plugin that
// doesn't implement an EventHandler for a given hook.
#define _KALEIDOSCOPE_INIT_PLUGINS(...) __NL__ \
namespace kaleidoscope_internal { __NL__ \
struct EventDispatcher { __NL__ \
__NL__ \
/* Iterate through plugins, calling each one's event handler with */ __NL__ \
/* the arguments passed to the hook */ __NL__ \
template<typename EventHandler__, typename... Args__ > __NL__ \
static kaleidoscope::EventHandlerResult apply(Args__&&... hook_args) { __NL__ \
__NL__ \
kaleidoscope::EventHandlerResult result; __NL__ \
MAP(_INLINE_EVENT_HANDLER_FOR_PLUGIN, __VA_ARGS__) __NL__ \
__NL__ \
return result; __NL__ \
} __NL__ \
}; __NL__ \
__NL__ \
} __NL__ \
/* We register event handlers here - which is not technically related */ __NL__ \
/* to initialization, nor is it in the same namespace - to support the */ __NL__ \
/* transition from the old APIs. When the user sketch does not use */ __NL__ \
/* KALEIDOSCOPE_INIT_PLUGINS(), the event handlers will not exist */ __NL__ \
/* either, thus wrapping them would produce a compile error. For this */ __NL__ \
/* reason, we do the wrapping here, tied to _KALEIDOSCOPE_INIT_PLUGINS. */ __NL__ \
/* */ __NL__ \
/* TODO(anyone): Move this somewhere else, outside of _internal, once */ __NL__ \
/* the V1 API is removed. */ __NL__ \
_FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER)

@ -0,0 +1,124 @@
#pragma once
#include "macro_helpers.h"
#include "plugin.h"
// *************************************************************************
// *************************************************************************
// NOTHING IN THIS HEADER WILL RESULT IN ANY CODE COMPILED INTO KALEIDOSCOPE
//
// YOU DO NOT NEED TO LOOK INSIDE THIS FILE IF YOU'RE TRYING TO UNDERSTAND
// HOW KALEIDOSCOPE WORKS.
// *************************************************************************
// *************************************************************************
// This header file implements compile-time method signature verification for
// plugins' event handlers as the _VALIDATE_EVENT_HANDLER_SIGNATURE macro.
// If the method signatures don't match the API definitions, compilation
// should fail with a helpful error message.
// *************************************************************************
// *************************************************************************
// We use non-virtual methods for event handlers. This reduces RAM (and to a
// smaller extent, PROGMEM) consumption, runtime call overhead, and allows the
// compiler to a better job removing dead code.
//
// The downside is that event handler methods in derived plugins are hidden,
// rather than overridden. Because only virtual methods can be overridden, we
// can't apply C++'s `override` keyword to hook methods.
//
// To make it easier to debug issues with plugins' event handler method
// signatures, the _VALIDATE_EVENT_HANDLER_SIGNATURE macro compares a plugin's
// event handler signatures with those of the kaleidoscope::Plugin baseclass. If
// any differences are detected, it outputs a compile-time error message.
//
// Ideally, we'd be able to use this (much simpler) code.
//
// decltype(&::kaleidoscope::Plugin::fooEventHandler)
// == decltype(&childPlugin::fooEventHandler)
//
// Unfortunately, this is not possible with C++-11, as it does not allow
// comparing function-types for equality. As a workaround, we can use the trait
// class EventHandlerSignaturesMatch to perform the comparison.
//
// It defines the bool constant 'value' as true if both signatures match.
// It checks the return value, argument types and const specification.
// (As of this writing, we don't have any const hook method.)
template<typename T1, typename T2>
struct EventHandlerSignaturesMatch {
static constexpr bool value = false;
};
// R: The return value,
// T1: Type of the first class (plugin),
// T2: Type of the second class (plugin),
// EventHandlerArgs: Variadic types of plugin event handler arguments.
template<typename R, typename T1, typename T2, typename... EventHandlerArgs>
struct EventHandlerSignaturesMatch<R(T1::*)(EventHandlerArgs...), R(T2::*)(EventHandlerArgs...)> {
static constexpr bool value = true;
};
// Equivalent to allow for const-eventhandlers, e.g. bool getFooEventHandler() const
template<typename R, typename T1, typename T2, typename... EventHandlerArgs> struct
EventHandlerSignaturesMatch<R(T1::*)(EventHandlerArgs...) const, R(T2::*)(EventHandlerArgs...) const> {
static constexpr bool value = true;
};
// This template is instantiated when something goes wrong.
// Because it does not define a constant 'value', it triggers a compiler error.
template<typename Plugin__, bool result> struct
___________Culprit_Plugin___________ { };
// This specialization is instantiated when everything is ok.
template<typename Plugin__> struct
___________Culprit_Plugin___________ <Plugin__, true> {
static constexpr bool value = true;
};
// If the pointer types are the same, the signatures match, causing
// the first or second specialization to be instantiated. This makes
// the causes the compile time constant `value` to be defined as `true`.
// Otherwise, the unspecialized version of the template class is instantiated
// and `value` is defined as `false`
#define _VALIDATE_EVENT_HANDLER_SIGNATURE(EVENTHANDLER, PLUGIN) \
{ __NL__ \
/* Check if the signatures match. If not, the plugin has implemented */ __NL__ \
/* a method with a hook's name, but a different signature. */ __NL__ \
typedef EventHandlerSignaturesMatch< __NL__ \
decltype(&::kaleidoscope::Plugin::EVENTHANDLER), __NL__ \
decltype(&PLUGIN::EVENTHANDLER) __NL__ \
> Check; __NL__ \
__NL__ \
static_assert(Check::value, __NL__ \
VERBOSE_STATIC_ASSERT_HEADER __NL__ \
"\nOne of your plugins implemented the \"" #EVENTHANDLER "\"" __NL__ \
"\nevent handler, but its signature didn't match the base class." __NL__ \
"\n" __NL__ \
"\nThe plugin with this issue will be marked in the compiler" __NL__ \
"\noutput with the string:" __NL__ \
"\n" __NL__ \
"\n ___________Culprit_Plugin___________." __NL__ \
"\n" __NL__ \
"\nYou should compare the event handlers implemented in this plugin" __NL__ \
"\nto those in \"kaleidoscope::Plugin\"." __NL__ \
"\n" __NL__ \
"\nAll of the event handler's argument types, return values and" __NL__ \
"\nconst qualifiers need to match." __NL__ \
"\n" __NL__ \
VERBOSE_STATIC_ASSERT_FOOTER __NL__ \
); __NL__ \
__NL__ \
/* The following construct is necessary enable reporting of the */ __NL__ \
/* type of a plugin that implements an event handler with an */ __NL__ \
/* incorrect signature, because it's not possible to include any */ __NL__ \
/* non-literal string constant in a static_assert error message. */ __NL__ \
constexpr bool dummy = ___________Culprit_Plugin___________ __NL__ \
<PLUGIN, Check::value>::value; __NL__ \
}

@ -16,8 +16,9 @@
#include "KeyboardioHID.h" #include "KeyboardioHID.h"
#endif #endif
namespace kaleidoscope {
typedef union Key_ { union Key {
struct { struct {
uint8_t keyCode; uint8_t keyCode;
@ -28,14 +29,14 @@ typedef union Key_ {
inline bool operator==(uint16_t rhs) { inline bool operator==(uint16_t rhs) {
return this->raw == rhs; return this->raw == rhs;
} }
inline bool operator==(const Key_ rhs) { inline bool operator==(const Key rhs) {
return this->raw == rhs.raw; return this->raw == rhs.raw;
} }
inline Key_& operator=(uint16_t raw) { inline Key& operator=(uint16_t raw) {
this->raw = raw; this->raw = raw;
return *this; return *this;
} }
inline bool operator!=(const Key_& rhs) { inline bool operator!=(const Key& rhs) {
return !(*this == rhs); return !(*this == rhs);
} }
inline bool operator>=(uint16_t raw) { inline bool operator>=(uint16_t raw) {
@ -50,21 +51,27 @@ typedef union Key_ {
inline bool operator<(uint16_t raw) { inline bool operator<(uint16_t raw) {
return this->raw < raw; return this->raw < raw;
} }
inline bool operator>=(const Key_& other) { inline bool operator>=(const Key& other) {
return this->raw >= other.raw; return this->raw >= other.raw;
} }
inline bool operator<=(const Key_& other) { inline bool operator<=(const Key& other) {
return this->raw <= other.raw; return this->raw <= other.raw;
} }
inline bool operator>(const Key_& other) { inline bool operator>(const Key& other) {
return this->raw > other.raw; return this->raw > other.raw;
} }
inline bool operator<(const Key_& other) { inline bool operator<(const Key& other) {
return this->raw < other.raw; return this->raw < other.raw;
} }
} Key; };
} // namespace kaleidoscope
// For compatibility reasons make the Key class also available
// in global namespace.
//
typedef kaleidoscope::Key Key;
typedef kaleidoscope::Key Key_;
#define KEY_FLAGS B00000000 #define KEY_FLAGS B00000000
#define CTRL_HELD B00000001 #define CTRL_HELD B00000001

@ -1,5 +1,6 @@
#include "Kaleidoscope.h" #include "Kaleidoscope.h"
#include "kaleidoscope/hooks.h"
#include "plugin.h"
static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) { static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) {
if (mappedKey.flags & RESERVED) if (mappedKey.flags & RESERVED)
@ -92,12 +93,21 @@ void handleKeyswitchEvent(Key mappedKey, byte row, byte col, uint8_t keyState) {
// Keypresses with out-of-bounds (row,col) start here in the processing chain // Keypresses with out-of-bounds (row,col) start here in the processing chain
// Legacy event handlers
#if KALEIDOSCOPE_ENABLE_V1_API
for (byte i = 0; Kaleidoscope.eventHandlers[i] != NULL && i < HOOK_MAX; i++) { for (byte i = 0; Kaleidoscope.eventHandlers[i] != NULL && i < HOOK_MAX; i++) {
Kaleidoscope_::eventHandlerHook handler = Kaleidoscope.eventHandlers[i]; Kaleidoscope_::eventHandlerHook handler = Kaleidoscope.eventHandlers[i];
mappedKey = (*handler)(mappedKey, row, col, keyState); mappedKey = (*handler)(mappedKey, row, col, keyState);
if (mappedKey.raw == Key_NoKey.raw) if (mappedKey.raw == Key_NoKey.raw)
return; return;
} }
#endif
// New event handler interface
//
if (kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, row, col, keyState) != kaleidoscope::EventHandlerResult::OK)
return;
mappedKey = Layer.eventHandler(mappedKey, row, col, keyState); mappedKey = Layer.eventHandler(mappedKey, row, col, keyState);
if (mappedKey.raw == Key_NoKey.raw) if (mappedKey.raw == Key_NoKey.raw)
return; return;

@ -35,24 +35,24 @@
// Allow for the creation of verbose messages in static_asserts // Allow for the creation of verbose messages in static_asserts
// //
#define VERBOSE_STATIC_ASSERT_HEADER \ #define VERBOSE_STATIC_ASSERT_HEADER \
__NL__ "\n" \ "\n" __NL__ \
__NL__ "\n***************************************************************" \ "\n***************************************************************" __NL__ \
__NL__ "\n******************** READ THIS CAREFULLY! *********************" \ "\n******************** READ THIS CAREFULLY! *********************" __NL__ \
__NL__ "\n***************************************************************" \ "\n***************************************************************" __NL__ \
__NL__ "\n" "\n"
#define VERBOSE_STATIC_ASSERT_FOOTER \ #define VERBOSE_STATIC_ASSERT_FOOTER \
__NL__ "\n" \ "\n" __NL__ \
__NL__ "\n***************************************************************" \ "\n***************************************************************" __NL__ \
__NL__ "\n***************************************************************" \ "\n***************************************************************" __NL__ \
__NL__ "\n***************************************************************" \ "\n***************************************************************" __NL__ \
__NL__ "\n" "\n"
#define VERBOSE_FILE_INFO \ #define VERBOSE_FILE_INFO \
__NL__ "\nFile: " __FILE__ "\nFile: " __FILE__ __NL__
#define VERBOSE_LINE_INFO \ #define VERBOSE_LINE_INFO \
__NL__ "\nLine: " STRINGIZE(__LINE__) "\nLine: " STRINGIZE(__LINE__) __NL__
// The macro function RESTRICT_ARGS_COUNT can be used to generate more // The macro function RESTRICT_ARGS_COUNT can be used to generate more
// verbose error messages when users supply an insuitable number of arguments // verbose error messages when users supply an insuitable number of arguments
@ -81,43 +81,41 @@ int array[] = { A, B, RESTRICT_ARGS_COUNT(C, 3, B_MACRO, ##__VA_ARGS__) };
NUM_EXPECTED_ARGS, \ NUM_EXPECTED_ARGS, \
ORIGINAL_MACRO, \ ORIGINAL_MACRO, \
...) \ ...) \
__NN__ ( \ ( __NL__ \
__NL__ []{ /* Here we are in the body of a dummy lambda function. \ []{ /* Here we are in the body of a dummy lambda function. */ __NL__ \
__NN__ []{} is, BTW, the shortest way to write a lambda. \ /* []{} is, BTW, the shortest way to write a lambda. */ __NL__ \
__NN__ It is only used to hold the static_assert that cannot be \ /* It is only used to hold the static_assert that cannot be */ __NL__ \
__NN__ defined directly in the keymap initializer list. By using the \ /* defined directly in the keymap initializer list. By using the */ __NL__ \
__NN__ comma operator ((A, B) always evaluates to b), we ensure \ /* comma operator ((A, B) always evaluates to b), we ensure */ __NL__ \
__NN__ that not the lambda but B is what ASSERT_ARGS_COUNT \ /* that not the lambda but B is what ASSERT_ARGS_COUNT */ __NL__ \
__NN__ finally evaluates to. \ /* finally evaluates to. */ __NL__ \
__NN__ Please not that passing B through this macro is a must \ /* Please not that passing B through this macro is a must */ __NL__ \
__NN__ as we need it for the comma operator to work. \ /* as we need it for the comma operator to work. */ __NL__ \
__NN__ */ \ static_assert(sizeof(const char) == sizeof(#__VA_ARGS__ ), __NL__ \
__NN__ static_assert(sizeof(const char) == sizeof(#__VA_ARGS__ ), \ /* sizeof(const char) == sizeof(#__VA_ARGS__ ) checks the quoted */ __NL__ \
__NN__ /* sizeof(const char) == sizeof(#__VA_ARGS__ ) checks the quoted \ /* list of additional arguments. If there are none, then the */ __NL__ \
__NN__ list of additional arguments. If there are none, then the \ /* length of #__VA_ARGS__ is a single char as it contains '\0'. */ __NL__ \
__NN__ length of #__VA_ARGS__ is a single char as it contains '\0'. \ /* This check is not able to find the corner case of a single */ __NL__ \
__NN__ This check is not able to find the corner case of a single \ /* superfluous comma at the end of the macro arguments as this */ __NL__ \
__NN__ superfluous comma at the end of the macro arguments as this \ /* causes #__VA_ARGS__ being empty (only '\0'). */ __NL__ \
__NN__ causes #__VA_ARGS__ being empty (only '\0'). \ VERBOSE_STATIC_ASSERT_HEADER __NL__ \
__NN__ */ \ __NL__ \
__NN__ VERBOSE_STATIC_ASSERT_HEADER \ VERBOSE_FILE_INFO __NL__ \
__NN__ \ VERBOSE_LINE_INFO __NL__ \
__NN__ VERBOSE_FILE_INFO \ "\n" __NL__ \
__NN__ VERBOSE_LINE_INFO \ "\nStrange arguments found in invocation of " #ORIGINAL_MACRO "." __NL__ \
__NL__ "\n" \ "\n" __NL__ \
__NL__ "\nStrange arguments encountered in invocation of " #ORIGINAL_MACRO "." \ "\nPlease make sure to pass exactly " #NUM_EXPECTED_ARGS __NL__ \
__NL__ "\n" \ " macro arguments to" __NL__ \
__NL__ "\nPlease make sure to pass exactly " #NUM_EXPECTED_ARGS \ "\n" #ORIGINAL_MACRO ". Also make sure that there are no dangling" __NL__ \
__NN__ " macro arguments to" \ "\ncommas at the end of the argument list." __NL__ \
__NL__ "\n" #ORIGINAL_MACRO ". Also make sure that there are no dangling" \ "\n" __NL__ \
__NL__ "\ncommas at the end of the argument list." \ "\nThis is the superfluous part at the end of the macro" __NL__ \
__NL__ "\n" \ "\narguments list: \'" #__VA_ARGS__ "\'" __NL__ \
__NL__ "\nThis is the superfluous part at the end of the macro" \ __NL__ \
__NL__ "\narguments list: \'" #__VA_ARGS__ "\'" \ VERBOSE_STATIC_ASSERT_FOOTER __NL__ \
__NN__ \ ); __NL__ \
__NN__ VERBOSE_STATIC_ASSERT_FOOTER \ __NL__ \
__NL__ ); \ }, /* End of dummy lambda, the comma operator's A operand. */ __NL__ \
__NL__ \ B /* The overall ASSERT_ARGS_COUNT evaluates to B. */ __NL__ \
__NL__ }, /* End of dummy lambda, the comma operator's A operand. */ \ )
__NL__ B /* The overall ASSERT_ARGS_COUNT evaluates to B. */ \
__NL__ )

@ -0,0 +1,67 @@
/*
* Copyright (C) 2012 William Swanson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the names of the authors or
* their institutions shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without
* prior written authorization from the authors.
*/
#pragma once
#define EVAL0(...) __VA_ARGS__
#define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__)))
#define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__)))
#define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__)))
#define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__)))
#define EVAL(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__)))
#define MAP_END(...)
#define MAP_OUT
#define MAP_COMMA ,
#define MAP_GET_END2() 0, MAP_END
#define MAP_GET_END1(...) MAP_GET_END2
#define MAP_GET_END(...) MAP_GET_END1
#define MAP_NEXT0(test, next, ...) next MAP_OUT
#define MAP_NEXT1(test, next) MAP_NEXT0(test, next, 0)
#define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next)
#define MAP0(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP1)(f, peek, __VA_ARGS__)
#define MAP1(f, x, peek, ...) f(x) MAP_NEXT(peek, MAP0)(f, peek, __VA_ARGS__)
#define MAP_LIST_NEXT1(test, next) MAP_NEXT0(test, MAP_COMMA next, 0)
#define MAP_LIST_NEXT(test, next) MAP_LIST_NEXT1(MAP_GET_END test, next)
#define MAP_LIST0(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST1)(f, peek, __VA_ARGS__)
#define MAP_LIST1(f, x, peek, ...) f(x) MAP_LIST_NEXT(peek, MAP_LIST0)(f, peek, __VA_ARGS__)
/**
* Applies the function macro `f` to each of the remaining parameters.
*/
#define MAP(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
/**
* Applies the function macro `f` to each of the remaining parameters and
* inserts commas between the results.
*/
#define MAP_LIST(f, ...) EVAL(MAP_LIST1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))

@ -0,0 +1,94 @@
#pragma once
#include "kaleidoscope/event_handler_result.h"
#include "kaleidoscope_internal/event_dispatch.h"
#include "event_handlers.h"
#ifndef KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
#define KALEIDOSCOPE_ENABLE_V1_PLUGIN_API 1
#endif
namespace kaleidoscope {
class Kaleidoscope_;
// A base class that implements default noop versions of all event
// handler methods.
//
// A note to developers:
//
// The only purpose of class EventHandlerBasePlugin
// is to enable the use of _FOR_EACH_EVENT_HANDLER
// to define default event handlers. This is currently not possible
// inside class Plugin directly as it would collide with
// the separate definition of onSetup(). This additional
// definition is, however, necessary to support the V1 plugin API.
//
// TODO(anyone): As soon as the V1 plugin API is removed from Kaleidoscope,
// class EventHandlerBasePlugin can be removed and its content be moved to
// class Plugin.
//
class EventHandlerBasePlugin {
public:
#define DEFINE_AND_IMPLEMENT_EVENT_HANDLER_METHOD( \
HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST) __NL__ \
__NL__ \
EventHandlerResult HOOK_NAME SIGNATURE { __NL__ \
return EventHandlerResult::OK; __NL__ \
}
_FOR_EACH_EVENT_HANDLER(DEFINE_AND_IMPLEMENT_EVENT_HANDLER_METHOD)
#undef DEFINE_AND_IMPLEMENT_EVENT_HANDLER_METHOD
};
class Plugin : public EventHandlerBasePlugin {
friend class Kaleidoscope_;
protected:
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
/** Legacy plugin setup hook.
* In version one of the plugin API, `plugin->begin()` was called at
* `Kaleidoscope.use()` time, to perform boot-time initialization. We needed
* this because plugins are supposed to provide a singleton object, statically
* initialized at compile-time (with few exceptions). Because of this, the
* order in which they are instantiated is unspecified, and cannot be relied
* upon. For this reason, one's expected to explicitly initialize, "use" the
* plugins one wishes to, by calling `Kaleidoscope.use()` with a list of
* plugin object pointers.
*
* This is the only time this function will be called. It is intentionally
* protected, and accessible by the `Kaleidoscope` class only.
*
* Also, this method is deprecated in favour of the V2 API, which you can read
* about below.
*/
virtual void begin() {}
#endif
public:
// Please see "event_handlers.h" for a list of supported event handlers and
// their documentation!
EventHandlerResult onSetup() {
#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API
// By letting the new `onSetup()` method call the legacy begin() method, we
// make sure that the old hooking interface is supported even if
// KALEIDOSCOPE_INIT_PLUGINS() is used to register a plugin that relies on
// the legacy `begin()` method to initialize itself and register hooks.
//
this->begin();
#endif
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
typedef kaleidoscope::Plugin KaleidoscopePlugin __attribute__((deprecated("class KaleidoscopePlugin is deprecated. Please derive plugins from kaleidoscope::Plugin instead.")));
Loading…
Cancel
Save