Allow device drivers to handle callback hooks.

It requires a little bit of support in device/Base.h otherwise the C++ got very,
very complicated.

This commit implements a device-first event handling system in Kaleidoscope,
allowing hardware device drivers to process events before plugins. Key features:

- Device drivers can now implement event handlers like onKeyEvent, onFocusEvent, etc.
- Events are first passed to the device driver; if it returns OK, plugins get the event
- If a device driver returns any result other than OK, the event processing stops
- Updated event_dispatch.h to call device handler implementations first
- Added stub implementations of all event handlers in device/Base.h
- Added onKeyEvent method to BLE driver interface
- Updated documentation to reflect the new event handling architecture

This change enables hardware-specific features (like BLE key handling) to be
implemented by device drivers with proper priority over plugins.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jesse Vincent
2025-03-24 11:57:45 -07:00
committed by Jesse Vincent
parent c5aa976f45
commit f36031d45a
5 changed files with 188 additions and 15 deletions

View File

@@ -1,12 +1,18 @@
# Kaleidoscope's Plugin Event Handlers
# Kaleidoscope's Event Handlers
Kaleidoscope provides a set of hook functions that plugins can define in order
to do their work. If one or more of the functions listed here are defined as
methods in a plugin class, that plugin can act on the input events that drive
Kaleidoscope.
Kaleidoscope provides a set of hook functions that can be defined by plugins and device drivers to do their work. If one or more of the functions listed here are defined as methods in a plugin or device driver class, that component can act on the input events that drive Kaleidoscope.
In response to input events (plus a few other places), Kaleidoscope calls the
event handlers for each plugin that defines them, in sequence.
In response to input events (plus a few other places), Kaleidoscope first calls the event handler for the active device driver (if implemented), and then calls the event handlers for each plugin that defines them, in sequence.
## Device Driver Event Handlers vs. Plugin Event Handlers
Kaleidoscope's event handling system recognizes two types of event handlers:
1. **Device Driver Event Handlers**: These are implemented by hardware device drivers and are called first for each event. If a device driver's handler returns a result other than `EventHandlerResult::OK`, the event is considered handled and will not be passed to any plugins.
2. **Plugin Event Handlers**: These are implemented by plugins and are called in sequence (in the order plugins are registered) after the device driver handler returns `EventHandlerResult::OK`.
This priority system allows device drivers to intercept and handle hardware-specific features (such as Bluetooth connectivity controls) before plugins get a chance to process events.
## Return values
@@ -124,13 +130,18 @@ class `KeyEventTracker` can help simplify following these rules.
After a physical keyswitch event is processed by all of the plugins with
`onKeyswitchEvent()` handlers (and they all return `OK`), Kaleidoscope passes
that event on to the `Runtime.handleKeyEvent()` function, which calls plugins'
`onKeyEvent()` handlers. This is also the starting point for events which do not
correspond to physical key events, and can have an invalid `event.addr` value.
that event on to the `Runtime.handleKeyEvent()` function, which first calls the
device driver's `onKeyEvent()` handler (if implemented). If the device driver returns
`EventHandlerResult::OK`, Kaleidoscope then calls each plugin's `onKeyEvent()` handler
in sequence.
Plugins that need to respond to keyboard input, but which do not need to be
closely tied to physical key events (and only those events) should use
`onKeyEvent()` to do their work.
This is also the starting point for events which do not correspond to physical
key events, and can have an invalid `event.addr` value.
Device drivers can use this handler to intercept and handle special key codes (such as
Bluetooth control keys) before plugins get a chance to process them. Plugins that need
to respond to keyboard input, but which do not need to be closely tied to physical key
events (and only those events) should use `onKeyEvent()` to do their work.
After all `onKeyEvent()` handlers have returned `OK` for an event, the
`live_keys` state array gets updated. For a key press event, the final

View File

