Merge pull request #1024 from gedankenexperimenter/key-event-handler
New `KeyEvent` handlers and main event loop rewritepull/1038/head
commit
3a8eb5d839
@ -0,0 +1,249 @@
|
|||||||
|
# Kaleidoscope's Plugin 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.
|
||||||
|
|
||||||
|
In response to input events (plus a few other places), Kaleidoscope calls the
|
||||||
|
event handlers for each plugin that defines them, in sequence.
|
||||||
|
|
||||||
|
## Return values
|
||||||
|
|
||||||
|
Every Kaleidoscope event handler function returns a value of type
|
||||||
|
`EventHandlerResult`, an enum with several variants. In some handlers,
|
||||||
|
Kaleidoscope ignores the return value, but for others, the result is used as a
|
||||||
|
signal to control Kaleidoscope's behavior. In particular, some event handler
|
||||||
|
hooks are "abortable". For those hooks, the return value of the plugin handlers
|
||||||
|
are used to control what Kaleidoscope does after each plugin's event handler
|
||||||
|
returns.
|
||||||
|
|
||||||
|
- `EventHandlerResult::OK` is used to signal that Kaleidoscope should continue
|
||||||
|
on to the next handler in the sequence.
|
||||||
|
|
||||||
|
- `EventHandlerResult::ABORT` is used to signal that Kaleidoscope should not
|
||||||
|
continue to call the other plugin handlers in the sequence, and stop
|
||||||
|
processing the event entirely. This is used by some plugins to cancel events
|
||||||
|
and/or delay them so that they occur at a later time, possibly with different
|
||||||
|
values.
|
||||||
|
|
||||||
|
- `EventHandlerResult::EVENT_CONSUMED` is used to signal that the plugin has
|
||||||
|
successfully handled the event, and that there is nothing further to be done,
|
||||||
|
so there is no point in continuing to call further plugin event handlers for
|
||||||
|
the event.
|
||||||
|
|
||||||
|
## Non-event "event" handlers
|
||||||
|
|
||||||
|
There are three special "event" handlers that are not called in response to
|
||||||
|
input events, but are instead called at fixed points during Kaleidoscope's run
|
||||||
|
time.
|
||||||
|
|
||||||
|
### `onSetup()`
|
||||||
|
|
||||||
|
This handler is called when Kaleidoscope first starts. If a plugin needs to do
|
||||||
|
some work after its constructor is called, but before Kaleidoscope enters its
|
||||||
|
main loop and starts scanning for keyswitch events, it can do it in this
|
||||||
|
function.
|
||||||
|
|
||||||
|
### `beforeEachCycle()`
|
||||||
|
|
||||||
|
This handler gets called at the beginning of every keyswitch scan cycle, before
|
||||||
|
the scan. It can be used by plugins to do things that need to be done
|
||||||
|
repeatedly, regardless of any input from the user. Typically, this involves
|
||||||
|
things like checking for timeouts.
|
||||||
|
|
||||||
|
### `afterEachCycle()`
|
||||||
|
|
||||||
|
This is just like `beforeEachCycle()`, but gets called after the keyswitches
|
||||||
|
have been scanned (and any input events handled).
|
||||||
|
|
||||||
|
## Keyswitch input event handlers
|
||||||
|
|
||||||
|
This group of event handlers is triggered when keys on the keyboard are pressed
|
||||||
|
and released. With one exception, they use a `KeyEvent` object as their one
|
||||||
|
parameter. The `KeyEvent` class encapsulates the essential data about a key
|
||||||
|
press (or release):
|
||||||
|
|
||||||
|
- `event.addr` contains the `KeyAddr` of the key that toggled on or off.
|
||||||
|
|
||||||
|
- `event.state` contains information about the current and former state of the
|
||||||
|
key in the form of a `uint8_t` bitfield.
|
||||||
|
|
||||||
|
- `event.key` contains the `Key` value of the event. For key presses, this is
|
||||||
|
generally determined by means of a keymap lookup. For releases, the value is
|
||||||
|
taken from the `live_keys` structure. Because the `event` is passed by
|
||||||
|
reference, changing this value in a plugin handler will affect which value
|
||||||
|
ends up in the `live_keys` array, and thus, the output of the keyboard.
|
||||||
|
|
||||||
|
- `event.id` contains a `KeyEventId` value: an integer, usually monotonically
|
||||||
|
increasing. This is useful as a tool to allow plugins to avoid re-processing
|
||||||
|
the same event, thus avoiding infinite loops without resorting to an
|
||||||
|
`INJECTED` key state flag which would cause other plugins to ignore events
|
||||||
|
that they might otherwise be interested in.
|
||||||
|
|
||||||
|
### `onKeyswitchEvent(KeyEvent &event)`
|
||||||
|
|
||||||
|
This handler is called in response to changes detected in the state of
|
||||||
|
keyswitches, via the `Runtime.handleKeyswitchEvent()` function. After the
|
||||||
|
keyswitches are scanned in each cycle, Kaleidoscope goes through them all and
|
||||||
|
compares the state of each one to its previous state. For any of them that have
|
||||||
|
either toggled on or off, plugins that define this function get called (until
|
||||||
|
one of them returns either `ABORT` or `EVENT_CONSUMED`).
|
||||||
|
|
||||||
|
This handler should be defined by any plugin that is concerned only with
|
||||||
|
physical keyswitch events, where the user has pressed or released a physical
|
||||||
|
key. For example, plugins that determine key values based on the timing of these
|
||||||
|
physical events should define this handler (for example, Qukeys and
|
||||||
|
TapDance). Plugins that don't explicitly need to use this handler should define
|
||||||
|
`onKeyEvent()` instead.
|
||||||
|
|
||||||
|
Plugins that use this handler should abide by certain rules in order to interact
|
||||||
|
with each other to avoid infinite loops. A plugin might return `ABORT` to delay
|
||||||
|
an event (until some other event or a timeout occurs), then later re-start
|
||||||
|
processing of the same event by calling `Runtime.handleKeyswitchEvent()`. When
|
||||||
|
it does this, it must take care to use the same `KeyEventId` value as that
|
||||||
|
event's `id` parameter, and it should also take care to preserve the order of
|
||||||
|
any such events. This way, plugins implementing `onKeyswitchEvent()` are able
|
||||||
|
to keep track of event id numbers that they have already processed fully, and
|
||||||
|
ignore those events when plugins later in the sequence re-start them.
|
||||||
|
|
||||||
|
In more specific detail, plugins that implement `onKeyswitchEvent()` must
|
||||||
|
guarantee that the `event.id` values they emit when returning `OK` are
|
||||||
|
monotonically increasing, and should only include `id` values that the plugin
|
||||||
|
has already received as input. Additionally, such plugins must ignore any event
|
||||||
|
with an `id` value that it has recently received and finished processing. The
|
||||||
|
class `KeyEventTracker` can help simplify following these rules.
|
||||||
|
|
||||||
|
### `onKeyEvent(KeyEvent &event)`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
`event.key` value gets inserted into `live_keys[event.addr]`. From that point
|
||||||
|
on, the keyboard will behave as though a key with that value is being held until
|
||||||
|
that entry in `live_keys` is cleared (most likely as a result of a key release
|
||||||
|
event's `onKeyEvent()` handlers returning `OK`). Thus, if an `onKeyEvent()`
|
||||||
|
handler returns `ABORT` for a key release event, the keyboard will behave as
|
||||||
|
though that key is still held after it has been released. This is what enables
|
||||||
|
plugins like OneShot to function, but it also means that plugin authors need to
|
||||||
|
take care about returning `ABORT` (but not `EVENT_CONSUMED`) from an
|
||||||
|
`onKeyEvent()` handler, because it could result in "stuck" keys.
|
||||||
|
|
||||||
|
`onKeyEvent()` handlers should not store events and release them later (by
|
||||||
|
calling `Runtime.handleKeyEvent()`), and must never call
|
||||||
|
`Runtime.handleKeyswitchEvent()`.
|
||||||
|
|
||||||
|
### `onAddToReport(Key key)`
|
||||||
|
|
||||||
|
After the `onKeyEvent()` handlers have all returned `OK`, Kaleidoscope moves on
|
||||||
|
to sending Keyboard HID reports. It clears the current report, and iterates
|
||||||
|
through the `live_keys` array, looking for non-empty values, and adding them to
|
||||||
|
the report. For System Control, Consumer Control, and Keyboard HID type `Key`
|
||||||
|
values, Kaleidoscope handles adding the keycodes to the correct report, but it
|
||||||
|
also calls this handler, in case a plugin needs to alter that report.
|
||||||
|
|
||||||
|
A return value of `OK` allows Kaleidoscope to proceed with adding the
|
||||||
|
corresponding keycode(s) to the HID report, and `ABORT` causes it to leave and
|
||||||
|
keycodes from `key` out of the report.
|
||||||
|
|
||||||
|
Note that this only applies to the Keyboard and Consumer Control HID reports,
|
||||||
|
not the System Control report, which has different semantics, and only supports
|
||||||
|
a single keycode at a time.
|
||||||
|
|
||||||
|
### `beforeReportingState(const KeyEvent &event)`
|
||||||
|
|
||||||
|
This gets called right before a set of HID reports is sent. At this point,
|
||||||
|
plugins have access to a (tentative) complete HID report, as well as the full
|
||||||
|
state of all live keys on the keyboard. This is especially useful for plugins
|
||||||
|
that might need to do things like remove keycodes (such as keyboard modifiers)
|
||||||
|
from the forthcoming report just before it gets sent.
|
||||||
|
|
||||||
|
This event handler still has access to the event information for the event that
|
||||||
|
triggered the report, but because it is passed as a `const` reference, it is no
|
||||||
|
longer possible to change any of its values.
|
||||||
|
|
||||||
|
[Note: The older version of `beforeReportingState()` got called once per cycle,
|
||||||
|
regardless of the pattern of keyswitches toggling on and off, and many plugins
|
||||||
|
used it as a place to do things like check for timeouts. This new version does
|
||||||
|
not get called every cycle, so when porting old code to the newer handlers, it's
|
||||||
|
important to move any code that must be called every cycle to either
|
||||||
|
`beforeEachCycle()` or `afterEachCycle()`.]
|
||||||
|
|
||||||
|
[Also note: Unlike the deprecated `beforeReportingState()`, this one is
|
||||||
|
abortable. That is, if it returns a result other than `OK` it will stop the
|
||||||
|
subsequent handlers from getting called, and if it returns `ABORT`, it will also
|
||||||
|
stop the report from being sent.]
|
||||||
|
|
||||||
|
## Other events
|
||||||
|
|
||||||
|
### `onLayerChange()`
|
||||||
|
|
||||||
|
Called whenever one or more keymap layers are activated or deactivated (just
|
||||||
|
after the change takes place).
|
||||||
|
|
||||||
|
### `onLEDModeChange()`
|
||||||
|
|
||||||
|
Called by `LEDControl` whenever the active LED mode changes.
|
||||||
|
|
||||||
|
### `beforeSyncingLeds()`
|
||||||
|
|
||||||
|
Called immediately before Kaleidoscope sends updated color values to the
|
||||||
|
LEDs. This event handler is particularly useful to plugins that need to override
|
||||||
|
the active LED mode (e.g. LED-ActiveModColor).
|
||||||
|
|
||||||
|
### `onFocusEvent()`
|
||||||
|
|
||||||
|
### `onNameQuery()`
|
||||||
|
|
||||||
|
### `exploreSketch()`
|
||||||
|
|
||||||
|
## Deprecated
|
||||||
|
|
||||||
|
Two existing "event" handlers have been deprecated. In the old version of
|
||||||
|
Kaleidoscope's main loop, the keyboard's state information was stored in the
|
||||||
|
keyscanner (which physical switches were on in the current and former scans),
|
||||||
|
and in the HID reports. The Keyboard HID report would be cleared at the start of
|
||||||
|
every cycle, and re-populated, on key at a time, calling every
|
||||||
|
`onKeyswitchEvent()` handler for every active key. Then, once the tentative HID
|
||||||
|
report was complete, the `beforeReportingState()` handlers would be called, and
|
||||||
|
the complete report would be sent to the host. In most cycles, that report would
|
||||||
|
be identical to the previous report, and would be suppressed.
|
||||||
|
|
||||||
|
The new system stores the keyboard's current state in the `live_keys` array
|
||||||
|
instead, and only calls event handlers in response to keyswitch state changes
|
||||||
|
(and artificially generated events), ultimately sending HID reports in response
|
||||||
|
to events, rather than at the end of every cycle.
|
||||||
|
|
||||||
|
### `onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state)`
|
||||||
|
|
||||||
|
This handler was called in every cycle, for every non-idle key. Its concept of
|
||||||
|
an "event" included held keys that did not have a state change. These deprecated
|
||||||
|
handlers are still called, in response to events and also when preparing the HID
|
||||||
|
reports, but there is no longer a reasonable mechanism to call them in every
|
||||||
|
cycle, for every active key, so some functionality could be lost.
|
||||||
|
|
||||||
|
It is strongly recommended to switch to using one of the two `KeyEvent`
|
||||||
|
functions instead, depending on the needs of the plugin (either `onKeyEvent()`
|
||||||
|
if it is fit for the purpose, or `onKeyswitchEvent()` if necessary). The
|
||||||
|
`onAddToReport()` function might also be useful, particularly if the plugin in
|
||||||
|
question uses special `Key` values not recognized by Kaleidoscope itself, but
|
||||||
|
which should result in keycodes being added to HID reports.
|
||||||
|
|
||||||
|
### `beforeReportingState()`
|
||||||
|
|
||||||
|
The old version of this handler has been deprecated, but it will still be called
|
||||||
|
both before HID reports are sent and also once per cycle. It is likely that
|
||||||
|
these handlers will continue to function, but the code therein should be moved
|
||||||
|
either to the new `KeyEvent` version of `beforeReportingState()` and/or
|
||||||
|
`afterEachCycle()` (or `beforeEachCycle()`), depending on whether it needs to be
|
||||||
|
run only in response to input events or if it must execute every cycle,
|
||||||
|
respectively.
|
@ -0,0 +1,79 @@
|
|||||||
|
/* -*- mode: c++ -*-
|
||||||
|
* AppSwitcher -- A Kaleidoscope Example
|
||||||
|
* Copyright (C) 2021 Keyboardio, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define KALEIDOSCOPE_HOSTOS_GUESSER 1
|
||||||
|
|
||||||
|
#include <Kaleidoscope-HostOS.h>
|
||||||
|
|
||||||
|
#include "AppSwitcher.h"
|
||||||
|
|
||||||
|
namespace kaleidoscope {
|
||||||
|
namespace plugin {
|
||||||
|
|
||||||
|
EventHandlerResult AppSwitcher::onKeyEvent(KeyEvent &event) {
|
||||||
|
// Ignore all key releases
|
||||||
|
if (keyToggledOff(event.state))
|
||||||
|
return EventHandlerResult::OK;
|
||||||
|
|
||||||
|
if (event.key == AppSwitcher_Next || event.key == AppSwitcher_Prev) {
|
||||||
|
bool add_shift_flag = false;
|
||||||
|
if (event.key == AppSwitcher_Prev) {
|
||||||
|
add_shift_flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For good measure:
|
||||||
|
event.state |= INJECTED;
|
||||||
|
|
||||||
|
// If AppSwitcher was not already active, hold its modifier first.
|
||||||
|
if (!active_addr_.isValid()) {
|
||||||
|
if (::HostOS.os() == hostos::OSX) {
|
||||||
|
event.key = Key_LeftGui;
|
||||||
|
} else {
|
||||||
|
event.key = Key_LeftAlt;
|
||||||
|
}
|
||||||
|
Runtime.handleKeyEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the event's key address so we don't clobber the modifier.
|
||||||
|
event.addr.clear();
|
||||||
|
event.key = Key_Tab;
|
||||||
|
if (add_shift_flag)
|
||||||
|
event.key.setFlags(SHIFT_HELD);
|
||||||
|
// Press tab
|
||||||
|
Runtime.handleKeyEvent(event);
|
||||||
|
// Change state to release; this will get processed when we return OK below.
|
||||||
|
event.state = WAS_PRESSED | INJECTED;
|
||||||
|
} else if (active_addr_.isValid()) {
|
||||||
|
// If any non-AppSwitcher key is pressed while AppSwitcher is active, that
|
||||||
|
// will close AppSwitcher instead of processing that keypress. We mask the
|
||||||
|
// address of the key that closed AppSwitcher so that its release doesn't
|
||||||
|
// have any effect. Then we turn the event for that key's press into an
|
||||||
|
// event for the release of the AppSwitcher's modifier key.
|
||||||
|
live_keys.mask(event.addr);
|
||||||
|
event.addr = active_addr_;
|
||||||
|
event.state = WAS_PRESSED | INJECTED;
|
||||||
|
event.key = live_keys[event.addr];
|
||||||
|
// Turn off AppSwitcher:
|
||||||
|
active_addr_.clear();
|
||||||
|
}
|
||||||
|
return EventHandlerResult::OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace plugin
|
||||||
|
} // namespace kaleidoscope
|
||||||
|
|
||||||
|
kaleidoscope::plugin::AppSwitcher AppSwitcher;
|
@ -0,0 +1,43 @@
|
|||||||
|
/* -*- mode: c++ -*-
|
||||||
|
* AppSwitcher -- A Kaleidoscope Example
|
||||||
|
* Copyright (C) 2021 Keyboardio, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Kaleidoscope.h>
|
||||||
|
#include "Kaleidoscope-Ranges.h"
|
||||||
|
|
||||||
|
constexpr Key AppSwitcher_Next{kaleidoscope::ranges::SAFE_START};
|
||||||
|
constexpr uint16_t _prev_val = AppSwitcher_Next.getRaw() + 1;
|
||||||
|
constexpr Key AppSwitcher_Prev{_prev_val};
|
||||||
|
|
||||||
|
namespace kaleidoscope {
|
||||||
|
namespace plugin {
|
||||||
|
|
||||||
|
class AppSwitcher : public kaleidoscope::Plugin {
|
||||||
|
|
||||||
|
public:
|
||||||
|
EventHandlerResult onKeyEvent(KeyEvent &event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
KeyAddr active_addr_ = KeyAddr::none();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace plugin
|
||||||
|
} // namespace kaleidoscope
|
||||||
|
|
||||||
|
extern kaleidoscope::plugin::AppSwitcher AppSwitcher;
|
@ -1,65 +0,0 @@
|
|||||||
/* -*- mode: c++ -*-
|
|
||||||
* AppSwitcher -- A Kaleidoscope Example
|
|
||||||
* Copyright (C) 2016-2018 Keyboardio, Inc.
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by the Free Software
|
|
||||||
* Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
||||||
* details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define KALEIDOSCOPE_HOSTOS_GUESSER 1
|
|
||||||
|
|
||||||
#include <Kaleidoscope-HostOS.h>
|
|
||||||
|
|
||||||
#include "Macros.h"
|
|
||||||
|
|
||||||
namespace H = kaleidoscope::hostos;
|
|
||||||
|
|
||||||
static bool appSwitchActive = false;
|
|
||||||
|
|
||||||
const macro_t *macroAppSwitch(uint8_t keyState) {
|
|
||||||
appSwitchActive = true;
|
|
||||||
|
|
||||||
// Key was just pressed, or is being held
|
|
||||||
if (keyIsPressed(keyState)) {
|
|
||||||
if (HostOS.os() == H::OSX)
|
|
||||||
return MACRO(Dr(Key_LeftGui), D(Tab));
|
|
||||||
else
|
|
||||||
return MACRO(Dr(Key_LeftAlt), D(Tab));
|
|
||||||
}
|
|
||||||
// Key was just released
|
|
||||||
if (keyToggledOff(keyState)) {
|
|
||||||
if (HostOS.os() == H::OSX)
|
|
||||||
return MACRO(U(Tab), Dr(Key_LeftGui));
|
|
||||||
else
|
|
||||||
return MACRO(U(Tab), Dr(Key_LeftAlt));
|
|
||||||
}
|
|
||||||
// otherwise we do nothing
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const macro_t *macroAppCancel(uint8_t keyState) {
|
|
||||||
if (keyToggledOn(keyState))
|
|
||||||
appSwitchActive = false;
|
|
||||||
return MACRO_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void macroAppSwitchLoop() {
|
|
||||||
Key mod = Key_LeftAlt;
|
|
||||||
|
|
||||||
if (HostOS.os() == H::OSX)
|
|
||||||
mod = Key_LeftGui;
|
|
||||||
|
|
||||||
// if appSwitchActive is true, we continue holding Alt.
|
|
||||||
if (appSwitchActive) {
|
|
||||||
handleKeyswitchEvent(mod, UnknownKeyswitchLocation, IS_PRESSED);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,154 @@
|
|||||||
|
/* -*- mode: c++ -*-
|
||||||
|
* Kaleidoscope-Macros Examples
|
||||||
|
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Kaleidoscope.h>
|
||||||
|
#include <Kaleidoscope-Macros.h>
|
||||||
|
#include <Kaleidoscope-OneShot.h>
|
||||||
|
|
||||||
|
// Macros
|
||||||
|
enum {
|
||||||
|
TOGGLE_ONESHOT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// *INDENT-OFF*
|
||||||
|
KEYMAPS(
|
||||||
|
[0] = KEYMAP_STACKED
|
||||||
|
(___, M(1), M(2), M(3), M(4), M(5), ___,
|
||||||
|
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
||||||
|
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
||||||
|
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
||||||
|
|
||||||
|
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
||||||
|
ShiftToLayer(1),
|
||||||
|
|
||||||
|
___, M(6), M(7), M(8), M(9), M(0), ___,
|
||||||
|
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
||||||
|
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
||||||
|
___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
||||||
|
|
||||||
|
Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl,
|
||||||
|
ShiftToLayer(1)),
|
||||||
|
|
||||||
|
[1] = KEYMAP_STACKED
|
||||||
|
(
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
___, ___, ___, ___, ___, ___,
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
|
||||||
|
___, ___, ___, ___,
|
||||||
|
___,
|
||||||
|
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
Key_UpArrow, Key_DownArrow, Key_LeftArrow, Key_RightArrow,___, ___,
|
||||||
|
___, ___, ___, ___, ___, ___, ___,
|
||||||
|
|
||||||
|
___, ___, ___, ___,
|
||||||
|
___),
|
||||||
|
)
|
||||||
|
// *INDENT-ON*
|
||||||
|
|
||||||
|
// Example macro for typing a string of characters.
|
||||||
|
void macroTypeString(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
Macros.type(PSTR("Hello, world!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example macro for macro step sequence.
|
||||||
|
const macro_t* macroSteps(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
// Note that the following sequence leaves two keys down (`Key_RightAlt` and
|
||||||
|
// `Key_C`). These virtual keys will remain in effect until the Macros key
|
||||||
|
// is released.
|
||||||
|
return MACRO(I(200), D(LeftShift), T(A), D(RightAlt), T(B), U(LeftShift), D(C));
|
||||||
|
}
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example macro that sets `event.key`.
|
||||||
|
const macro_t* macroNewSentence1(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
event.key = OSM(LeftShift);
|
||||||
|
return MACRO(Tc(Period), Tc(Spacebar), Tc(Spacebar));
|
||||||
|
}
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternate example for above.
|
||||||
|
void macroNewSentence2(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
Macros.type(PSTR(". "));
|
||||||
|
event.key = OSM(LeftShift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macro that calls `handleKeyEvent()`. This version works even if the OneShot
|
||||||
|
// plugin is registered before Macros in `KALEIDOSCOPE_INIT_PLUGINS()`.
|
||||||
|
void macroNewSentence3(KeyEvent &event) {
|
||||||
|
Macros.tap(Key_Period);
|
||||||
|
Macros.tap(Key_Spacebar);
|
||||||
|
Macros.tap(Key_Spacebar);
|
||||||
|
// Change the event into a OneShot key event.
|
||||||
|
event.key = OSM(LeftShift);
|
||||||
|
kaleidoscope::Runtime.handleKeyEvent(event);
|
||||||
|
// We can effectively erase the Macros key event, effectively aborting it.
|
||||||
|
event.key = Key_NoKey;
|
||||||
|
event.addr.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Macro that auto-repeats?
|
||||||
|
|
||||||
|
const macro_t* macroAction(uint8_t macro_id, KeyEvent &event) {
|
||||||
|
switch (macro_id) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
macroTypeString(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
return macroNewSentence1(event);
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
macroNewSentence2(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
macroNewSentence3(event);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
return macroSteps(event);
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For some of the above examples, it's important that Macros is registered
|
||||||
|
// before OneShot here.
|
||||||
|
KALEIDOSCOPE_INIT_PLUGINS(Macros, OneShot);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Kaleidoscope.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Kaleidoscope.loop();
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"cpu": {
|
||||||
|
"fqbn": "keyboardio:avr:model01",
|
||||||
|
"port": ""
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
# Upgrading Macros code
|
||||||
|
|
||||||
|
This is a guide to upgrading existing Macros code to use the new version of
|
||||||
|
Kaleidoscope and the Macros plugin.
|
||||||
|
|
||||||
|
## New `macroAction()` function
|
||||||
|
|
||||||
|
There is a new version of the `macroAction()` function, which is the entry point
|
||||||
|
for user-defined Macros code. The old version takes two integer parameters, with
|
||||||
|
the following call signature:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
const macro_t* macroAction(uint8_t macro_id, uint8_t key_state)
|
||||||
|
```
|
||||||
|
|
||||||
|
If your sketch has this function, with a `key_state` bitfield parameter, it
|
||||||
|
might still work as expected, but depending on the specifics of the code that
|
||||||
|
gets called from it, your macros might not work as expected. Either way, you
|
||||||
|
should update that function to the new version, which takes a `KeyEvent`
|
||||||
|
reference as its second parameter:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
const macro_t* macroAction(uint8_t macro_id, KeyEvent &event)
|
||||||
|
```
|
||||||
|
|
||||||
|
For simple macros, it is a simple matter of replacing `key_state` in the body of
|
||||||
|
the `macroAction()` code with `event.state`. This covers most cases where all
|
||||||
|
that's done is a call to `Macros.type()`, or a `MACRO()` or `MACRODOWN()`
|
||||||
|
sequence is returned.
|
||||||
|
|
||||||
|
|
||||||
|
## Code that calls `handleKeyswitchEvent()` or `pressKey()`
|
||||||
|
|
||||||
|
It is very likely that if you have custom code that calls
|
||||||
|
`handleKeyswitchEvent()` or `pressKey()` directly, it will no longer function
|
||||||
|
properly after upgrading. To adapt this code to the new `KeyEvent` system
|
||||||
|
requires a deeper understanding of the changes to Kaleidoscope, but likely
|
||||||
|
results in much simpler Macros code.
|
||||||
|
|
||||||
|
The first thing that is important to understand is that the `macroAction()`
|
||||||
|
function will now only be called when a Macros `Key` toggles on or off, not once
|
||||||
|
per cycle while the key is held. This is because the new event handling code in
|
||||||
|
Kaleidoscope only calls plugin handlers in those cases, dealing with one event
|
||||||
|
at a time, in a single pass through the plugin event handlers (rather than one
|
||||||
|
pass per active key)--and only sends a keyboard HID report in response to those
|
||||||
|
events, not once per scan cycle.
|
||||||
|
|
||||||
|
This means that any Macros code that is meant to keep keycodes in the keyboard
|
||||||
|
HID report while the Macros key is held needs to be changed. For example, if a
|
||||||
|
macro contained the following code:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
if (keyIsPressed(key_state)) {
|
||||||
|
Runtime.hid().keyboard().pressKey(Key_LeftShift);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
...that wouldn't work quite as expected, because as soon as the next key is
|
||||||
|
pressed, a new report would be generated without ever calling `macroAction()`,
|
||||||
|
and therefore that change to the HID report would not take place, effectively
|
||||||
|
turning off the `shift` modifier immediately before sending the report with the
|
||||||
|
keycode that it was intended to modify.
|
||||||
|
|
||||||
|
Furthermore, that `shift` modifier would never even get sent in the first place,
|
||||||
|
because the HID report no longer gets cleared at the beginning of every
|
||||||
|
cycle. Now it doesn't get cleared until _after_ the plugin event handlers get
|
||||||
|
called (in the case of Macros, that's `onKeyEvent()`, which calls the
|
||||||
|
user-defined `macroAction()` function), so any changes made to the HID report
|
||||||
|
from that function will be discarded before it's sent.
|
||||||
|
|
||||||
|
Instead of the above, there are two new mechanisms for keeping keys active while
|
||||||
|
a Macros key is pressed:
|
||||||
|
|
||||||
|
### Alter the `event.key` value
|
||||||
|
|
||||||
|
If your macro only needs to keep a single `Key` value active after running some
|
||||||
|
code, and doesn't need to run any custom code when the key is released, the
|
||||||
|
simplest thing to do is to override the event's `Key` value:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
// do some macro action(s)
|
||||||
|
event.key = Key_LeftShift;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will (temporarily) replace the Macros key with the value assigned (in this
|
||||||
|
case, `Key_LeftShift`), starting immediately after the `macroAction()` function
|
||||||
|
returns, and lasting until the key is released. This key value can include
|
||||||
|
modifier flags, or it can be a layer-shift, or any other valid `Key` value
|
||||||
|
(though it won't get processed by plugins that are initialized before Macros in
|
||||||
|
`KALEIDOSCOPE_INIT_PLUGINS()`, and Macros itself won't act on the value, if it
|
||||||
|
gets replaced by a different Macros key).
|
||||||
|
|
||||||
|
### Use the supplemental Macros `Key` array
|
||||||
|
|
||||||
|
The Macros plugin now contains a small array of `Key` values that will be
|
||||||
|
included when building HID reports triggered by subsequent, non-Macros
|
||||||
|
events. To use it, just call one (or more) of the following methods:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
Macros.press(key);
|
||||||
|
Macros.release(key);
|
||||||
|
Macros.tap(key)
|
||||||
|
```
|
||||||
|
|
||||||
|
Each one of these functions generates a new artificial key event, and processes
|
||||||
|
it (including sending a HID report, if necessary). For `press()` and
|
||||||
|
`release()`, it also stores the specified key's value in the Macros supplemental
|
||||||
|
`Key` array. In the case of the `tap()` function, it generates matching press
|
||||||
|
and release events, but skips storing them, assuming that no plugin will
|
||||||
|
generate an intervening event. All of the events generated by these functions
|
||||||
|
will be marked `INJECTED`, which will cause Macros itself (and many other
|
||||||
|
plugins) to ignore them.
|
||||||
|
|
||||||
|
This will allow you to keep multiple `Key` values active while a Macros key is
|
||||||
|
held, while leaving the Macros key itself active, enabling more custom code to
|
||||||
|
be called on its release. Note that whenever a Macros key is released, the
|
||||||
|
supplemental key array is cleared to minimize the chances of keycodes getting
|
||||||
|
"stuck". It is still possible to write a macro that will cause values to persist
|
||||||
|
in this array, however, by combining both a sequence that uses key presses
|
||||||
|
without matched releases _and_ replacing `event.key` (see above) in the same
|
||||||
|
macro.
|
||||||
|
|
||||||
|
### Borrow an idle key (not recommended)
|
||||||
|
|
||||||
|
It's also possible to "borrow" one (or more) idle keys on the keyboard by
|
||||||
|
searching the `live_keys[]` array for an empty entry, and generating a new event
|
||||||
|
with the address of that key. This is not recommended because surprising things
|
||||||
|
can happen if that key is then pressed and released, but it's still an option
|
||||||
|
for people who like to live dangerously.
|
||||||
|
|
||||||
|
|
||||||
|
## Code that calls `sendReport()`
|
||||||
|
|
||||||
|
Calling `sendReport()` directly from a macro is now almost always unnecessary.
|
||||||
|
Instead, a call to `Runtime.handleKeyEvent()` will result in a keyboard HID
|
||||||
|
report being sent in response to the generated event without needing to make it
|
||||||
|
explicit.
|
||||||
|
|
||||||
|
|
||||||
|
## Code that uses `Macros.key_addr`
|
||||||
|
|
||||||
|
This variable is deprecated. Instead, using the new `macroAction(id, event)`
|
||||||
|
function, the address of the Macros key is available via the `event.addr`
|
||||||
|
variable.
|
||||||
|
|
||||||
|
|
||||||
|
## Working with other plugins
|
||||||
|
|
||||||
|
### Plugin-specific `Key` values
|
||||||
|
|
||||||
|
When the the Macros plugin generates events, it marks the event state as
|
||||||
|
`INJECTED` in order to prevent unbounded recursion (Macros ignores injected
|
||||||
|
events). This causes most other plugins to ignore the event, as well.
|
||||||
|
Therefore, including a plugin-specific key (e.g. a OneShot modifier such as
|
||||||
|
`OSM(LeftAlt)`) will most likely be ignored by the target plugin, and will
|
||||||
|
therefore not have the desired effect. This applies to any calls to
|
||||||
|
`Macros.play()` (including returning `MACRO()` from `macroAction()`),
|
||||||
|
`Macros.tap()`, `Macros.press()`, and `Macros.release()`.
|
||||||
|
|
||||||
|
### Physical event plugins
|
||||||
|
|
||||||
|
Macros cannot usefully produce events handled by plugins that implement the
|
||||||
|
`onKeyswitchEvent()` handler, such as Qukeys, TapDance, and Leader. To make
|
||||||
|
those plugins work with Macros, it's necessary to have the other plugin produce
|
||||||
|
a Macros key, not the other way around. A `macroAction()` function must not call
|
||||||
|
`Runtime.handleKeyswitchEvent()`.
|
||||||
|
|
||||||
|
### OneShot
|
||||||
|
|
||||||
|
This is one plugin that you might specifically want to use with a macro,
|
||||||
|
generally at the end of a sequence. For example, a macro for ending one
|
||||||
|
sentence and beginning the next one might print a period followed by a space
|
||||||
|
(`. `), then a OneShot shift key tap, so that the next character will be
|
||||||
|
automatically capitalized. The problem, as mentioned before is that the
|
||||||
|
following won't work:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
MACRO(Tc(Period), Tc(Spacebar), Tr(OSM(LeftShift)))
|
||||||
|
```
|
||||||
|
|
||||||
|
...because OneShot will ignore the `INJECTED` event. One solution is to change
|
||||||
|
the value of `event.key`, turning the pressed Macros key into a OneShot
|
||||||
|
modifier. This will only work if Macros is registered before OneShot in
|
||||||
|
`KALEIDOSCOPE_INIT_PLUGINS()`:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
const macro_t* macroNewSentence(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
event.key = OSM(LeftShift);
|
||||||
|
return MACRO(Tc(Period), Tc(Spacebar));
|
||||||
|
}
|
||||||
|
return MACRO_NONE;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A more robust solution is to explicitly call `Runtime.handleKeyEvent()`, but
|
||||||
|
this is more complex, because you'll need to prevent the Macros key from
|
||||||
|
clobbering the OneShot key in the `live_keys[]` array:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void macroNewSentence(KeyEvent &event) {
|
||||||
|
if (keyToggledOn(event.state)) {
|
||||||
|
Macros.tap(Key_Period);
|
||||||
|
Macros.tap(Key_Spacebar);
|
||||||
|
event.key = OSM(LeftShift);
|
||||||
|
kaleidoscope::Runtime.handleKeyEvent(event);
|
||||||
|
// Last, we invalidate the current event's key address to prevent the Macros
|
||||||
|
// key value from clobbering the OneShot shift.
|
||||||
|
event.key = Key_NoKey;
|
||||||
|
event.addr.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
@ -0,0 +1,187 @@
|
|||||||
|
/* Kaleidoscope - Firmware for computer input devices
|
||||||
|
* Copyright (C) 2020 Keyboard.io, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "kaleidoscope/KeyAddr.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace kaleidoscope {
|
||||||
|
|
||||||
|
// Return the number of `UnitType` units required to store `n` bits. Both `UnitType` &
|
||||||
|
// `WidthType` should be integer types. `WidthType` is whatever type the parameter `n` is
|
||||||
|
// stored as, and can be deduced by the compiler, so it's not necessary to declare it
|
||||||
|
// when calling this function (e.g. `bitfieldSize<uint16_t>(n)`). The default `UnitType`
|
||||||
|
// is `byte` (i.e. `uint8_t`, which is almost always what we want, so most of the time we
|
||||||
|
// can also drop that template parameter (e.g. `bitfieldSize(n)`).
|
||||||
|
template <typename _UnitType = byte, typename _WidthType>
|
||||||
|
constexpr _WidthType bitfieldSize(_WidthType n) {
|
||||||
|
return ((n - 1) / (8 * sizeof(_UnitType))) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================================
|
||||||
|
// Generic Bitfield class, useful for defining KeyAddrBitfield, and others.
|
||||||
|
class KeyAddrBitfield {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
static constexpr uint8_t size = KeyAddr::upper_limit;
|
||||||
|
static constexpr uint8_t block_size = 8 * sizeof(uint8_t);
|
||||||
|
static constexpr uint8_t total_blocks = bitfieldSize<uint8_t>(size);
|
||||||
|
|
||||||
|
static constexpr uint8_t blockIndex(KeyAddr k) {
|
||||||
|
return k.toInt() / block_size;
|
||||||
|
}
|
||||||
|
static constexpr uint8_t bitIndex(KeyAddr k) {
|
||||||
|
return k.toInt() % block_size;
|
||||||
|
}
|
||||||
|
static constexpr KeyAddr index(uint8_t block_index, uint8_t bit_index) {
|
||||||
|
uint8_t offset = (block_index * block_size) + bit_index;
|
||||||
|
return KeyAddr(offset);
|
||||||
|
}
|
||||||
|
bool read(KeyAddr k) const {
|
||||||
|
// assert(k.toInt() < size);
|
||||||
|
return bitRead(data_[blockIndex(k)], bitIndex(k));
|
||||||
|
}
|
||||||
|
void set(KeyAddr k) {
|
||||||
|
// assert(k.toInt() < size);
|
||||||
|
bitSet(data_[blockIndex(k)], bitIndex(k));
|
||||||
|
}
|
||||||
|
void clear(KeyAddr k) {
|
||||||
|
// assert(k.toInt() < size);
|
||||||
|
bitClear(data_[blockIndex(k)], bitIndex(k));
|
||||||
|
}
|
||||||
|
void write(KeyAddr k, bool value) {
|
||||||
|
// assert(k.toInt() < size);
|
||||||
|
bitWrite(data_[blockIndex(k)], bitIndex(k), value);
|
||||||
|
}
|
||||||
|
void clear() {
|
||||||
|
memset(data_, 0, sizeof(data_));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function returns the number of set bits in the bitfield up to and
|
||||||
|
// including the bit at index `k`. Two important things to note: it doesn't
|
||||||
|
// verify that the bit for index `k` is set (the caller must do so first,
|
||||||
|
// using `read()`), and what is returned is 1-indexed, so the caller will need
|
||||||
|
// to subtract 1 before using it as an array index (e.g. when doing a `Key`
|
||||||
|
// lookup for a sparse keymap layer).
|
||||||
|
uint8_t ordinal(KeyAddr k) const {
|
||||||
|
// assert(k.toInt() < size);
|
||||||
|
uint8_t block_index = blockIndex(k);
|
||||||
|
uint8_t count{0};
|
||||||
|
for (uint8_t b{0}; b < block_index; ++b) {
|
||||||
|
count += __builtin_popcount(data_[b]);
|
||||||
|
}
|
||||||
|
uint8_t last_data_unit = data_[block_index];
|
||||||
|
last_data_unit &= ~(0xFF << bitIndex(k));
|
||||||
|
count += __builtin_popcount(last_data_unit);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t &block(uint8_t block_index) {
|
||||||
|
// assert(block_index < total_blocks);
|
||||||
|
return data_[block_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
uint8_t data_[total_blocks] = {};
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Iterator!
|
||||||
|
public:
|
||||||
|
class Iterator;
|
||||||
|
friend class KeyAddrBitfield::Iterator;
|
||||||
|
|
||||||
|
Iterator begin() {
|
||||||
|
return Iterator{*this, 0};
|
||||||
|
}
|
||||||
|
Iterator end() {
|
||||||
|
return Iterator{*this, total_blocks};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
Iterator(KeyAddrBitfield &bitfield, uint8_t x)
|
||||||
|
: bitfield_(bitfield), block_index_(x) {}
|
||||||
|
|
||||||
|
bool operator!=(const Iterator &other) {
|
||||||
|
// First, the test for the end condition (return false when all the blocks have been
|
||||||
|
// tested):
|
||||||
|
while (block_index_ < other.block_index_) {
|
||||||
|
// Get the data for the block at `block_index_` from the bitfield, then shift it
|
||||||
|
// by the number of bits we've already checked (`bit_index_`):
|
||||||
|
block_ = bitfield_.data_[block_index_];
|
||||||
|
block_ >>= bit_index_;
|
||||||
|
|
||||||
|
// Now we iterate through that block until we either find a bit that is set, or we
|
||||||
|
// find that there are no more bits set. If (as expected most of the time) no bits
|
||||||
|
// are set, we do nothing:
|
||||||
|
while (block_ != 0) {
|
||||||
|
// If the low (remaining) bit is set, generate an `KeyAddr` object from the
|
||||||
|
// bitfield coordinates and store it for the dereference operator to return:
|
||||||
|
if (block_ & 1) {
|
||||||
|
index_ = KeyAddrBitfield::index(block_index_, bit_index_);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// The low bit wasn't set, so we shift the data block by one and track that
|
||||||
|
// shift with the bit coordinate (`bit_index_`):
|
||||||
|
block_ >>= 1;
|
||||||
|
bit_index_ += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we're done checking a block, move on to the next one:
|
||||||
|
block_index_ += 1;
|
||||||
|
bit_index_ = 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyAddr operator*() {
|
||||||
|
// assert(index_ < size);
|
||||||
|
return index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator++() {
|
||||||
|
++bit_index_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KeyAddrBitfield &bitfield_;
|
||||||
|
uint8_t block_index_; // index of the block
|
||||||
|
uint8_t bit_index_{0}; // bit index in the block
|
||||||
|
uint8_t block_;
|
||||||
|
KeyAddr index_;
|
||||||
|
|
||||||
|
}; // class Iterator {
|
||||||
|
|
||||||
|
} __attribute__((packed)); // class KeyAddrBitfield {
|
||||||
|
|
||||||
|
} // namespace kaleidoscope {
|
||||||
|
|
||||||
|
|
||||||
|
// ================================================================================
|
||||||
|
// How to use the iterator above:
|
||||||
|
#if 0
|
||||||
|
// To use the KeyAddrBitfield::Iterator, write a loop like the following:
|
||||||
|
KeyAddrBitfield bitfield;
|
||||||
|
for (KeyAddr k : bitfield) {
|
||||||
|
// Here, you'll get a `KeyAddr` object for each bit that is set in `bitfield`.
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,98 @@
|
|||||||
|
/* Kaleidoscope - Firmware for computer input devices
|
||||||
|
* Copyright (C) 2013-2021 Keyboard.io, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "kaleidoscope_internal/device.h"
|
||||||
|
#include "kaleidoscope/KeyAddr.h"
|
||||||
|
#include "kaleidoscope/key_defs.h"
|
||||||
|
|
||||||
|
namespace kaleidoscope {
|
||||||
|
|
||||||
|
// A `KeyAddrMap` is a collection of objects, indexed by `KeyAddr`, with one
|
||||||
|
// entry per key on the keyboard.
|
||||||
|
|
||||||
|
template<typename _ContentType, uint8_t _size>
|
||||||
|
class KeyAddrMap {
|
||||||
|
|
||||||
|
private:
|
||||||
|
_ContentType values_[_size]; // NOLINT(runtime/arrays)
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef KeyAddrMap<_ContentType, _size> ThisType;
|
||||||
|
|
||||||
|
// Return the number of `Key` entries in the array
|
||||||
|
static constexpr uint8_t size() {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// To set the value of an entry:
|
||||||
|
// key_array[key_addr] = Key_X;
|
||||||
|
_ContentType& operator[](KeyAddr key_addr) {
|
||||||
|
return values_[key_addr.toInt()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// To get the value of an entry:
|
||||||
|
// Key key = key_array[key_addr];
|
||||||
|
const _ContentType& operator[](KeyAddr key_addr) const {
|
||||||
|
return values_[key_addr.toInt()];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// The following code defines an iterator class for a class `KeyMap`, such
|
||||||
|
// that we can write the following code to get each entry in the array:
|
||||||
|
//
|
||||||
|
// typedef KeyAddrMap<Key> KeyMap;
|
||||||
|
//
|
||||||
|
// for (Key key : key_map) {...}
|
||||||
|
//
|
||||||
|
// Or, if we need write access to the entries in the array:
|
||||||
|
//
|
||||||
|
// for (Key &key : key_map) {...}
|
||||||
|
private:
|
||||||
|
class Iterator;
|
||||||
|
friend class ThisType::Iterator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Iterator begin() {
|
||||||
|
return {*this, KeyAddr(uint8_t(0))};
|
||||||
|
}
|
||||||
|
Iterator end() {
|
||||||
|
return {*this, KeyAddr(_size)};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
Iterator(ThisType &map, KeyAddr key_addr)
|
||||||
|
: map_(map), key_addr_(key_addr) {}
|
||||||
|
bool operator!=(const Iterator &other) const {
|
||||||
|
return key_addr_ != other.key_addr_;
|
||||||
|
}
|
||||||
|
_ContentType& operator*() const {
|
||||||
|
return map_[key_addr_];
|
||||||
|
}
|
||||||
|
Iterator& operator++() {
|
||||||
|
++key_addr_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
ThisType &map_;
|
||||||
|
KeyAddr key_addr_;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kaleidoscope
|
@ -0,0 +1,23 @@
|
|||||||
|
/* Kaleidoscope - Firmware for computer input devices
|
||||||
|
* Copyright (C) 2020 Keyboard.io, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU General Public License as published by the Free Software
|
||||||
|
* Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
* details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "kaleidoscope/KeyEvent.h"
|
||||||
|
|
||||||
|
namespace kaleidoscope {
|
||||||
|
|
||||||
|
KeyEventId KeyEvent::last_id_ = 0;
|
||||||
|
|
||||||
|
} // namespace kaleidoscope
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue