From 8130dfdf1d772c71ee399eb3b08e38490dea2dfc Mon Sep 17 00:00:00 2001 From: noseglasses Date: Fri, 11 May 2018 08:03:55 +0200 Subject: [PATCH] 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 Signed-off-by: Jesse Vincent Signed-off-by: Gergely Nagy --- UPGRADING.md | 114 ++++++ doc/glossary.md | 13 + doc/plugin-api-internals.md | 342 ++++++++++++++++++ examples/AppSwitcher/AppSwitcher.ino | 8 +- examples/Kaleidoscope/Kaleidoscope.ino | 15 +- src/Kaleidoscope.cpp | 132 ++++--- src/Kaleidoscope.h | 108 ++++-- src/event_handlers.h | 57 +++ src/kaleidoscope/event_handler_result.h | 11 + src/kaleidoscope/hooks.cpp | 27 ++ src/kaleidoscope/hooks.h | 56 +++ src/kaleidoscope_internal/event_dispatch.h | 133 +++++++ .../eventhandler_signature_check.h | 124 +++++++ src/key_defs.h | 25 +- src/key_events.cpp | 12 +- src/macro_helpers.h | 104 +++--- src/macro_map.h | 67 ++++ src/plugin.h | 94 +++++ 18 files changed, 1284 insertions(+), 158 deletions(-) create mode 100644 UPGRADING.md create mode 100644 doc/glossary.md create mode 100644 doc/plugin-api-internals.md create mode 100644 src/event_handlers.h create mode 100644 src/kaleidoscope/event_handler_result.h create mode 100644 src/kaleidoscope/hooks.cpp create mode 100644 src/kaleidoscope/hooks.h create mode 100644 src/kaleidoscope_internal/event_dispatch.h create mode 100644 src/kaleidoscope_internal/eventhandler_signature_check.h create mode 100644 src/macro_map.h create mode 100644 src/plugin.h diff --git a/UPGRADING.md b/UPGRADING.md new file mode 100644 index 00000000..695acfae --- /dev/null +++ b/UPGRADING.md @@ -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`. diff --git a/doc/glossary.md b/doc/glossary.md new file mode 100644 index 00000000..c129ee14 --- /dev/null +++ b/doc/glossary.md @@ -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` diff --git a/doc/plugin-api-internals.md b/doc/plugin-api-internals.md new file mode 100644 index 00000000..56a01267 --- /dev/null +++ b/doc/plugin-api-internals.md @@ -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 \ + 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` 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`. + 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 \ + 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 \ + 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 + 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(); +} + +} +``` + +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 + 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 + 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`! Lets see what happens when we do a call like that: + +```c++ +namespace kaleidoscope_internal { +struct EventDispatcher { + template + 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(); +} +``` + +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. diff --git a/examples/AppSwitcher/AppSwitcher.ino b/examples/AppSwitcher/AppSwitcher.ino index 7303cc33..03933667 100644 --- a/examples/AppSwitcher/AppSwitcher.ino +++ b/examples/AppSwitcher/AppSwitcher.ino @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * 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 * it under the terms of the GNU General Public License as published by @@ -46,7 +46,6 @@ KEYMAPS( ) /* *INDENT-ON* */ - const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { switch (macroIndex) { case M_APPSWITCH: @@ -57,9 +56,10 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { return MACRO_NONE; } -void setup() { - Kaleidoscope.use(&HostOS, &Macros); +KALEIDOSCOPE_INIT_PLUGINS(HostOS, + Macros); +void setup() { Kaleidoscope.setup(); } diff --git a/examples/Kaleidoscope/Kaleidoscope.ino b/examples/Kaleidoscope/Kaleidoscope.ino index e3a90086..af75e5fd 100644 --- a/examples/Kaleidoscope/Kaleidoscope.ino +++ b/examples/Kaleidoscope/Kaleidoscope.ino @@ -84,17 +84,16 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { return MACRO_NONE; } +KALEIDOSCOPE_INIT_PLUGINS(TestMode, + LEDControl, LEDOff, + solidRed, solidOrange, solidYellow, solidGreen, solidBlue, solidIndigo, solidViolet, + LEDBreatheEffect, LEDRainbowEffect, LEDChaseEffect, NumLock, + Macros, + MouseKeys); + void 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; LEDOff.activate(); } diff --git a/src/Kaleidoscope.cpp b/src/Kaleidoscope.cpp index e2cdba9f..a823c845 100644 --- a/src/Kaleidoscope.cpp +++ b/src/Kaleidoscope.cpp @@ -1,6 +1,8 @@ #include "Kaleidoscope.h" #include +namespace kaleidoscope { + Kaleidoscope_::eventHandlerHook Kaleidoscope_::eventHandlers[HOOK_MAX]; Kaleidoscope_::loopHook Kaleidoscope_::loopHooks[HOOK_MAX]; @@ -9,6 +11,8 @@ Kaleidoscope_::Kaleidoscope_(void) { void Kaleidoscope_::setup(void) { + kaleidoscope::Hooks::onSetup(); + KeyboardHardware.setup(); kaleidoscope::hid::initializeKeyboard(); @@ -31,115 +35,144 @@ Kaleidoscope_::setup(void) { void Kaleidoscope_::loop(void) { + kaleidoscope::Hooks::beforeEachCycle(); + KeyboardHardware.scanMatrix(); + kaleidoscope::Hooks::beforeReportingState(); + +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) { loopHook hook = loopHooks[i]; (*hook)(false); } +#endif kaleidoscope::hid::sendKeyboardReport(); kaleidoscope::hid::releaseAllKeys(); +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; loopHooks[i] != NULL && i < HOOK_MAX; i++) { loopHook hook = loopHooks[i]; (*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 Kaleidoscope_::replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; i < HOOK_MAX; i++) { if (eventHandlers[i] == oldHook) { eventHandlers[i] = newHook; return; } } +#endif } void Kaleidoscope_::appendEventHandlerHook(eventHandlerHook hook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API replaceEventHandlerHook((eventHandlerHook)NULL, hook); +#endif } void Kaleidoscope_::useEventHandlerHook(eventHandlerHook hook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; i < HOOK_MAX; i++) { if (eventHandlers[i] == hook) return; } appendEventHandlerHook(hook); +#endif } void Kaleidoscope_::replaceLoopHook(loopHook oldHook, loopHook newHook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; i < HOOK_MAX; i++) { if (loopHooks[i] == oldHook) { loopHooks[i] = newHook; return; } } +#endif } void Kaleidoscope_::appendLoopHook(loopHook hook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API replaceLoopHook((loopHook)NULL, hook); +#endif } void Kaleidoscope_::useLoopHook(loopHook hook) { +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API for (byte i = 0; i < HOOK_MAX; i++) { if (loopHooks[i] == hook) return; } appendLoopHook(hook); +#endif } -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 */ - +#if KALEIDOSCOPE_ENABLE_V1_PLUGIN_API void event_handler_hook_use(Kaleidoscope_::eventHandlerHook hook) { Kaleidoscope.useEventHandlerHook(hook); } @@ -148,13 +181,18 @@ void loop_hook_use(Kaleidoscope_::loopHook hook) { Kaleidoscope.useLoopHook(hook); } -void __USE_PLUGINS(KaleidoscopePlugin *plugin, ...) { +void __USE_PLUGINS(kaleidoscope::Plugin *plugin, ...) { va_list ap; Kaleidoscope.use(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); va_end(ap); } +#endif + +#pragma GCC diagnostic pop // restore diagnostic options + +} // namespace kaleidoscope diff --git a/src/Kaleidoscope.h b/src/Kaleidoscope.h index a8e199f2..4138f69c 100644 --- a/src/Kaleidoscope.h +++ b/src/Kaleidoscope.h @@ -18,12 +18,16 @@ void setup(); #include #include - +#include #include KALEIDOSCOPE_HARDWARE_H #include "key_events.h" #include "kaleidoscope/hid.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 @@ -66,29 +70,7 @@ static_assert(KALEIDOSCOPE_REQUIRED_API_VERSION == KALEIDOSCOPE_API_VERSION, const uint8_t KEYMAP_SIZE __attribute__((deprecated("Kaleidoscope.setup() does not require KEYMAP_SIZE anymore."))) = 0; -class 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) { }; -}; +namespace kaleidoscope { class Kaleidoscope_ { public: @@ -103,13 +85,39 @@ class Kaleidoscope_ { // ---- 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. inline void use() { } // Then, the one-argument version, that gives us type safety for a single // plugin. - inline void use(KaleidoscopePlugin *p) { + inline void __attribute__((deprecated(DEPRECATED_USE))) use(kaleidoscope::Plugin *p) { p->begin(); } @@ -124,10 +132,14 @@ class Kaleidoscope_ { // are passed back to either ourselves, or the zero-argument version a few // lines above. template - 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(plugins...); +#pragma GCC diagnostic pop } +#endif // ---- hooks ---- @@ -147,21 +159,39 @@ class Kaleidoscope_ { typedef Key(*eventHandlerHook)(Key mappedKey, byte row, byte col, uint8_t keyState); static eventHandlerHook eventHandlers[HOOK_MAX]; - static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook); - static void appendEventHandlerHook(eventHandlerHook hook); - static void useEventHandlerHook(eventHandlerHook hook); + static void replaceEventHandlerHook(eventHandlerHook oldHook, eventHandlerHook newHook) + __attribute__((deprecated("Please implement kaleidoscope::Plugin.onKeyswitchEvent(...) instead."))); + 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); static loopHook loopHooks[HOOK_MAX]; - static void replaceLoopHook(loopHook oldHook, loopHook newHook); - static void appendLoopHook(loopHook hook); - static void useLoopHook(loopHook hook); + static void replaceLoopHook(loopHook oldHook, loopHook newHook) + __attribute__((deprecated("Please implement kaleidoscope::Plugin.beforeEachCycle(), .beforeReportingState(...) or .afterEachCycle(...) instead."))); + 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); }; -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, \ "layer.on\n" \ @@ -170,12 +200,18 @@ extern Kaleidoscope_ Kaleidoscope; /* -- 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"))); -void loop_hook_use(Kaleidoscope_::loopHook hook) +void loop_hook_use(kaleidoscope::Kaleidoscope_::loopHook hook) __attribute__((deprecated("Use Kaleidoscope.useLoopHook instead"))); -void __USE_PLUGINS(KaleidoscopePlugin *plugin, ...) +void __USE_PLUGINS(kaleidoscope::Plugin *plugin, ...) __attribute__((deprecated("Use Kaleidoscope.use(...) instead"))); #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__) diff --git a/src/event_handlers.h b/src/event_handlers.h new file mode 100644 index 00000000..0981f678 --- /dev/null +++ b/src/event_handlers.h @@ -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__) diff --git a/src/kaleidoscope/event_handler_result.h b/src/kaleidoscope/event_handler_result.h new file mode 100644 index 00000000..75b1724c --- /dev/null +++ b/src/kaleidoscope/event_handler_result.h @@ -0,0 +1,11 @@ +#pragma once + +namespace kaleidoscope { + +enum class EventHandlerResult { + OK, + EVENT_CONSUMED, + ERROR, +}; + +} diff --git a/src/kaleidoscope/hooks.cpp b/src/kaleidoscope/hooks.cpp new file mode 100644 index 00000000..d97b70e8 --- /dev/null +++ b/src/kaleidoscope/hooks.cpp @@ -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 diff --git a/src/kaleidoscope/hooks.h b/src/kaleidoscope/hooks.h new file mode 100644 index 00000000..dcd0e1dd --- /dev/null +++ b/src/kaleidoscope/hooks.h @@ -0,0 +1,56 @@ +#pragma once + +#include + +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 +}; + +} diff --git a/src/kaleidoscope_internal/event_dispatch.h b/src/kaleidoscope_internal/event_dispatch.h new file mode 100644 index 00000000..2659bfa4 --- /dev/null +++ b/src/kaleidoscope_internal/event_dispatch.h @@ -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 __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 __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 __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) diff --git a/src/kaleidoscope_internal/eventhandler_signature_check.h b/src/kaleidoscope_internal/eventhandler_signature_check.h new file mode 100644 index 00000000..9b5948c1 --- /dev/null +++ b/src/kaleidoscope_internal/eventhandler_signature_check.h @@ -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 +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 +struct EventHandlerSignaturesMatch { + static constexpr bool value = true; +}; + +// Equivalent to allow for const-eventhandlers, e.g. bool getFooEventHandler() const +template struct +EventHandlerSignaturesMatch { + 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 struct + ___________Culprit_Plugin___________ { }; + +// This specialization is instantiated when everything is ok. +template struct + ___________Culprit_Plugin___________ { + 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__ \ + ::value; __NL__ \ +} diff --git a/src/key_defs.h b/src/key_defs.h index 181a4964..5e02389f 100644 --- a/src/key_defs.h +++ b/src/key_defs.h @@ -16,8 +16,9 @@ #include "KeyboardioHID.h" #endif +namespace kaleidoscope { -typedef union Key_ { +union Key { struct { uint8_t keyCode; @@ -28,14 +29,14 @@ typedef union Key_ { inline bool operator==(uint16_t rhs) { return this->raw == rhs; } - inline bool operator==(const Key_ rhs) { + inline bool operator==(const Key rhs) { return this->raw == rhs.raw; } - inline Key_& operator=(uint16_t raw) { + inline Key& operator=(uint16_t raw) { this->raw = raw; return *this; } - inline bool operator!=(const Key_& rhs) { + inline bool operator!=(const Key& rhs) { return !(*this == rhs); } inline bool operator>=(uint16_t raw) { @@ -50,21 +51,27 @@ typedef union Key_ { inline bool operator<(uint16_t raw) { return this->raw < raw; } - inline bool operator>=(const Key_& other) { + inline bool operator>=(const Key& other) { return this->raw >= other.raw; } - inline bool operator<=(const Key_& other) { + inline bool operator<=(const Key& other) { return this->raw <= other.raw; } - inline bool operator>(const Key_& other) { + inline bool operator>(const Key& other) { return this->raw > other.raw; } - inline bool operator<(const Key_& other) { + inline bool operator<(const Key& other) { 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 CTRL_HELD B00000001 diff --git a/src/key_events.cpp b/src/key_events.cpp index ca1b69e3..e9b6aea7 100644 --- a/src/key_events.cpp +++ b/src/key_events.cpp @@ -1,5 +1,6 @@ #include "Kaleidoscope.h" - +#include "kaleidoscope/hooks.h" +#include "plugin.h" static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) { 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 + // Legacy event handlers +#if KALEIDOSCOPE_ENABLE_V1_API for (byte i = 0; Kaleidoscope.eventHandlers[i] != NULL && i < HOOK_MAX; i++) { Kaleidoscope_::eventHandlerHook handler = Kaleidoscope.eventHandlers[i]; mappedKey = (*handler)(mappedKey, row, col, keyState); if (mappedKey.raw == Key_NoKey.raw) 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); if (mappedKey.raw == Key_NoKey.raw) return; diff --git a/src/macro_helpers.h b/src/macro_helpers.h index 31b0976e..03b6a057 100644 --- a/src/macro_helpers.h +++ b/src/macro_helpers.h @@ -34,25 +34,25 @@ // Allow for the creation of verbose messages in static_asserts // -#define VERBOSE_STATIC_ASSERT_HEADER \ -__NL__ "\n" \ -__NL__ "\n***************************************************************" \ -__NL__ "\n******************** READ THIS CAREFULLY! *********************" \ -__NL__ "\n***************************************************************" \ -__NL__ "\n" +#define VERBOSE_STATIC_ASSERT_HEADER \ + "\n" __NL__ \ + "\n***************************************************************" __NL__ \ + "\n******************** READ THIS CAREFULLY! *********************" __NL__ \ + "\n***************************************************************" __NL__ \ + "\n" #define VERBOSE_STATIC_ASSERT_FOOTER \ -__NL__ "\n" \ -__NL__ "\n***************************************************************" \ -__NL__ "\n***************************************************************" \ -__NL__ "\n***************************************************************" \ -__NL__ "\n" + "\n" __NL__ \ + "\n***************************************************************" __NL__ \ + "\n***************************************************************" __NL__ \ + "\n***************************************************************" __NL__ \ + "\n" #define VERBOSE_FILE_INFO \ -__NL__ "\nFile: " __FILE__ + "\nFile: " __FILE__ __NL__ #define VERBOSE_LINE_INFO \ -__NL__ "\nLine: " STRINGIZE(__LINE__) + "\nLine: " STRINGIZE(__LINE__) __NL__ // The macro function RESTRICT_ARGS_COUNT can be used to generate more // 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, \ ORIGINAL_MACRO, \ ...) \ -__NN__ ( \ -__NL__ []{ /* Here we are in the body of a dummy lambda function. \ -__NN__ []{} is, BTW, the shortest way to write a lambda. \ -__NN__ It is only used to hold the static_assert that cannot be \ -__NN__ defined directly in the keymap initializer list. By using the \ -__NN__ comma operator ((A, B) always evaluates to b), we ensure \ -__NN__ that not the lambda but B is what ASSERT_ARGS_COUNT \ -__NN__ finally evaluates to. \ -__NN__ Please not that passing B through this macro is a must \ -__NN__ as we need it for the comma operator to work. \ -__NN__ */ \ -__NN__ static_assert(sizeof(const char) == sizeof(#__VA_ARGS__ ), \ -__NN__ /* sizeof(const char) == sizeof(#__VA_ARGS__ ) checks the quoted \ -__NN__ list of additional arguments. If there are none, then the \ -__NN__ length of #__VA_ARGS__ is a single char as it contains '\0'. \ -__NN__ This check is not able to find the corner case of a single \ -__NN__ superfluous comma at the end of the macro arguments as this \ -__NN__ causes #__VA_ARGS__ being empty (only '\0'). \ -__NN__ */ \ -__NN__ VERBOSE_STATIC_ASSERT_HEADER \ -__NN__ \ -__NN__ VERBOSE_FILE_INFO \ -__NN__ VERBOSE_LINE_INFO \ -__NL__ "\n" \ -__NL__ "\nStrange arguments encountered in invocation of " #ORIGINAL_MACRO "." \ -__NL__ "\n" \ -__NL__ "\nPlease make sure to pass exactly " #NUM_EXPECTED_ARGS \ -__NN__ " macro arguments to" \ -__NL__ "\n" #ORIGINAL_MACRO ". Also make sure that there are no dangling" \ -__NL__ "\ncommas at the end of the argument list." \ -__NL__ "\n" \ -__NL__ "\nThis is the superfluous part at the end of the macro" \ -__NL__ "\narguments list: \'" #__VA_ARGS__ "\'" \ -__NN__ \ -__NN__ VERBOSE_STATIC_ASSERT_FOOTER \ -__NL__ ); \ -__NL__ \ -__NL__ }, /* End of dummy lambda, the comma operator's A operand. */ \ -__NL__ B /* The overall ASSERT_ARGS_COUNT evaluates to B. */ \ -__NL__ ) + ( __NL__ \ + []{ /* Here we are in the body of a dummy lambda function. */ __NL__ \ + /* []{} is, BTW, the shortest way to write a lambda. */ __NL__ \ + /* It is only used to hold the static_assert that cannot be */ __NL__ \ + /* defined directly in the keymap initializer list. By using the */ __NL__ \ + /* comma operator ((A, B) always evaluates to b), we ensure */ __NL__ \ + /* that not the lambda but B is what ASSERT_ARGS_COUNT */ __NL__ \ + /* finally evaluates to. */ __NL__ \ + /* Please not that passing B through this macro is a must */ __NL__ \ + /* as we need it for the comma operator to work. */ __NL__ \ + static_assert(sizeof(const char) == sizeof(#__VA_ARGS__ ), __NL__ \ + /* sizeof(const char) == sizeof(#__VA_ARGS__ ) checks the quoted */ __NL__ \ + /* list of additional arguments. If there are none, then the */ __NL__ \ + /* length of #__VA_ARGS__ is a single char as it contains '\0'. */ __NL__ \ + /* This check is not able to find the corner case of a single */ __NL__ \ + /* superfluous comma at the end of the macro arguments as this */ __NL__ \ + /* causes #__VA_ARGS__ being empty (only '\0'). */ __NL__ \ + VERBOSE_STATIC_ASSERT_HEADER __NL__ \ + __NL__ \ + VERBOSE_FILE_INFO __NL__ \ + VERBOSE_LINE_INFO __NL__ \ + "\n" __NL__ \ + "\nStrange arguments found in invocation of " #ORIGINAL_MACRO "." __NL__ \ + "\n" __NL__ \ + "\nPlease make sure to pass exactly " #NUM_EXPECTED_ARGS __NL__ \ + " macro arguments to" __NL__ \ + "\n" #ORIGINAL_MACRO ". Also make sure that there are no dangling" __NL__ \ + "\ncommas at the end of the argument list." __NL__ \ + "\n" __NL__ \ + "\nThis is the superfluous part at the end of the macro" __NL__ \ + "\narguments list: \'" #__VA_ARGS__ "\'" __NL__ \ + __NL__ \ + VERBOSE_STATIC_ASSERT_FOOTER __NL__ \ + ); __NL__ \ + __NL__ \ + }, /* End of dummy lambda, the comma operator's A operand. */ __NL__ \ + B /* The overall ASSERT_ARGS_COUNT evaluates to B. */ __NL__ \ + ) diff --git a/src/macro_map.h b/src/macro_map.h new file mode 100644 index 00000000..e5fd2082 --- /dev/null +++ b/src/macro_map.h @@ -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)) diff --git a/src/plugin.h b/src/plugin.h new file mode 100644 index 00000000..499db0e5 --- /dev/null +++ b/src/plugin.h @@ -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.")));