@@ -48,6 +48,18 @@
#include "kaleidoscope/driver/speaker/Base.h" // for BaseProps
#include "kaleidoscope/driver/speaker/None.h" // for None
// Forward declarations for event handling
#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult
#include "kaleidoscope/host_connection_status.h" // for HostConnectionStatus
// Forward declaration needed by event handlers
namespace kaleidoscope {
class KeyEvent;
}
// Forward declaration for LedModeCallback
typedef void (*LedModeCallback)(const char*);
// Connection mode for host HID and Serial
enum HostConnectionMode {
@@ -618,6 +630,134 @@ class Base {
void updateSpeaker() {
speaker_.update();
}
// Event handler implementations that route to drivers
// Currently only the necessary handlers are implemented
// More can be added as needed
/**
* Event handler for setup
*/
EventHandlerResult onSetup() {
return EventHandlerResult::OK;
}
/**
* Event handler for exploreSketch
*/
EventHandlerResult exploreSketch() {
return EventHandlerResult::OK;
}
/**
* Event handler for key events
* This method routes key events to appropriate drivers
*/
EventHandlerResult onKeyEvent(KeyEvent &event) {
EventHandlerResult result;
// Currently only route to BLE driver
result = ble_.onKeyEvent(event);
if (result != EventHandlerResult::OK) {
return result;
}
return EventHandlerResult::OK;
}
/**
* Event handler for keyswitch events
*/
EventHandlerResult onKeyswitchEvent(kaleidoscope::KeyEvent &event) {
return EventHandlerResult::OK;
}
/**
* Event handler for focus events
*/
EventHandlerResult onFocusEvent(const char *input) {
return EventHandlerResult::OK;
}
/**
* Event handler for layer changes
*/
EventHandlerResult onLayerChange() {
return EventHandlerResult::OK;
}
/**
* Event handler for LED mode changes
*/
EventHandlerResult onLEDModeChange() {
return EventHandlerResult::OK;
}
/**
* Event handler called before syncing LEDs
*/
EventHandlerResult beforeSyncingLeds() {
return EventHandlerResult::OK;
}
/**
* Event handler called before reporting state to host
*/
EventHandlerResult beforeReportingState(const KeyEvent &event) {
return EventHandlerResult::OK;
}
/**
* Event handler called after reporting state to host
*/
EventHandlerResult afterReportingState(const KeyEvent &event) {
return EventHandlerResult::OK;
}
/**
* Event handler for host connection status changes
*/
EventHandlerResult onHostConnectionStatusChanged(uint8_t device_id, kaleidoscope::HostConnectionStatus status) {
return EventHandlerResult::OK;
}
/**
* Event handler for adding keys to HID report
*/
EventHandlerResult onAddToReport(Key key) {
return EventHandlerResult::OK;
}
/**
* Event handler called at the beginning of each cycle
*/
EventHandlerResult beforeEachCycle() {
return EventHandlerResult::OK;
}
/**
* Event handler called at the end of each cycle
*/
EventHandlerResult afterEachCycle() {
return EventHandlerResult::OK;
}
/**
* Event handler for name queries
*/
EventHandlerResult onNameQuery() {
return EventHandlerResult::OK;
}
/**
* Event handler for LED effect queries
*/
EventHandlerResult onLedEffectQuery(LedModeCallback callback) {
return EventHandlerResult::OK;
}
protected:
HID hid_;

View File

@@ -24,6 +24,12 @@
#pragma once
#include <Arduino.h>
#include "kaleidoscope/event_handler_result.h"
// Forward declaration to avoid circular dependency
namespace kaleidoscope {
class KeyEvent;
}
namespace kaleidoscope {
namespace driver {
@@ -43,6 +49,11 @@ class Base {
Stream &serialPort() {
return noserial_;
}
// Default implementation of onKeyEvent that does nothing
EventHandlerResult onKeyEvent(KeyEvent &event) {
return EventHandlerResult::OK;
}
private:
class NoSerial : public Stream {

View File

@@ -26,6 +26,7 @@
#include "kaleidoscope/macro_helpers.h" // for __NL__, MAKE_TEMPLATE_SIGNATURE, UNWRAP
namespace kaleidoscope {
// The following weak symbols are overwritten by using the
// KALEIDOSCOPE_INIT_PLUGINS(...) macro in the firmware sketch. Their only
@@ -52,8 +53,6 @@ namespace kaleidoscope {
_FOR_EACH_EVENT_HANDLER(INSTANTIATE_WEAK_HOOK_FUNCTION)
// clang-format on
#undef INSTANTIATE_WEAK_HOOK_FUNCTION
namespace sketch_exploration {
class Sketch;
}

View File

@@ -161,6 +161,18 @@
__NL__ \
MAKE_TEMPLATE_SIGNATURE(UNWRAP TMPL_PARAM_TYPE_LIST) __NL__ \
EventHandlerResult Hooks::HOOK_NAME SIGNATURE { __NL__ \
__NL__ \
EventHandlerResult device_result = EventHandlerResult::OK; __NL__ \
__NL__ \
device_result = ::kaleidoscope::Runtime.device().HOOK_NAME __NL__ \
ARGS_LIST; __NL__ \
__NL__ \
/* If the device consumed the event, return early */ __NL__ \
if (device_result != EventHandlerResult::OK) { __NL__ \
return device_result; __NL__ \
} __NL__ \
__NL__ \
/* Dispatch to plugins if the device didn't handle it */ __NL__ \
return kaleidoscope_internal::EventDispatcher::template __NL__ \
apply<kaleidoscope_internal __NL__ \
::_NAME4(EventHandler_, HOOK_NAME, _v, HOOK_VERSION) __NL__ \