This new plugin will allow MouseKeys to be configured via Focus, and store and
retrieve its config parameters from EEPROM, enabling Chrysalis to control it.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a major rewrite of the MouseKeys plugin, primarily focused on mouse
cursor movement keys. There is no change to the keys themselves, and the
behaviour is fairly similar, but there are now better configuration parameters
and defaults. The new parameters are a minimum speed, a maximum speed, and the
length of time it takes to reach that maximum speed, without needing to worry
about the report update interval.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Remove the effectively unused `initial_suspend_` variable. It wasn't
doing anything useful, because it was set to false on the first round
through the event loop. Also remove the unused reference to the internal
AVR USB core variable `_usbSuspendState`.
Saves 18 bytes of flash on AVR, and 32 on GD32.
Signed-off-by: Taylor Yu <tlyu@mit.edu>
Fix a race condition in `sketch.mk` that tends to pop up when doing
`make -j smoke-sketches`. Ignore failures from `install -d`, because
they seem to only occur due to a TOCTOU condition in `install`.
Signed-off-by: Taylor Yu <tlyu@mit.edu>
The difference between the AVR and the GD32 implementation was the way we
checked if the device is suspended, otherwise the logic was exactly the same,
duplicated.
To remove the need for duplicating the same code for every architecture we
support, lift out the suspension check instead. This way the core logic becomes
shared between all of them.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
detecting the device port at evaluation time rather than execution time.
This meant that we were doing the "where is the board" check for any
compilation target, even if we'd never flash.
Arduino's board probing is somewhat heavyweight and can take a couple of
seconds.
We move that logic into a shell expression executed at runtime.
On my laptop, this shaves 10 seconds off make -j 9 simulator tests,
which is pretty nice since that used to take about 30 seconds.
But on a plain `make simulator-tests`, it shaved a full minute from the
2 minute and 30 second runtime.
When flushing the on-tap action, we need to use `handleKeyEvent`, because our
address may not be valid, and `handleKeyswitchEvent` will not perform the action
if the address is invalid.
This addresses keyboardio/Chrysalis#1055.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This reverts commit 1d8eb6d2e2.
Rainbow effects on the Model 100 had some harsh transitions,
especially from red/orange to yellow.
@obra reported that the gamma implementation was disabled on the
Model 100 earlier because it broke the firmware so badly that
the keyboard was unusable for typing. This no longer seems to be
the case, possibly due to changes in the Arduino core and/or
<pgmspace.h> compatibility.
Signed-off-by: Taylor Yu <tlyu@mit.edu>
Updates for the FocusSerial documentation, showing the new patterns, and
documenting the new methods.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
With the new patterns, `Focus.inputMatchesCommand(command, cmd)` felt wrong, so
this patch renames the `command` argument of `onFocusEvent()` to `input`, to
better match what it really is.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
In the past, we were using `Focus.handleHelp()` to see if we're handling a
`help` command, and print the commands supported by the handler. We also used
`strcmp_P` directly to compare (parts of) our input against command supported by
the handler. This approach works, but had multiple major disadvantages: it
duplicated strings between `handleHelp` and the `strcmp_P` calls, and it relied
on fragile substring pointers to save space.
To replace all that, this patch implements a different approach. Help handling
is split between a check (`Focus.inputMatchesHelp()`) and a
reply (`Focus.printHelp()`), the latter of which takes a list of `PSTR()`
strings, rather than one single string. This allows us to reuse the same
strings for comparing against the handler's input.
The new approach no longer uses the fragile substring pointers, nor does it use
`strcmp_P` directly, but goes through a wrapper (`Focus.inputMatchesCommand()`)
instead.
These changes lead to a more readable pattern. While we do use longer strings as
a result, there is less duplication, and the new patterns also require less
code, so we end up with saving space, at least on AVR devices.
The old methods are still available and usable, but they're deprecated.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
`Serial.read()` is unbuffered, and will return `-1` (which we cast to an
unsigned char, so 255) immediately if there is no data in the incoming buffer.
This is unlike every other kind of read we do, which use `parseInt()`, and are
thus buffered reads with a timeout.
The problem with returning `-1` immediately is that Chrysalis sends data in
chunks, so if we end up trying to read a char at a chunk boundary, we'll end up
reading -1s, which we treat as valid data, and cast it to an unsigned char,
completely throwing off the protocol in the process.
By using `readBytes()`, we have a one second window during which more data can
arrive, and as such, is consistent with the rest of our reads.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The new plugin enables configuring some aspects of SpaceCadet through Focus: the
current mode, and the global timeout. This is makes it possible to ship firmware
with SpaceCadet included, disabled by default, but still allow one to enable it
without having to map and tap the enable key.
The settings are also persisted into storage.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The example in the documentation was referring to a function that does not
exist. Correct it to use the one that does.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Fall back to `dash` on macOS, because `bash` randomly drops serial
input, causing the tool to hang.
Flush the command buffer before sending the requested command. A
failed upload session can cause characters to remain in the command
buffer.
Redirect stdin instead of using a separate file descriptor. Also do
this before running `stty`. This allows the `stty` settings to
actually take effect on macOS, which seems to reset the termios
state of serial devices upon the last close of the device.
Tested on macOS 10.15 and Ubuntu 20.04.
Signed-off-by: Taylor Yu <tlyu@mit.edu>
Previously, a newline that wasn't read with the rest of the command in
a single event would get appended to the command buffer instead of
remaining in the "peek" buffer, so the command dispatch would never
fire.
Stash a space delimiter instead of storing it in the buffer, only to
overwrite it later.
Fix an off-by-one error that could cause a buffered command to not be
null-terminated. This probably didn't cause a problem in practice,
because Focus plugins mostly did a `strcmp` against a fixed string that
is smaller than the command buffer size.
Signed-off-by: Taylor Yu <tlyu@mit.edu>
The new plugin provides a Focus-based interface for setting custom layer names.
The layer names aren't used internally, they're purely for use by host-side
applications.
This addresses the Kaleidoscope-side of keyboardio/Chrysalis#3.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
It was always intended to be public - it is even documented as such! -, but was
mistakenly left private.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Instead of simply echoing "device.reset" into the device port, call
`bin/focus-send`, which sets the `raw` setting on the port, before writing into
it. Without that, the command is not recognised by the firmware.
We could do the stty call in the makefile directly, but to support macOS, we'd
need to treat that specially, and at that point, it's easier to just call
`bin/focus-send`. The sketch relies on having the Kaleidoscope repo available
anyway, so we can safely rely on `bin/focus-send` being there, too.
Because we're now using the tool in the makefiles, `focus-test` was renamed to
`focus-send`.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
A number of devices declared the bootloader in their device properties as
`BootLoader`, while the base class, and anything using it, was looking for
`Bootloader`. This resulted in these devices using the default, dummy
bootloader, rather than the one we intended to set.
This patch corrects that, and everything's using `Bootloader` now, including the
documentation.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This simple plugin does nothing more than provide a `version` focus command,
which will print the firmware version configured at build-time (defaulting to
"0.0.0").
This is a header-only plugin, so that Arduino compiles it in the same
compilation unit as the main sketch, allowing us to set the version from the
sketch, if so desired.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The Model100 has a lot more space available compared to the Model01, so we can
have more layers in EEPROM. While we could have more than 8, 8 is the limit that
OneShot and dual-use keys support via Chrysalis, so to avoid potential
confusion, lets have 8 layers only.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This enables the IdleLEDs, Qukeys, OneShot, Escape-OneShot and DynamicMacros
plugins for the Model100 sketch. None of these - apart from IdleLEDs - cause any
change in behavior unless first configured so.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This new plugin lets us store a default palette and colormap in PROGMEM.
Rather than teaching Colormap to pull from either EEPROM or PROGMEM, this
implements an entirely separate plugin, `DefaultColormap`, which is able
to *push* a palette and colormaps into Colormap.
When `DefaultColormap.setup()` is called, it checks if Colormap's storage area
is empty (both palette and the map must be empty), and if so, copies the
built-in palette and colormap over, and forces a refresh, and it has done its
job.
It does provide an additional Focus command too, `colormap.install`, which will
forcibly copy both palette and colormaps over. Useful for resetting back to a
factory setting.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
To be able to set a theme from the firmware itself, we need a couple of helper
methods. First, we need to be able to update the palette without using Focus,
and we also need to be able to update a single LED without committing it. On top
of that, we'll likely want to know if the theme is initialized.
To this end, we introduce `updatePaletteColor()`, which updates an entry in the
palette, but does not commit it, and `isThemeUninitialized()` which does as the
name suggests: it checks if the palette and the theme slices are all uninitialized.
We also change `updateColorIndexAtPosition()` to not commit. The single user of
it was FingerPainter, and we update that to do an explicit commit after.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This test verifies that PrefixLayer doesn't clear held non-modifier keys from
the report before sending the prefix sequence.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This new plugin provides a way to set a default (but configurable, via Focus)
led mode, or use a specific one if EEPROM is uninitialized.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This basically reverts 07dcf1dc9b, returning the
plugin to its original behaviour of persisting the current led mode. We do this
because we'll be using a new, different plugin to set the default led mode, to
not conflate the two different functionalities.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
`kaleidoscope::driver::storage::AVREEPROM` wasn't implementing its own
`isSliceUninitialized()` method, and relied on the Base class to do so. However,
since we're not using `virtual` methods, the base class was using
`Base::read()` (which always returns 0) rather than `AVREEPROM::read()`, so
`isSliceUninitialized()` always returned false.
To fix this, we implement the function in `AVREEPROM`, and let the default
implementation in Base always return false.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This plugin provides a shared virtual keys array used by Macros and
DynamicMacros, along with some functions to interact with it (`press()`,
`release()`, `tap()`, `clear()`).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The plugin was both more complex and less accurate than it could have been. For
simplicity, it used a weighted average, with each cycle getting twice the weight
of the previous one. As a result, the reported average really only took into
account the last three or four cycles. On a keyboard with LEDs, some cycles
take much longer than others because of relatively rare updates, so this could
lead to misleading results, with the "average" cycle time usually being reported
as lower than it really should have been, and occasionally much higher.
This new version computes an evenly-weighted mean cycle time for each interval,
and runs more efficiently, by dividing the total elapsed time by the number of
cycles that has passed since the last report, rather than computing the time for
each individual cycle.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
We'd like to be able to set the default LED mode via Focus, so it can be
configured via Chrysalis. However, we may not want auto-save, so make that
configurable too.
To preserve the EEPROM layout, the highest bit of the previous led mode index
setting was repurposed for the auto save setting. This lets us set the default
mode to anything between 0 and 126 (or 127, if auto save is turned off).
While there, we also add an `onNameQuery` handler, to make it easier for
Chrysalis to detect if the plugin is available.
This addresses the Kaleidoscope parts of keyboardio/Chrysalis#846.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
I think it's reasonable to assume that other plugins won't be bad actors and
remove an active Turbo key from the live keys array unceremoniously, so this
check is really unnecessary.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Previously, the "sticky" state was simply ignored. Now it's handled properly,
leaving the "sticky" active Turbo key in the live keys array.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Our lookup table should have 32 entries, not 31, as Kaleidoscope-Ranges gives
DynamicMacros 32 entries.
Thanks @gedankenexperimenter for spotting this!
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The plugin was restricted to the Model01, because it depends on a very specific
key coordinate -> geometric shape mapping. Because the Model 01 and the Model
100 share this mapping, we can safely enable the plugin for the latter, too.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
There were a number of problems with how we updated and handled our cache.
First of all, we did not support empty macros that consist only of a single
`MACRO_ACTION_END`: we returned immediately as soon as we encountered one such.
This is undesirable, we want to support such empty macros.
Seconds, we did _not_ bail out early when encountering an unknown step. We
continued reading from storage until we reached our slice's end. That's also
undesirable, because data past an unknown step is not something we can reliably
parse. We should have bailed out here.
On top of that, we did not keep our id->location map in good shape. The initial
cache update did the right thing, but if we did an update where we ended up with
less macros, our map would have dangling pointers for macro ids that no longer
exist. That's not a problem when our update clears the rest of the storage
slice, but if it doesn't, the results of trying to run an unknown macro would be
unpredictable. Even if we don't care about that, it's still very inefficient,
especially when we have large macro storage.
So, this update does a whole lot of things to improve the situation:
We now keep track of how many macros we find during a cache update, and will
refuse to play macro ids that are beyond our count. This improves efficiency,
and protects us from trying to run garbage.
We also support empty macros now, and return early from a cache update when we
encounter an unknown step type.
Fixes#1177.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
We should be treating the end of data the same way we treat a newline.
A common issue that affects a good number of plugins is that we don't deal with
trailing space well. For example, most plugins that respond with a large list of
values, where we iterate over an array or list, or something else, they usually
end up responding with a trailing space before the newline.
If we feed that same string back as an update, we can end up in a situation
where we lock up (or become very, very slow), because we want to read more data
than is available. Why? Because `Serial.parseInt()` (used by Focus under the
hood) will swallow up any leading whitespace. So if we have "255 \n" as an
argument list, we'll parse the first number, and the second `parseInt()` will
return 0, because it times out waiting for a number, consuming both the space
and the newline in the process. Thus, the next `::isEOL()` will still return
false, because `peek()` returns `-1`, signaling no data.
That can confuse the heck out of our plugins. To combat that, we should treat
end of data the same as EOL, and return false if `peek()` returns -1, too.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
When updating our map via Focus, do not read past `storage_size_`, because we do
not want to clobber storage space past our slice by accident.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
On GD32, while the build process does create .hex files, we use the .bin ones
for flashing. So copy those to the appropriate place, and do the same symlinking
as for the .elf and .hex files.
Because .bin is not compiled on all platforms, we guard it with an if that
checks for its existence.
There might be a better way to do this, but this was fast and easy.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This matches the default EEPROM size of the underlying FlashStorage library, and
is substantially bigger than AVR-based keyboards, yet not _too_ big.
For various reasons, we're mirroring EEPROM into RAM 1:1, so we're constrained
by the size of that, too. That makes larger storage sizes undesirable at this
time. On top of this limitation, larger storage sizes also pose backup & restore
speed issues with Chrysalis, so lets settle for 16k, which is still very big,
all things considered, but not big enough to be a problem.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The `::isSliceUninitialized()` method is required by the Base flash driver
class, but `GD32Flash` did not implement it. While the Base class does so,
`GD32Flash` is subclassing `EEPROMClass`, rather than `storage::Base`, for
historical and technical reasons. As such, we need to implement this method
ourselves.
We could probably use multiple inheritance, but I feel that would be more
trouble than its worth.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
DynamicMacros was missing a necessary `beforeReportingState()` handler that is
responsible for adding keys held by an active macro to the HID report. This
handler is identical to the one used by the Macros plugin for the same purpose.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Both Macros and DynamicMacros were only reading one byte for each `Key` object
in a tap sequence, so it would first read the flags byte of each key in the
sequence and treat it as a keycode byte, using a flags byte of `0`. As soon as
an unmodified keyboard key was encountered, this would be recognized as the end
of the sequence. This change fixes the bug by reading and using the flags byte
of each key in the sequence as intended.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Because GitHub's Ubuntu image offers multiple version fo clang-format, and
sometimes it seems to prefer a version lower than 12 (which is the minimum
required by our config file), I added an override via the new environment
variable `KALEIDOSCOPE_CODE_FORMATTER`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This mixes some manual work (IWYU pragmas, a better solution to the Arduino
preprocessor macros problem) with automated running of the tools. At this
point, it would be too much work to separate these into distinct commits, and
there isn't that much value to doing so.
There are still some things we could do to make things more robust, as some of
the headers need to be in a certain order, which happens to be in the same sort
order used by IWYU (`testing/*` files need to come after certain headers than
include `Arduino.h`), but it's probably not worth the clutter of adding an `#if
1` just to stop IWYU from re-ordering them.
I tried to get `#pragma push_macro("max")/pop_macro("max")` to work, but ended
up getting completely nonsensical compilation errors, so I gave up on it.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This adds a `bin/fix-header-includes` script that uses `iwyu.py` and
`format-code.py` to manage header includes in Kaleidoscope source files. In
addition to the `src` and `plugins` trees, it is now also capable of handling
files in `testing` for the test simulator, which introduces some particular
complications due to Arduino's ill-advised `min` and `max` preprocessor macros.
The new `fix-header-includes` script can be run on a repository, and runs IWYU
and clang-format on code that differs between the current worktree and a
specified commit (default: `origin/master`), hopefully ensuring compliance with
the code style guide.
Also added: a new `check-all-includes` makefile target meant to be useful for
running as a git workflows check.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Suppose the following:
`foo.cpp` refers to the symbol `Bar`, declared in `bar.h`.
`foo.h` includes `bar.h`.
`foo.cpp` includes `foo.h`, but not `bar.h`.
`foo.h` does not refer to any symbols declared in `bar.h`.
If we process `foo.h` first, `#include "bar.h"` will be removed, causing IWYU to
fail with an error when it tries to process `foo.cpp`, but if we process them in
the other order, `foo.cpp` will get that include before it gets removed from
`foo.h`. This change sorts the files to be processed, putting all cpp files
first, then all header files, minimizing that problem.
We could do even better by saving the results from `include-what-you-use` for
every file, then going back and calling `fix_includes.py` on each of them, but I
don't think it's worth it.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This allows `bin/iwyu.py` to get library dirs other than the ones for
Kaleidoscope when running on files in `testing`, without risk of collisions when
running on Kaleidoscope itself.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This means we can just include `Runtime.h` instead of all of `Kaleidoscope.h`,
as a way of narrowing header includes.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The `TEST()` macro defined in `macro_helpers.h` shared a name with a critical
macro used by gtest code in the simulator, making simulator code very sensitive
to the order of header includes, with rather unhelpful error messages when it
failed. This change renames the offending macro, and a related one, for good
measure. Neither renamed macro was directly used by any Kaleidoscope code, so
this doesn't affect any APIs.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This causes the check to proceed even where there are unstaged changes in the
working tree.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This target calls include-what-you-use, followed by clang-format, and checks to
verify that there are no changes as a result.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Instead of using the phony `DEFAULT_GOAL` target, use make's special variable
`.DEFAULT_GOAL` to work around the problem of including arduino-cli.mk at the
top of the sketch makefile.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
There were sometimes two blank lines instead of one between makefile targets,
without any clear pattern. Now each one is separated from the next by one blank
line.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Instead of one long line declaring a lot of makefile targets as phony, include a
line like `.PHONY: <target>` immediately above each one. This makes the
makefile longer, but it's now obvious if the target you're looking at is phony.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This replaces the shell script with a python script that doesn't hardcode the
paths, and changes the makefile so that it also checks plugins for filename
conflicts, since they might suffer from the same problem.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is the result of running the `include-what-you-use` wrapper, followed by
the `clang-format` wrapper on the Kaleidoscope codebase. It is now safe to use
both without needed any manual corrections after the fact, but it's still
necessary to run clang-format after IWYU, because the two differ in the way they
indent comments after header files.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This file contains a list of files (shell globbing) that will be ignored by the
IWYU wrapper script, since we have a number of such files in Kaleidoscope that
either can't be parsed properly (because we're using clang and the virtual
hardware device) or will be mangled (either because of IWYU bugs or
peculiarities in the Kaleidoscope code).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This adds better argument parsing, and more useful options for detecting an
analyzing errors. I relies on version >=0.18 of IWYU to work properly, because
prior to that its exit codes were non-standard and unhelpful.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The output of test results from gtest gets sent to stdout, so it makes more
sense to send this to stdout, as well.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Using `make -jN simulator-tests` wasn't producing colored output because it was
not detecting the output as a terminal correctly. This change restores the
terminal coloring when running the simulator.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This applies to turning off formatting of keymaps. There were a few files that
were missing these comments, so those were added where necessary, as well.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This python script is more configurable than the shell script that it replaces,
and makes it clearer when looking at the makefile what it's acting on.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Rather than specifying all the filters on the command-line in the makefile, we
use two different cpplint config files (.cpplint for normal operation, and
.cpplint-noisy for more verbose analysis).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a copy of cpplint/cpplint#198, letting us use alternative config files
when running cpplint.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This brings our copy of cpplint up to date as of 2022-03-20, copied from
https://github.com/cpplint/cpplint, revision 7d9920c459dcdd55b043a2450c6ee3519102d106
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Tested to work on both a Model 01 and a Model 100. This will become
unnecessary when the GD32 Arduino core grows the ability to autoflush on
SOF packets from the host
The class we're deriving from - `FlashAsEEPROM`'s `EEPROMClass` - already has a
`commit()` method, we do not need our own in the child class, especially not
when all it does is call the parent.
We previously added this call because in the original implementation of the
driver, we had an empty `commit()`. The correct fix is to remove the override,
rather than reimplement the inheritance.
Spotted by @obra, thanks!
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Before doing anything else, initialize the device, so that whatever the rest of
the hooks we'll call do, they'll be able to rely on an initialized device.
Thanks to @obra for catching this, and proposing the patch!
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Switch the plugin to use the `F303CC_GENERIC` variant rather than `F303ZE_EVAL`,
to match the hardware more closely. Also drop the keyscanner, we do not need
that for most testing, and it was just complicating things.
On top of those simplifications, add a `serialPort()` method to make
`FocusSerial` work, and `rebootBootloader()` to make rebooting function as one
would expect.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
There are a number of places in our code where clang-format tries too hard, and
destroys human readability, so I protected them with `clang-format off`
directives.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
I added a `.clang-format` file to try to get as close as possible to our current
code formatting. I also updated the makefile targets and the github workflows.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Three plugins (`AutoShiftConfig`, `EscapeOneShotConfig` and `TypingBreaks`) that
used the same checker pattern to see if their storage slice is uninitialized now
use the new `storage().isSliceUninitialized()` method instead.
This reduces code duplication, among other things.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
First, we add an `uinitialized_byte` prop to `BaseProps`, so implementations
that don't use `0xff` as default can set it to whatever they use. Do keep in
mind that plenty of code still assumes `0xff` is the uninitialized byte, but
this way we have a starting point.
Then, we add an `isSliceUninitialized()` function, which checks whether a given
slice is completely made up from uninitialized bytes or not.
Fixes#1145.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
https://blog.melski.net/2010/11/15/shell-commands-in-gnu-make/
"In short: not using := assignment can cause your makefile to invoke the
shell far more often than you realize, which can be a performance
problem, and leave you with unpredictable build results. Always use :=
assignment with $(shell)."
* only check on specific makefile targets
* use make's dependencies to propagate the check through the places we'd
normally want it for sketch makefiles
This standardizes namespace closing brackets for namespace blocks. Each one is
on its own line, with a comment clearly marking which namespace it closes.
Consecutive lines closing namespace blocks have no whitespace between them, but
there is one blank line before and after a set of namespace block closing lines.
To generate the namespace comments, I used clang-format, with
`FixNamespaceComments: true`. But since clang-format can't exactly duplicate
our astyle formatting, it made lots of other changes, too. To isolate the
namespace comments from the other formatting changes, I first ran clang-format
with `FixNamespaceComments: false`, committed those changes, then ran it again
to generate the namespace comments. Then I stashed the namespace comments,
reset `HEAD` to remove the other changes, applied the stashed namespace
comments, and committed the results (after examining them and making a few minor
adjustments by hand).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
I ran `include-what-you-use` wherever possible, and made a number of manual
changes required to get things working with the new header organization.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
DynamicMacros reads sequences from EEPROM, not PROGMEM, so it needs to call
`Runtime.storage().read()` instead. The code was copied from Macros, but a
couple of spots were missed.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
To make it easier - or in some cases, even possible - to put the keyboard into
programmable mode, we need to be able to initiate a reset from the host side.
For example, ynlike on AVR, where we can send a HUP to the serial port, on GD32,
we need to do it a bit differently: by sending the keyboard a Focus request to
reboot itself.
This implements the command itself.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
In some cases, we still need preprocessor macros to preserve the same keymap
markup as before, because they convert `X` to `Key_X` (for example).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Instead of hardcoding 255 in the `eeprom.erase` handler, use a constant instead,
to make it clear what the number is.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The `eeprom.erase` command makes it easier to erase the whole of EEPROM, and in
addition, it will reboot the keyboard so that the changes are picked up by every
single plugin.
Fixes#1134.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
When handling the update variant of `eeprom.contents`, we need to commit the
update storage, too. This does not affect AVR that doesn't need an explicit
commit, but it does affect ARM, where `eeprom.contents` likely didn't work at
all in update mode.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
LED-ActiveModColor needs some LED mode active for the LEDs to return to their
normal state upon release. This adds the simple `LEDOff` mode to accomplish
that, and also adds OneShot keys as a better demonstration of the capabilities
of the plugin.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Move MouseWrapper class into the `plugin::mousekeys` namespace
This helper class is meant to be internal to MouseKeys, so it would be best to
sequester it from the shared `kaleidoscope::plugin` namespace.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Remove unnecessary explicit MouseWrapper constructor
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Remove trailing underscore from MouseWrapper class name
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Rename Mousewrapper methods to comply with coding style guide
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Make MouseWrapper warping utility methods private
MouseKeys doesn't call these functions directly, and making them private helps
make the API for MouseWrapper clearer.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Make MouseWrapper public variables comply with code style guide
Variables should be in `snake_case`. Since MouseKeys is the only client of
`MouseWrapper`, I don't feel it's necessary to go through a deprecation process
for the sake of this rename.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Remove unnecessary explicit MouseKeys constructor
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Remove trailing underscore from MouseKeys class name
Because the `MouseKeys_` class is now contained in the `kaleidoscope::plugin`
namespace, but the `MouseKeys` object is in the global namespace, we can safely
drop the trailing underscore from the class name, like most other plugins.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Add some namespace comments
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Make `MouseWrapper::subpixels_per_pixel` constexpr
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Change MouseKeys variables from `static` to member variables
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Change name of MouseWrapper object
Instead of `kaleidoscope::plugin::MouseWrapper`, it is now named like a
variable: `kaleidoscope::plugin::mousekeys::wrapper`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Downcase directory name to match namespace
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* Adjust header includes to match new dirname
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
By specifying the type of the `EventHandlerResult` enum as `uint8_t`, it only
takes up one byte instead of two, saving a significant amout of PROGMEM.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
* docker-bash now mounts Kaleidoscope live and interactive
* Switch to a less rigorous but still apropriate mounting method for
volumes in docker when running 'make docker-bash'
* Switch docker to no longer build a bundle separate from the kaleidoscope
dir.
Stop copying over a bunch of tarballs we don't need.
* fix shellcheck
* Stop copying the core-bundled copies of Kaleidoscope over to docker
* Symlink our Kaleidoscope into the versions packaged into the arduino
cores when running in docker. We need to do this to satisfy the
sketch_header.h/sketch_footer.h system we use to be able to amend
arduino sketches on avr and virtual. (This feature is not used by the
core and may be removed in the future)
* Reimplement docker-clean to do much less work and get the same result
* Add a stub at docs for the docker test runner
With the reorganization of arduino-cli libraries in .arduino, we no longer use a
symlink for Kaleidoscope, so don't try to delete it or create it inside the
Docker container.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Now that it's not a `static` class function, we need to use a different
invocation, and we can't declare `isStickableDefault()` with the `always_inline`
attribute, or the user's override won't be able to call it because there will be
nothing to link to.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
These declarations save some PROGMEM (and probably make things run very slightly
faster).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is more like "standard" C++ code, resulting in more readable code, with
default configuration values stored in the header file, and `const`-correct
member functions clearly marking which ones alter internal state and which ones
don't (with the exception of the event handlers).
Things had been declared `static` because the compiler produced a significantly
smaller binary in PROGMEM, but that appears not to be the case now.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
In preparation for validation of report types other than just Keyboard, rename
these keyboard-specific functions (and variables) to have appropriately
keyboard-specific names.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This removes the pre-`KeyEvent` handler hooks for `onKeyswitchEvent()` and
`beforeReportingState()`. Any third-party plugins that still use either should
be updated to use the new handlers instead.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This removes all the core code that called the pre-`KeyEvent` hook functions
`onKeyswitchEvent(key, addr, state)` and `beforeReportingState()`. Any plugins
that were still using these functions should use the newer equivalents instead:
`onKeyEvent(event)` & `beforeReportingState(event)`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This removes the `key_events.*` files that once contained the main
`handleKeyswitchEvent()` function, and all references to it. Because
`key_events.h` was included in the main `Kaleidoscope.h` header file,
`key_defs.h` and `keyswitch_state.h` were added to that header so that other
code that relies on those things being included via `Kaleidoscope.h` will
continue to work.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This function was mainly intended for internal use by Kaleidoscope's event
handling functions. The logic that it used to provide is now handled by
`Runtime.handleKeyEvent()`, and it is not generally necessary or recommended for
plugin or sketch code to directly modify HID reports any longer.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This removes several vestigial functions from the `Layer` class:
- `.handleKeymapKeyswitchEvent()` & `.eventHandler()`, which have been replaced
with the `.handleLayerKeyEvent()` function that operates on `KeyEvent` objects.
- `.lookup()`, which has been replaced by either `Runtime.lookupKey()` or
`Layer.lookupOnActiveLayer()`, depending on the specific purpose.
- `.updateLiveCompositeKeymap()`, which referred to a structure that has been
replaced. `live_keys.activate()` serves a similar purpose, but in most cases
shouldn't be called directly by user code.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
to set up a custom directory containing Arduino libraries to include in
your sketch's library search path.
Signed-off-by: Jesse Vincent <jesse@keyboard.io>
Fixes#1116 - now we don't include other random libraries in
Kaleidoscope's parent dir in our arduino search path
Signed-off-by: Jesse Vincent <jesse@keyboard.io>
Storage must be page aligned and must span full pages. Adjust the default
storage size accordingly, so it is a multiple of 4096.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
In order to allow custom `tapDanceAction()` code distinguish between a "hold"
and a "tap" when a timeout has elapsed, we first check to see if there's a
release event for the TapDance key in the event queue, using the `Timeout`
action if one is found, and the `Hold` action otherwise.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This testcase is for a version of TapDance that distinguishes between a "tap"
timeout and a "hold" timeout, allowing for two different `Key` values for the
same tap count.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The commit method of the GD32Flash storage driver was accidentally left empty,
disabling the functionality. Call the parent's commit method to restore it.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This mini plugin is an example of how to suppress a held `shift` key from a
Macros sequence. In this case, it's to enable a macro that types an accented
character using a dead key (where the dead key must be entered without the
`shift` modifier held).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is closer to a real-world scenario than the QueueLeaker plugin testcase.
It doesn't test the bug as deliberately, but it shows the failure of Qukeys
prior to the fix.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
To prevent the possibility of a call to `flushEvent()` when the queue is empty,
we call `processQueue()` (which checks for an empty queue) after checking the
hold timeout, rather than before.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This change prevents `KeyAddrEventQueue::remove()` from shifting values in
memory out of bounds of its arrays if `shift()` is called on an empty queue. It
also adds a check to be sure that the entry removed is in the queue.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
`KeyAddrEventQueue::remove()` fails to confirm that the current queue is empty
before decrementing the length and shifting entries in the queue arrays. If
`remove()` or `shift()` is called when the queue is empty, `length_` gets
decremented from 0 to 255 (because it's unsigned), and then a large section of
memory gets shifted, mostly out of bounds of the event queue arrays, and
probably wreaking havoc with any number of things.
The plugin in this testcase should trigger this bug, and is detectable because
it affects the value for the current time. It's not guaranteed to detect this
bug, but it seems to be fairly consistent.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This adds a way to add a delay (advancing the virtual clock) in a simulator test
script without running Kaleidoscope cycles.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
By default, `kaleidoscope::device::Base<>` uses a dummy serial implementation,
and devices must override that to use the proper one. For the Model01, on which
the Model100 plugin was based on, this is done by the `ATMega32U4Keyboard`
class. For the Model100, we need to do it ourselves, because we're deriving from
Base directly.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The upgrade notes for Macros wasn't in the best place. This change moves it to
the "Breaking changes" section of the main UPGRADING document, where it belongs.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The documentation on how to upgrade old Macros code was not published on
readthedocs.io, and was therefore insufficiently discoverable. This change
moves everything from the Macros UPGRADING document to its README.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
A number of questions have come up regarding how to adapt old Macros to the new
version, and have not been adequately addressed in the existing docs. Two new
sections cover the most common questions regarding `MACRODOWN()` and how to
handle releasing held keys temporarily while a Macro plays.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
I was unable to compile the default firmware with the version of arduino that was packaged with Debian. On issue #1098, @obra explained that Debian's version of arduino has been heavily modified, and installing the version of arduino that's available on the arduino website fixed my compilation problems. I have therefore added a note to the docs suggesting not to use the Debian version of arduino.
It turns out that being able to toggle the plugin at run-time is unnecessary: if
one wants to disable the functionality, they can just set the cancel key to
something that will never be pressed.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
To make it easier to use a dedicated cancel key, always treat it as a cancel key
if seen, without having to set it via `setCancelKey()` on top. The key has no
use apart from this one task, lets make it easier to use.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The new plugin - EscapeOneShotConfig - allows one to configure the main
EscapeOneShot plugin via Focus. To make this functionality optional, it is a
separate plugin, still contained in the same library for ease of us.
Documentation and example updated accordingly.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
We want to make it possible to have the plugin in firmwares shipped by
Chrysalis, but still have the functionality optional. To achieve this, we need
to be able to toggle it on and off at will.
We move both the existing `cancel_oneshot_key_` property, and the new toggle
into a struct, which we will later make use of in the upcoming configuration
plugin.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Until Chrysalis knows about CharShift keys, they can only be added to a
Chrysalis keymap by using a custom key code. This adds a section to the
CharShift docs giving the offset needed to reference those keys.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
With Dash, and presumably other shells that aren't Bash, calling read
with no arguments produces the error:
/bin/sh: 1: read: arg count
Passing a dummy argument produces the desired behavior.
Signed-off-by: Tim Pope <code@tpope.net>
This lets us remove some awkward code from `handleKeyswitchEvent()`, because as
long as the default value (which triggers a keymap lookup) was the same as
`Key_Masked`, it wasn't sufficient for an `onKeyswitchEvent()` handler to change
`event.key` to `Key_Masked`, because that would be interpreted by
`handleKeyEvent()` as a signal to do a keymap lookup.
This also makes it more consistent with other parts of the code. The values
`Key_Undefined`, `Key_Inactive`, and `Key_Transparent` are all the same, and
with this change they are each interpreted the same way by code that encounters
them. In a keymap lookup, if an active layer has a `Key_Transparent` value, we
continue searching for a different value on another layer. In the live keys
array, if we find a `Key_Inactive` entry, we look for a value from the keymap.
And with this change, when processing a key event, if it has a `Key_Undefined`
value, we look for a value from `active_keys` (and then from the keymap layers,
if nothing is found there).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Apple started calling their operating system macOS, rather than OSX a while ago,
and as such, we should follow suit. This introduces `::hostos::MACOS`, and makes
`::OSX` an alias of it.
All internal users were updated to refer to `::hostos::MACOS`, but the `::OSX`
alias is being kept indefinitely.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Under Windows, we use an input method that requires a registry edit, mention
that in the README, and describe how to do it.
Fixes#1031.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
When there are different input methods available on Linux (Chinese, Japanese,
etc), using the numpad is more reliable than the number row. As both work,
rather than making it configurable, just switch to numpad on Linux.
Fixes#1064.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
To account for non-qwerty host-side layouts, we want to be able to easily set
the last part of the sequence used to start Unicode input on Linux. The newly
introduced `Unicode.setLinuxKey(Key_S)` function does just that.
Fixes#1063.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This is a demonstration that another plugin can use the new public OneShot
methods to turn a non-OneShot key into a OneShot.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
To allow us to use any other HID than KeyboardioHID, the base device _must_ use
something else (practically, the Base HID), otherwise we'll get a compile error
when building on a platform that KeyboardioHID does not support, even if we do
not use KeyboardioHID. We get that error, because the base class references it
anyway.
As such, lets use the base HID as default, and adjust all users of KeyboardioHID
to explicitly set that: Virtual, Dygma Raise, and ATmega32U4Keyboard. Everything
else derives from the last one, so they're covered with just the change to
ATmega32U4Keyboard.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
If `HID_BOOT_PROTOCOL` is undefined, define it ourselves (the value is set by
the USB standard, so we can do that). This allows compiling the base class
without KeyboardioHID, letting our HID base driver be completely independent of
it.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This function is completely unused, and should not have been exposed to begin
with. Neither in the base class, nor in specific implementations.
The `getReport()` method ties us to a specific report datatype, both in name,
and in shape. That's not something we want, we'd rather have the base class be
HID-library agnostic, which it can't be with `getReport()` present.
Luckily, the function is completely unused, and as such, is safe to remove
without deprecation.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
Instead of doing the stop in the base class, delegate it to the specific
implementation. Do this to avoid depending on the exact shape and layout of the
mouse report within the base class.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
We do not need to redefine `new` on STM32, as it is included in the standard
library, and defining it ourselves would lead to linking errors.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This is a guide to writing a Kaleidoscope plugin, more or less written as a
tutorial. It is (obviously) incomplete, but even in its partially-complete
state, it's still probably useful.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Other parts of Kaleidoscope require the NKRO and Boot keyboard classes to have
an `isKeyPressed()` method, which our base classes did not provide - until now.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The `layer_state_` bitfield is no longer necessary now that we have
activation-order layers. Removing it reduces clutter and saves a modicum of
PROGMEM & RAM.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
OneShot keys should apply to all the key events generated by a Macros key, not
just the first one, even if the Macros key is injected by TapDance.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
If multiple events are processed in a single cycle, we want a OneShot key whose
release is triggered by the first one to only affect that key, and not
subsequent ones. For example, if we tap `OSM(LeftShift)`, then `TD(0)`, then
`Key_X`, the OneShot shift should only apply to the output of the TapDance key,
not the `x`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This event handler is useful for plugins that need to react to events, but
should wait until after those events are fully processed before doing so. This
is useful for OneShot, which needs to keep keys active until after events that
trigger their release. The `afterEachCycle()` hook is unfortunately
insufficient for this purpose, because the same event could trigger multiple
plugins (e.g. TapDance & OneShot) to resolve events, and the OneShot should
apply only to the first ensuing report.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This fixes a problem with a plugin that returns `ABORT` from its
`onKeyswitchEvent()` handler, for a masked key addr. I'm convinced that a
better solution is to switch from using `Key_NoKey` (a.k.a. `Key_Masked`) to
using `Key_Transparent` as the default for new events, and thus, the value that
signals that a lookup should be done, but this at least fixes the bug for now,
with a much less intrusive change.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
SpaceCadet now has three "modes": on, off, and on with no delay. In "no-delay"
mode, when a SpaceCadet key is pressed, its primary (modifier) key value is sent
immediately to the host, and if it is released before timing out, that value is
then replaced by the configured "tap" value of the key.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Switch from `bool` to `enum` in preparation for a third mode of SpaceCadet
functionality, where the modifier becomes active immediately when the key is
pressed, rather than waiting for the key to resolve into the "hold" or "tap"
state.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Now that the timeout checker has been fixed, we need to remove the extra 1
millisecond from testcases that verify timeouts.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
`Runtime.hasTimeExpired()` had a minor flaw. Because it was comparing two values
using `>` instead of `>=`, it meant that a timeout set at 20ms wouldn't actually
time out until 21ms elapsed.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
If `ActiveModColorEffect` was registered ahead of `OneShot` in
`KALEIDOSCOPE_INIT_PLUGINS()`, `OSM()` and `OSL()` keys would light up in the
OneShot "sticky" state, not in the "held" or "one-shot" states. This happened
because OneShot changes the `event.key` value to the corresponding base
key (modifier or layer shift), but if ActiveModColor had already processed that
key event, it wouldn't recognize the key as a modifier/layer shift key, and
would therefore ignore it.
This change makes ActiveModColor also recognize OneShot keys as modifier/layer
shift keys.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Its utility is very limited now that `macroAction()` only gets called when a
Macros key toggles on or off, and it uses a symbol that breaks an abstraction
barrier (a local variable of the `macroAction()` function).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This makes it unnecessary to include `Arduino.h` (or `stdint.h`, or some other
header that includes it) before including Kaleidoscope-Ranges.h.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This example sketch is now a fairly good demonstration of the power and
simplicity of the new KeyEvent handlers, and an example of a custom plugin
written directly in the sketch file.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This allows plugins to override the current LED mode just before the LED sync is
done (i.e. after the mode sets the LED colors, but before those changes are
pushed to the hardware.)
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This class should help plugins that implement `onKeyswitchEvent()` to ensure
that they won't process the same event more than once.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
There's no need to trigger a keyboard HID report after processing a layer
change, so stop processing before calling `prepareKeyboardReport()` if
`event.key` is a layer change `Key`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The new version of the layer change `Key` handler is more consistent with the
other `KeyEvent` handling functions, and properly checks for a second layer
shift key being held when releasing the first one.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This defines four new event handlers for plugins to use with the forthcoming
redesigned main event loop:
- `onKeyEvent(KeyEvent &event)`
- `onPhysicalKeyEvent(KeyEvent &event)`
- `beforeReportingState(const KeyEvent &event)`
- `onAddToReport(Key key)`
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This allows it to return correct `KeyEvent` values when used by plugins that
need to track that information for delaying events.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The `KeyEvent` type will encapsulate all of the data that will be passed to the
new generation of event handler functions.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
With a non-zero default for tap-repeat, the timing of events changed, causing
this testcase to fail.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is not complete, but it does test the two basic cases of a double-tap and a
tap-then-hold (to produce a single primary key value hold in output) on all
three types of qukeys (Generic, DualUse, SpaceCadet).
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This change gives Qukeys the ability to repeat a primary keycode by
tapping the key, then immediately pressing and holding it. While doing
this, the extra release and press of the key are suppressed, so it
looks to the host just like a simple press-and-hold event, which is
particularly nice for users of macOS apps that use Cocoa, where
holding letter keys is the "standard" way of accessing accented
characters.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The new items have been added to the end of the list (before `SAFE_START`),
where they belong.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
It was failing to exclude `MoveToLayer()` keys, so it would return `true`
incorrectly for them.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This key makes any held key (or otherwise active key, most likely OneShot keys)
sticky when it toggles on.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a special OneShot key that makes any subsequently-pressed key sticky,
regardless of its value.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This adds a new feature to OneShot: it can now (optionally) treat
modifiers and layer-shift keys as automatic OneShot keys, with
functions to enable and disable this feature for modifiers and
layer-shifts independently.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Deprecates OneShot direct-access configuration variables, and replaces them with
setter functions:
- `time_out` => `setTimeout()`
- `hold_time_out` => `setHoldTimeout()`
- `double_tap_time_out` => `setDoubleTapTimeout()`
Deprecating public member variables is tricky, but possible. I've created new,
private member variables, and added code to keep them in sync with the
deprecated public ones for now.
Also of note: The old `OneShot.inject()` function should now be unnecessary for
most purposes. It still works, but has a potential undesirable side effect. It
now needs to pick a physical keyswitch address to use for the injected OneShot
key, and that key will not be usable for its normal value until that OneShot key
is deactivated. Because of this, use of `inject()` is not strongly discouraged.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a complete rewrite of OneShot, based on the keymap cache
redesign. This allows OneShot to abort the release of a key, causing
its cache entry to stay valid if it's in an active state after the key
is released, allowing us to fix#896 (double-tapping a layer shift key
doesn't make it sticky).
Instead of tracking `Key` values, OneShot now uses two bitfields of
the keyboard in order to track the OneShot state of every valid
`KeyAddr` independently. This could enable the creation of a OneShot
"meta" key, which could be used as a way to make any key on the
keyboard exhibit OneShot behaviour.
The new OneShot plugin immediately replaces the OneShot `Key` value
with its corresponding "normal" key, and activates its OneShot status
by setting one bit in one of the bitfields.
Also included:
* A rewrite of LED-ActiveModColor that makes it compatible
with the new OneShot, and add support for Qukeys
* Updates to Escape-OneShot for compatibility and efficiency
* Minor updates to Qukeys
* The new KeyAddrBitfield class
KeyAddrBitfield:
This class can be used to represent a binary state of the physical key
addresses on the keyboard. For example, ActiveModColor can use to to
mark all the keys which should be highlighted at any given time. It
includes a very efficient iterator, which returns only `KeyAddr`
values corresponding to bits that are set in the bitfield. It checks a
whole byte at a time before examining individual bits, so if most bits
are unset most of the time, it's very fast, and suitable for use in
hooks that get called every cycle.
ActiveModColor:
This makes LED-ActiveModColor compatible with Qukeys, and removes its
16-modifier limit, while simultaneously reducing it's footprint in RAM
and eliminating a potential buffer overrun bug where it could have
written past the end of its state array.
Fixes#882Fixes#894Fixes#896
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
It's now being used by more than one plugin, and is likely to get used by at
least a third, if not more.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Because the active key for redial was getting cached as the key being pressed,
Redial would only ever see a key toggled on event for `Key_Redial`. It would
then set `redial_held_` to `true`, but it would never get set to `false` on the
key's release.
This change both fixes it and simplifies the plugin as it is adapted to the
keyboard state array by doing away with unnecessary state variables, including
`redial_held_`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is an extensive rewrite, but I think it simplifies the logic and makes the
plugin's code easier to follow.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a major rewrite of TapDance, taking advantage of the keyboard state
array and the `KeyAddrEventQueue` class originally written for Qukeys.
fixes#806fixes#922fixes#908fixes#985
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
When Qukeys stops event processing from its `onKeyswitchEvent()` handler, it's
because the event should be treated as non-existent (in most cases, it's merely
delayed). This keeps the keyboard state array from getting updated, as well as
completely stopping event processing.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This replaces the `Layer.live_composite_keymap_[]` cache with a representation
of the keyboard's current state as an array of `Key` objects, one per key on the
keyboard. In this new array, an idle key will have the value `Key_Transparent`,
and a pressed key will have the value of whatever key it's currently mapped to
in the keymap (or whatever value the active set of plugins has assigned to
it). A value of `Key_NoKey` will mask that key until it is released.
If a plugin returns `ABORT` from its `onKeyswitchEvent()` handler, that means
that the keymap cache should not be updated. It's especially important to have
this occur after plugins like OneShot and Qukeys, where the key can stay
active (or become active) after the physical keyswitch has been released.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This will allow plugin handlers to send one of three different signals to the
calling hook functions, with three different interpretations:
`OK`: Continue calling the next handler.
`EVENT_CONSUMED`: Don't proceed to the next handler, but signal to the hook
function's caller that an event was handled successfully.
`ABORT`: Stop processing, and signal to the hook function's caller that the
event should be ignored.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Instead of only aborting hook functions if a handler returns `EVENT_CONSUMED`,
only continue abortable hooks if a handler returns `OK`. For existing core
plugins, this shouldn't make any difference because none of them use the `ERROR`
return value.
Also rename `shouldAbortOnConsumedEvent` to better match the new conditional.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This testcase checks to make sure that the keyboard state array gets cleared by
`handleKeyswitchEvent()` when a plugin returns `EVENT_CONSUMED`.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This adds a testcase for rollover from a TapDance key to the key that interrupts
the sequence, and a testcase for a TapDance key that times out while held.
I also adjusted the timing of the existing testcases to match the new version of
TapDance.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This testcase checks for any changes to existing values in Kaleidoscope-Ranges,
with the goal of preventing backwards-incompatible changes that will affect
existing EEPROM keymaps configured via Chrysalis.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This makes it possible for a testcase to include source files from plugin
directories. When the plugins got pulled out of the main Kaleidoscope source
dir, plugin header files became unavailable to testcases, but these files are
useful in some instances.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This replaces the magic number `24576` with the old definition of the start of
the Macros plugin's `Key` range, to make it clear that it will match bitwise
tests for "not a plugin key" (the two high bits are both zeros) and the
`SYNTHETIC` flags bit.
I also elaborated on the reasons for keeping the existing `Key` values stable
and added a comment addressing the more likely case where new values are being
added, rather than fighting the last war and focusing only on the one legacy
plugin range that exists outside the canonical plugin range, whose values have
already been incorporated here.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Some headers in the file Model01Side.h were included inside the namespace
`kaleidoscope::driver::keyboardio` instead of being left in the global
namespace, where they should have been.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This implements a new `FocusSerial` command: `plugins`. The `plugins` command
will reply back with a list of plugins enabled in the firmware. The list is not
exhaustive, only plugins that opt-in to this mechanism will be listed. It is
opt-in, because for a lot of plugins, having them listed isn't useful in a
practical sense.
The goal with this feature is to allow Chrysalis to detect plugins that would
affect what keys it offers, or which additional settings it displays, and do so
in a consistent way. This is why IdleLEDs has an `onNameQuery` handler too, even
though it can be detected otherwise: for consistency.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The option `--build-properties` was deprecated in v0.14.0 of arduino-cli. This
change uses the new option `--build-property` instead if the version of
arduino-cli being called is newer than that, thus avoiding deprecation warning
messages and innoculating the build system against the removal of the deprecated
version of the option.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Instead of safeguarding against an unrecoverable blank layer state by
guaranteeing that layer 0 is always active (and therefore always at the bottom
of the layer stack), we safeguard by a move to layer 0 if the only active layer
is deactivated.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Macro keycodes pre-date the Ranges plugin, so they previously had a keycode that
sorted before ranges::FIRST. For the sake of backwards compatibility, we want to
keep using the same keycodes.
Fixes#1010.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
The `INSTANTIATE_WEAK_HOOK_FUNCTION` claimed that it's there for v1 API
compatibility alone - it is not. It allows us to have sketches that use no
plugins too, without them having to use `KALEIDOSCOPE_INIT_PLUGINS()` with a
dummy plugin.
We'd need a dummy plugin because `KALEIDOSCOPE_INIT_PLUGINS()` does not support
being invoked with an empty plugin list, due to technical reasons. From an
end-user point of view, not using the macro is much preferable to using it with
a dummy plugin. We can't automatically inject a dummy plugin either, again, due
to technical reasons.
Fixes#1005.
Signed-off-by: Jesse Vincent <jesse@keyboard.io>
$(info You're using an older version of GNU Make that doesn't offer the --output-sync option. If you're running the test suite in parallel, output may be garbled. You might consider using GNU Make 4.0 or later instead)
@ -5,7 +5,7 @@ Flexible firmware for Arduino-powered keyboards.
This package contains the "core" of Kaleidoscope and a number of [example firmware "Sketches"](https://github.com/keyboardio/Kaleidoscope/tree/master/examples).
If you're just getting started with the Keyboardio Model 01, the [introductory docs are here](https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction) and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind.
If you're just getting started with the Keyboardio Model 01, the [introductory docs are here](https://github.com/keyboardio/Kaleidoscope/wiki/Keyboardio-Model-01-Introduction) and the source for the basic firmware package is here: https://github.com/keyboardio/Model01-Firmware. It's probably a good idea to start there, learn how to modify your keymap and maybe turn some modules on or off, and then come back to the full repository when you have more complex changes in mind. (The firmware for all other devices is inside examples/Devices in this Kaleidoscope repo.)
echo "Running tests in Docker requires that your Arduino environment be installed in .arduino inside the top-level Kaleidoscope directory. To set this up, run 'make setup'"
exit 1
fi
XFER_DIR="$(pwd)/.docker_xfer"
mkdir -p "${XFER_DIR}"
if [ -z "$_NO_SYNC_KALEIDOSCOPE" ]; then
echo "Preparing Kaleidoscope..."
echo "The bundle is coming from ${ARDUINO_DIRECTORIES_USER}/hardware/keyboardio"
### Better protection against unintended modifiers from Qukeys
Qukeys has two new configuration options for preventing unintended modifiers in
@ -86,23 +188,23 @@ See the [Kaleidoscope-USB-Quirks][plugin:USB-Quirks] plugin for a use-case.
### Finer stickability controls for OneShot
The [OneShot plugin](plugins/OneShot.md) gained finer stickability controls, one can now control whether the double-tap stickiness is enabled on a per-key basis. See [UPGRADING.md](UPGRADING.md#finer-oneshot-stickability-control) for more information.
The [OneShot plugin](plugins/Kaleidoscope-OneShot.md) gained finer stickability controls, one can now control whether the double-tap stickiness is enabled on a per-key basis. See [UPGRADING.md](UPGRADING.md#finer-oneshot-stickability-control) for more information.
### A way to slow down Unicode input
In certain cases we need to delay the unicode input sequence, otherwise the host is unable to process the input properly. For this reason, the [Unicode](plugins/Unicode.md) gained an `.input_delay()` method that lets us do just that. It still defaults to no delay.
In certain cases we need to delay the unicode input sequence, otherwise the host is unable to process the input properly. For this reason, the [Unicode](plugins/Kaleidoscope-Unicode.md) gained an `.input_delay()` method that lets us do just that. It still defaults to no delay.
### Better support for modifiers in the Cycle plugin
The [Cycle](plugins/Cycle.md) plugin has much better support for cycling through keys with modifiers applied to them, such as `LSHIFT(Key_A)`. Please see the documentation and the updated example for more information.
The [Cycle](plugins/Kaleidoscope-Cycle.md) plugin has much better support for cycling through keys with modifiers applied to them, such as `LSHIFT(Key_A)`. Please see the documentation and the updated example for more information.
### More control over when to send reports during Macro playback
There are situations where one would like to disable sending a report after each and every step of a macro, and rather have direct control over when reports are sent. The new `WITH_EXPLICIT_REPORT`, `WITH_IMPLICIT_REPORT` and `SEND_REPORT` steps help with that. Please see the [Macros](plugins/Macros.md) documentation for more information.
There are situations where one would like to disable sending a report after each and every step of a macro, and rather have direct control over when reports are sent. The new `WITH_EXPLICIT_REPORT`, `WITH_IMPLICIT_REPORT` and `SEND_REPORT` steps help with that. Please see the [Macros](plugins/Kaleidoscope-Macros.md) documentation for more information.
### LED-ActiveModColor can be asked to not highlight normal modifiers
The plugin was intended to work with OneShot primarily, and that's where it is most useful. To make it less surprising, and more suitable to include it in default-like firmware, we made it possible to ask it not to highlight normal modifiers. Please see the [LED-ActiveModColor](plugins/LED-ActiveModColor.md) documentation for more information.
The plugin was intended to work with OneShot primarily, and that's where it is most useful. To make it less surprising, and more suitable to include it in default-like firmware, we made it possible to ask it not to highlight normal modifiers. Please see the [LED-ActiveModColor](plugins/Kaleidoscope-LED-ActiveModColor.md) documentation for more information.
### Events now trigger on layer changes
@ -132,39 +234,47 @@ Kaleidoscope has been ported to the following devices:
For more information, please see the hardware plugins' documentation.
To make it easier to port Kaleidoscope, we introduced the [ATMegaKeyboard](plugins/ATMegaKeyboard.md) base class. For any board that's based on the ATMega MCU and a simple matrix, this might be a good foundation to develop the hardware plugin upon.
To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base class. For any board that's based on the ATMega MCU and a simple matrix, this might be a good foundation to develop the hardware plugin upon.
## New plugins
### CharShift
The [CharShift](plugins/Kaleidoscope-CharShift.md) plugin allows independent assignment of symbols to keys depending on whether or not a `shift` key is held.
### AutoShift
The [AutoShift](plugins/Kaleidoscope-AutoShift.md) plugin provides an alternative way to get shifted symbols, by long-pressing keys instead of using a separate `shift` key.
### DynamicMacros
The [DynamicMacros](plugins/DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis.
The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis.
### IdleLEDs
The [IdleLEDs](plugins/IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event.
The [IdleLEDs](plugins/Kaleidoscope-IdleLEDs.md) plugin is a simple, yet, useful one: it will turn the keyboard LEDs off after a period of inactivity, and back on upon the next key event.
### LEDActiveLayerColor
The [LEDActiveLayerColor][plugins/LEDActiveLayerColor.md] plugin makes it possible to set the color of all LEDs to the same color, depending on which layer is active topmost.
The [LEDActiveLayerColor][plugins/Kaleidoscope-LEDActiveLayerColor.md] plugin makes it possible to set the color of all LEDs to the same color, depending on which layer is active topmost.
### LED-Wavepool
We integrated the [LEDWavepool](plugins/LED-Wavepool.md) plugin by [ToyKeeper][wavepool:origin], with a few updates and new features added.
We integrated the [LEDWavepool](plugins/Kaleidoscope-LED-Wavepool.md) plugin by [ToyKeeper][wavepool:origin], with a few updates and new features added.
The [Turbo](plugins/Turbo.md) plugin provides a way to send keystrokes in very quick succession while holding down a key.
The [Turbo](plugins/Kaleidoscope-Turbo.md) plugin provides a way to send keystrokes in very quick succession while holding down a key.
### WinKeyToggle
The [WinKeyToggle](plugins/WinKeyToggle.md) plugin assists with toggling the Windows key on and off - a little something for those of us who game under Windows and are tired of accidentally popping up the start menu.
The [WinKeyToggle](plugins/Kaleidoscope-WinKeyToggle.md) plugin assists with toggling the Windows key on and off - a little something for those of us who game under Windows and are tired of accidentally popping up the start menu.
### FirmwareDump
The [FirmwareDump](plugins/FirmwareDump.md) plugin makes it possible to dump one's firmware over Focus.
The [FirmwareDump](plugins/Kaleidoscope-FirmwareDump.md) plugin makes it possible to dump one's firmware over Focus.
## Breaking changes
@ -197,29 +307,29 @@ The `NumPad` plugin used to toggle `NumLock` when switching to the NumPad layer.
### The `RxCy` macros and peeking into the keyswitch state
The `RxCy` macros changed from being indexes into a per-hand bitmap to being an index across the whole keyboard. This mostly affected the [MagicCombo](plugins/MagicCombo.md) plugin.
The `RxCy` macros changed from being indexes into a per-hand bitmap to being an index across the whole keyboard. This mostly affected the [MagicCombo](plugins/Kaleidoscope-MagicCombo.md) plugin.
Please see the [relevant upgrade notes](UPGRADING.md#the-rxcy-macros-and-peeking-into-the-keyswitch-state) for more information.
### The `Redial` plugin had a breaking API change
The [Redial](plugins/Redial.md) plugin was simplified, one no longer needs to define `Key_Redial` on their own, the plugin defines it itself. See the [upgrade notes](UPGRADING.md#Redial) for more information about how to upgrade.
The [Redial](plugins/Kaleidoscope-Redial.md) plugin was simplified, one no longer needs to define `Key_Redial` on their own, the plugin defines it itself. See the [upgrade notes](UPGRADING.md#Redial) for more information about how to upgrade.
### Color palette storage has changed
The [LED-Palette-Theme](plugins/LED-Palette-Theme.md) had to be changed to store the palette colors in reverse. This change had to be made in order to not default to a bright white palette, that would draw so much power that most operating systems would disconnect the keyboard due to excessive power usage. With inverting the colors, we now default to a black palette instead. This sadly breaks existing palettes, and you will have to re-set the colors.
The [LED-Palette-Theme](plugins/Kaleidoscope-LED-Palette-Theme.md) had to be changed to store the palette colors in reverse. This change had to be made in order to not default to a bright white palette, that would draw so much power that most operating systems would disconnect the keyboard due to excessive power usage. With inverting the colors, we now default to a black palette instead. This sadly breaks existing palettes, and you will have to re-set the colors.
We also changed when we reserve space for the palette in EEPROM: we used to do it as soon as possible, but that made it impossible to go from a firmware that does not use the plugin to one that does, and still have a compatible EEPROM layout. We now reserve space as late as possible. This breaks existing EEPROM layouts however.
### EEPROM-Keymap changed Focus commands
The [EEPROMKeymap](plugins/EEPROM-Keymap.md) plugin was changed to treat built-in (default) and EEPROM-stored (custom) layers separately, because that's less surprising, and easier to work with from Chrysalis. The old `keymap.map` and `keymap.roLayers` commands are gone, the new `keymap.default` and `keymap.custom` commands should be used instead.
The [EEPROMKeymap](plugins/Kaleidoscope-EEPROM-Keymap.md) plugin was changed to treat built-in (default) and EEPROM-stored (custom) layers separately, because that's less surprising, and easier to work with from Chrysalis. The old `keymap.map` and `keymap.roLayers` commands are gone, the new `keymap.default` and `keymap.custom` commands should be used instead.
### EEPROMSettings' version() setter has been deprecated
We're repurposing the `version` setting: instead of it being something end-users
can set, we'll be using it internally to track changes made to
[EEPROMSettings](plugins/EEPROM-Settings.md) itself, with the goal of
[EEPROMSettings](plugins/Kaleidoscope-EEPROM-Settings.md) itself, with the goal of
allowing external tools to aid in migrations. The setting wasn't widely used -
if at all -, which is why we chose to repurpose it instead of adding a new
- [git checkouts aren't compatible with Arduino IDE (GUI)]([#repository-rearchitecture)
- [Layer system switched to activation-order](#layer-system-switched-to-activation-order)
- [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state)
- [HostOS](#hostos)
- [MagicCombo](#magiccombo)
- [OneShot](#oneshot)
- [Qukeys](#qukeys)
- [TypingBreaks](#typingbreaks)
- [Redial](#redial)
@ -34,6 +41,107 @@ any API we've included in a release. Typically, this means that any code that us
## New features
### New event handler
One more `KeyEvent` handler has been added: `afterReportingState(const KeyEvent &event)`. This handler gets called after HID reports are sent for an event, providing a point for plugins to act after an event has been fully processed by `Runtime.handleKeyEvent()`.
### Event-driven main loop
Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events.
Furthermore, there are now two functions for initiating the processing of key events:
- `Runtime.handleKeyswitchEvent()` is the starting point for events that represent physical keyswitches toggling on or off.
- `Runtime.handleKeyEvent()` is the starting point for "artificial" key events. It is also called at the end of `handleKeyswitchEvent()`.
In general, if a plugin needs to generate a key event, it should call `handleKeyEvent()`, not `handleKeyswitchEvent()`.
Each of the above functions calls its own set of plugin event handlers. When those event handlers are all done, event processing continues as `handleKeyEvent()` prepares a new keyboard HID report, then sends it:
- `Runtime.prepareKeyboardReport()` first clears the HID report, then populates it based on the contents of the `live_keys[]` array. Note that the HID report is not cleared until _after_ the new plugin event handlers have been called.
- `Runtime.sendKeyboardReport()` handles generating extra HID reports required for keys with keyboard modifier flags to avoid certain bugs, then calls a new plugin event handler before finally sending the new HID report.
These functions should rarely, if ever, need to be called by plugins.
#### The `KeyEvent` data type
There is a new `KeyEvent` type that encapsulates all the data relevant to a new key event, and it is used as the parameter for the new event-handling functions.
- `event.addr` contains the `KeyAddr` associated with the event.
- `event.state` contains the state bitfield (`uint8_t`), which can be tested with `keyToggledOn()`/`keyToggledOff()`.
- `event.key` contains a `Key` value, usually looked up from the keymap.
- `event.id` contains a pseudo-unique ID number of type `KeyEventId` (an 8-bit integer), used by certain plugins (see `onKeyswitchEvent()` below).
Existing sketches should be mostly backwards-compatible, but some updates will be needed for sketches that use custom code. In particular, users of the Macros plugin are likely to need to make adjustments to the code in the user-defined `macroAction()` function, including that function's signature, the new version of which takes a `KeyEvent` parameter instead of just an event state value. In most cases, this will make the resulting code more straightforward without any loss of functionality.
In addition to Macros, these changes might also affect user-defined code executed by the TapDance, Leader, and Syster plugins. Please see the documentation and examples for the affected plugins for details.
### Keyboard State array
The keymap cache (`Layer_::live_composite_keymap_[]`) has been replaced by a keyboard state array (`kaleidoscope::live_keys[]`). The top-level functions that handle keyswitch events have been updated to treat this new array as a representation of the current state of the keyboard, with corresponding `Key` values for any keys that are active (physically held or activated by a plugin).
#### For end-users
There should be no user-visible changes for anyone who simply uses core plugins. A few functions have been deprecated (`Layer.eventHandler()` &`Layer.updateLiveCompositeKeymap()`), but there are straightforward replacements for both.
#### For developers
The major changes are to the `handleKeyswitchEvent()` function, which has been reorganized in order to update the new keyboard state array with correct values at the appropriate times. In addition to that, two new facilities are available:
##### `EventHandlerResult::ABORT`
This is a new return value available to plugin event handlers, which is similar to `EVENT_CONSUMED` in that it causes the calling hook function to return early (stopping any subsequent handlers from seeing the event), but is treated differently by `handleKeyswitchEvent()`. If a handler returns `EVENT_CONSUMED`, the keyboard state array will still be updated by `handleKeyswitchEvent()`, but if it returns `ABORT`, it will not. In both cases, no further event processing will be done by the built-in event handler.
##### `live_keys[key_addr]`
This is the new facility for checking the value of an entry in the keyboard state array. It is indexed directly by `KeyAddr` values, without the need to convert them to integers first. For example, it could be used in a range-based `for` loop to check for values of interest:
```c++
for (KeyAddr key_addr : KeyAddr::all()) {
Key key = live_keys[key_addr];
if (key == Key_LeftShift || key == Key_RightShift) {
// do something special...
}
}
```
Additionally, if the `KeyAddr` values are not needed, one can use the iterator from the new `KeyMap` class like so:
```c++
for (Key key : live_keys.all()) {
if (key == Key_X) {
// do something special...
}
}
```
The `live_keys` object's subscript operator can also be used to set values in the keyboard state array:
```c++
live_keys[key_addr] = Key_X;
```
It also comes with several convenience functions which can be used to make the intention of the code clear:
```c++
// Set a value in the keyboard state array to a specified Key value:
live_keys.activate(key_addr, Key_X);
// Set a value to Key_Inactive, deactivating the key:
live_keys.clear(key_addr);
// Set all values in the array to Key_Inactive:
live_keys.clear();)
// Set a value to Key_Masked, masking the key until its next release event:
live_keys.mask(key_addr);
```
In most cases, it won't be necessary for plugins or user sketches to call any of these functions directly, as the built-in event handler functions will manage the keyboard state array automatically.
### New build system
In this release, we replace kaleidoscope-builder with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation.
@ -48,7 +156,7 @@ For end users, this doesn't come with any breaking changes. A few things have be
#### For developers
For those wishing to port Kaleidoscope to devices it doesn't support yet, the new API should make most things considerably easier. Please see the (work in progress) documentation in [doc/device-apis.md](doc/device-apis.md).
For those wishing to port Kaleidoscope to devices it doesn't support yet, the new API should make most things considerably easier. Please see the documentation in [device-apis.md](api-reference/device-apis.md).
The old symbols and APIs are no longer available.
@ -101,6 +209,7 @@ In practice, this boils down to implementing one or more of the following hook p
- `beforeEachCycle()`: Called once, at the beginning of each cycle of the main loop. This is similar to the old "loop hook" with its `post_clear` argument set to false. Takes no arguments, must return `kaleidoscope::EventHandlerResult::OK`.
- `onKeyswitchEvent`: Called for every non-idle key event. This replaces the old "event handler hook". It takes a key reference, a key address, and a key state. The key reference can be updated to change the key being processed, so that any plugin that processes it further, will see the updated key. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing.
- `onFocusEvent`: Used to implement [bi-directional communication](#bidirectional-communication-for-plugins). This is called whenever the firmware receives a command from the host. The only argument is the command name. Can return `kaleidoscope::EventHandlerResult::OK` to let other plugins process the event further, or `kaleidoscope::EventHandlerResult::EVENT_CONSUMED` to stop processing.
- `onNameQuery`: Used by the [Focus](#bidirecional-communication-for-plugins) plugin, when replying to a `plugins` command. Should either send the plugin name, or not be implemented at all, if the host knowing about the plugin isn't important.
- `beforeReportingState`: Called without arguments, just before sending the keyboard and mouse reports to the host. Must return `kaleidoscope::EventHandlerResult::OK`.
- `afterEachCycle`: Called without arguments at the very end of each cycle. This is the replacement for the "loop hook" with its `post_clear` argument set.
@ -160,8 +269,17 @@ class FocusExampleCommand : public Plugin {
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();
}
}
```
### Removed `kaleidoscope-builder`
`kaleidoscope-builder` has been removed.
We replaced it with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation.
### OneShot meta keys
The special OneShot keys `OneShot_MetaStickyKey`&`OneShot_ActiveStickyKey` are no longer handled by the OneShot plugin directly, but instead by a separate OneShotMetaKeys plugin. If you use these keys in your sketch, you will need to add the new plugin, and register it after OneShot in `KALEIDOSCOPE_INIT_PLUGINS()` for those keys to work properly.
### Repository rearchitecture
To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory.
To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory.
The "breaking change" part of this is that git checkouts of Kaleidoscope are no longer directly compatible with the Arduino IDE, since plugins aren't in a directory the IDE looks in. They are, of course, visible to tools using our commandline build infrastructure / Makefiles.
@ -338,7 +818,7 @@ The layer system used to be index-ordered, meaning that we'd look keys up on
layers based on the _index_ of active layers. Kaleidoscope now uses activation
order, which looks up keys based on the order of layer activation.
This means that the following functions are deprecated, and will be removed by **2020-12-31**:
The following functions have been removed as of **2021-01-01**:
- `Layer.top()`, which used to return the topmost layer index. Use
`Layer.mostRecent()` instead, which returns the most recently activated layer.
@ -464,7 +944,7 @@ The new API is much shorter, and is inspired by the way the [Leader][leader]
plugin works: instead of having a list, and a dispatching function like
`magicComboActions`, we include the action method in the list too!
[leader]: plugins/Leader.md
[leader]: plugins/Kaleidoscope-Leader.md
We also don't make a difference between left- and right-hand anymore, you can
just list keys for either in the same list. This will be very handy for
@ -512,6 +992,12 @@ If your actions made use of the `left_hand` or `right_hand` arguments of
more involved to get to, out of scope for this simple migration guide. Please
open an issue, or ask for help on the forums, and we'll help you.
### OneShot
Older versions of the plugin were based on `Key` values; OneShot is now based on
`KeyAddr` coordinates instead, in order to improve reliability and
functionality.
### Qukeys
Older versions of the plugin used `row` and `col` indexing for defining `Qukey`
@ -532,7 +1018,7 @@ which accepts a value between 0 and 100 (interpreted as a percentage). User who
used higher values for `setReleaseDelay()` will want a lower values for
`setOverlapThreshold()`.
These functions have been deprecated since 2019-08-22, and will be removed by**2020-12-31**:
These functions have been removed as of**2020-12-31**:
- `Qukeys.setTimeout(millis)`
- `Qukeys.setReleaseDelay(millis)`
@ -548,16 +1034,22 @@ Storing the settable settings in EEPROM makes it depend on `Kaleidoscope-EEPROM-
Older versions of the plugin required one to set up `Key_Redial` manually, and let the plugin know about it via `Redial.key`. This is no longer required, as the plugin sets up the redial key itself. As such, `Redial.key` was removed, and `Key_Redial` is defined by the plugin itself. To upgrade, simply remove your definition of `Key_Redial` and the `Redial.key` assignment from your sketch.
### Key masking has been deprecated
### Key masking has been removed
Key masking was a band-aid introduced to avoid accidentally sending unintended keys when key mapping changes between a key being pressed and released. Since the introduction of keymap caching, this is no longer necessary, as long as we can keep the mapping consistent. Users of key masking are encouraged to find ways to use the caching mechanism instead.
As an example, if you had a key event handler that in some cases masked a key, it should now map it to `Key_NoKey` instead, until released.
The masking API has been deprecated, and is scheduled to be removed after **2020-11-25**.
The masking API has been removed on **2021-01-01**
## Deprecated APIs and their replacements
### Leader plugin
The `Leader.inject()` function is deprecated. Please call `Runtime.handleKeyEvent()` directly instead.
Direct access to the `Leader.time_out` configuration variable is deprecated. Please use the `Leader.setTimeout(ms)` function instead.
### Source code and namespace rearrangement
With the move towards a monorepo-based source, some headers have moved to a new location, and plenty of plugins moved to a new namespace (`kaleidoscope::plugin`). This means that the old headers, and some old names are deprecated. The old names no longer work.
@ -565,29 +1057,121 @@ With the move towards a monorepo-based source, some headers have moved to a new
The following headers and names have changed:
- `layers.h`, `key_defs_keymaps.h` and `macro_helpers.h` are obsolete, and should not be included in the first place, as `Kaleidoscope.h` will pull them in. In the rare case that one needs them, prefixing them with `kaleidoscope/` is the way to go. Of the various headers provided under the `kaleidoscope/` space, only `kaleidoscope/macro_helpers.h` should be included directly, and only by hardware plugins that can't pull `Kaleidoscope.h` in due to circular dependencies.
- `LED-Off.h`, provided by [LEDControl](plugins/LEDControl.md) is obsolete, the `LEDOff` LED mode is automatically provided by `Kaleidoscope-LEDControl.h`. The `LED-Off.h` includes can be safely removed.
- `LED-Off.h`, provided by [LEDControl](plugins/Kaleidoscope-LEDControl.md) is obsolete, the `LEDOff` LED mode is automatically provided by `Kaleidoscope-LEDControl.h`. The `LED-Off.h` includes can be safely removed.
- `LEDUtils.h` is automatically pulled in by `Kaleiodscope-LEDControl.h`, too, and there's no need to directly include it anymore.
- Plugins that implement LED modes should subclass `kaleidoscope::plugin::LEDMode` instead of `kaleidoscope::LEDMode`.
- [GhostInTheFirmware](plugins/GhostInTheFirmware.md) had the `kaleidoscope::GhostInTheFirmware::GhostKey` type replaced by `kaleidoscope::plugin::GhostInTheFirmware::GhostKey`.
- [HostOS](plugins/HostOS.md) no longer provides the `Kaleidoscope/HostOS-select.h` header, and there is no backwards compatibility header either.
- [Leader](plugins/Leader.md) had the `kaleidoscope::Leader::dictionary_t` type replaced by `kaleidoscope::plugin::Leader::dictionary_t`.
- [LED-AlphaSquare](plugins/LED-AlphaSquare.md) used to provide extra symbol graphics in the `kaleidoscope::alpha_square::symbols` namespace. This is now replaced by `kaleidoscope::plugin::alpha_square::symbols`.
- [LEDEffect-SolidColor](plugins/LEDEffect-SolidColor.md) replaced the base class - `kaleidoscope::LEDSolidColor` - with `kaleidoscope::plugin::LEDSolidColor`.
- [Qukeys](plugins/Qukeys.md) had the `kaleidoscope::Qukey` type replaced by `kaleidoscope::plugin::Qukey`.
- [ShapeShifter](plugins/ShateShifter.md) had the `kaleidoscope::ShapeShifter::dictionary_t` type replaced by `kaleidoscope::plugin::ShapeShifter::dictionary_t`.
- [SpaceCadet](plugins/SpaceCadet.md) had the `kaleidoscope::SpaceCadet::KeyBinding` type replaced by `kaleidoscope::plugin::SpaceCadet::KeyBinding`.
- [Syster](plugins/Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`.
- [TapDance](plugins/TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`.
- [GhostInTheFirmware](plugins/Kaleidoscope-GhostInTheFirmware.md) had the `kaleidoscope::GhostInTheFirmware::GhostKey` type replaced by `kaleidoscope::plugin::GhostInTheFirmware::GhostKey`.
- [HostOS](plugins/Kaleidoscope-HostOS.md) no longer provides the `Kaleidoscope/HostOS-select.h` header, and there is no backwards compatibility header either.
- [Leader](plugins/Kaleidoscope-Leader.md) had the `kaleidoscope::Leader::dictionary_t` type replaced by `kaleidoscope::plugin::Leader::dictionary_t`.
- [LED-AlphaSquare](plugins/Kaleidoscope-LED-AlphaSquare.md) used to provide extra symbol graphics in the `kaleidoscope::alpha_square::symbols` namespace. This is now replaced by `kaleidoscope::plugin::alpha_square::symbols`.
- [LEDEffect-SolidColor](plugins/Kaleidoscope-LEDEffect-SolidColor.md) replaced the base class - `kaleidoscope::LEDSolidColor` - with `kaleidoscope::plugin::LEDSolidColor`.
- [Qukeys](plugins/Kaleidoscope-Qukeys.md) had the `kaleidoscope::Qukey` type replaced by `kaleidoscope::plugin::Qukey`.
- [ShapeShifter](plugins/Kaleidoscope-ShapeShifter.md) had the `kaleidoscope::ShapeShifter::dictionary_t` type replaced by `kaleidoscope::plugin::ShapeShifter::dictionary_t`.
- [SpaceCadet](plugins/Kaleidoscope-SpaceCadet.md) had the `kaleidoscope::SpaceCadet::KeyBinding` type replaced by `kaleidoscope::plugin::SpaceCadet::KeyBinding`.
- [Syster](plugins/Kaleidoscope-Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`.
- [TapDance](plugins/Kaleidoscope-TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`.
# Removed APIs
### Removed on 2022-03-03
#### Pre-`KeyEvent` event handler hooks
The old event handler `onKeyswitchEvent(Key &key, KeyAddr addr, uint8_t state)` was removed on **2022-03-03**. It has been replaced with the new `onKeyEvent(KeyEvent &event)` handler (and, in some special cases the `onKeyswitchEvent(KeyEvent &event)` handler). Plugins using the deprecated handler will need to be rewritten to use the new one(s).
The old event handler `beforeReportingState()` was removed on **2022-03-03**. It has been replaced with the new `beforeReportingState(KeyEvent &event)` handler. However, the new handler will be called only when a report is being sent (generally in response to a key event), not every cycle, like the old one. It was common practice in the past for plugins to rely on `beforeReportingState()` being called every cycle, so when adapting to the `KeyEvent` API, it's important to check for code that should be moved to `afterEachCycle()` instead.
The old master function for processing key "events" was removed on **2022-03-03**. Functions that were calling this function should be rewritten to call `kaleidoscope::Runtime.handleKeyEvent(KeyEvent event)` instead.
This deprecated function was removed on **2022-03-03**. Its purpose was to handle rollover events for keys that include modifier flags, and that handling is now done elsewhere. Any code that called it should now simply call `Keyboard::pressKey(Key key)` instead, dropping the second argument.
#### Old layer key event handler functions
The deprecated `Layer.handleKeymapKeyswitchEvent()` function was removed on **2022-03-03**. Any code that called it should now call `Layer.handleLayerKeyEvent()` instead, with `event.addr` set to the appropriate `KeyAddr` value if possible, and `KeyAddr::none()` otherwise.
The deprecated `Layer.eventHandler(key, addr, state)` function was removed on **2022-03-03**. Any code that refers to it should now call call `handleLayerKeyEvent(KeyEvent(addr, state, key))` instead.
#### Keymap cache functions
The deprecated `Layer.updateLiveCompositeKeymap()` function was removed on **2022-03-03**. Plugin and user code probably shouldn't have been calling this directly, so there's no direct replacement for it. If a plugin needs to make changes to the `live_keys` structure (equivalent in some circumstances to the old "live composite keymap"), it can call `live_keys.activate(addr, key)`, but there are probably better ways to accomplish this goal (e.g. simply changing the value of `event.key` from an `onKeyEvent(event)` handler).
The deprecated `Layer.lookup(addr)` function was removed on **2022-03-03**. Please use `Runtime.lookupKey(addr)` instead in most circumstances. Alternatively, if you need information about the current state of the keymap regardless of any currently active keys (which may have values that override the keymap), use `Layer.lookupOnActiveLayer(addr)` instead.
Direct access to this configuration variable was removed on **2022-03-03**. Please use `LEDControl.setInterval()` to set the interval between LED updates instead.
#### Obsolete active macros array removed
The deprecated `Macros.active_macro_count` variable was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
The deprecated `Macros.active_macros[]` array was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
The deprecated `Macros.addActiveMacroKey()` function was removed on **2022-03-03**. Any references to it are obsolete, and can simply be removed.
#### Pre-`KeyEvent` Macros API
This is a brief summary of specific elements that were removed. There is a more comprehensive guide to upgrading existing Macros user code in the [Breaking Changes](#breaking-changes) section, under [Macros](#macros).
Support for deprecated form of the `macroAction(uint8_t macro_id, uint8_t key_state)` function was removed on **2022-03-03**. This old form must be replaced with the new `macroAction(uint8_t macro_id, KeyEvent &event)` for macros to continue working.
The `Macros.key_addr` public variable was removed on **2022-03-03**. To get access to the key address of a Macros key event, simply refer to `event.addr` from within the new `macroAction(macro_id, event)` function.
The deprecated `MACRODOWN()` preprocessor macro was removed on **2022-03-03**. Since most macros are meant to be triggered only by keypress events (not key release), and because `macroAction()` does not get called every cycle for held keys, it's better to simply do one test for `keyToggledOn(event.state)` first, then use `MACRO()` instead.
#### ActiveModColor public variables
The following deprecated `ActiveModColorEffect` public variables were removed on **2022-03-03**. Please use the following methods instead:
- For `ActiveModColor.highlight_color`, use `ActiveModColor.setHighlightColor(color)`
- For `ActiveModColor.oneshot_color`, use `ActiveModColor.setOneShotColor(color)`
- For `ActiveModColor.sticky_color`, use `ActiveModColor.setStickyColor(color)`
#### OneShot public variables
The following deprecated `OneShot` public variables were removed on **2022-03-03**. Please use the following methods instead:
- For `OneShot.time_out`, use `OneShot.setTimeout(ms)`
- For `OneShot.hold_time_out`, use `OneShot.setHoldTimeout(ms)`
- For `OneShot.double_tap_time_out`, use `OneShot.setDoubleTapTimeout(ms)`
#### Deprecated OneShot API functions
OneShot was completely rewritten in early 2021, and now is based on `KeyAddr` values (as if it keeps physical keys pressed) rather than `Key` values (with no corresponding physical key location). This allows it to operate on any `Key` value, not just modifiers and layer shifts.
The deprecated `OneShot.inject(key, key_state)` function was removed on **2022-03-03**. Its use was very strongly discouraged, and is now unavailable. See below for alternatives.
The deprecated `OneShot.isActive(key)` function was removed on **2022-03-03**. There is a somewhat equivalent `OneShot.isActive(KeyAddr addr)` function to use when the address of a key that might be currently held active by OneShot is known. Any code that needs information about active keys is better served by not querying OneShot specifically.
The deprecated `OneShot.isSticky(key)` function was removed on **2022-03-03**. There is a somewhat equivalent `OneShot.isStick(KeyAddr addr)` function to use when the address of a key that may be in the one-shot sticky state is known.
The deprecated `OneShot.isPressed()` function was removed on **2022-03-03**. It was already devoid of functionality, and references to it can be safely removed.
The deprecated `OneShot.isModifierActive(key)` function was removed on **2022-03-03**. OneShot modifiers are now indistinguishable from other modifier keys, so it is better for client code to do a more general search of `live_keys` or to use another mechanism for tracking this state.
#### `HostPowerManagement.enableWakeup()`
This deprecated function was removed on **2022-03-03**. The firmware now supports wakeup by default, so any references to it can be safely removed.
#### `EEPROMSettings.version(uint8_t version)`
This deprecated function was removed on **2022-03-03**. The information stored is not longer intended for user code to set, but instead is used internally.
#### Model01-TestMode plugin
This deprecated plugin was removed on **2022-03-03**. Please use the more generic HardwareTestMode plugin instead.
### Removed on 2020-10-10
### Deprecation of the HID facade
#### Deprecation of the HID facade
With the new Device APIs it became possible to replace the HID facade (the `kaleidoscope::hid` family of functions) with a driver. As such, the old APIs are deprecated, and was removed on 2020-10-10. Please use `Kaleidoscope.hid()` instead.
### Implementation of type Key internally changed from C++ union to class
#### Implementation of type Key internally changed from C++ union to class
The deprecated functions were removed on 2020-10-10.
@ -658,7 +1242,7 @@ The deprecated row/col based indexing APIs have been removed on **2020-06-16**.
#### EEPROMKeymap mode
The [EEPROM-Keymap](plugins/EEPROM-Keymap.md) plugin had its `setup()` method changed, the formerly optional `method` argument is now obsolete and unused. It can be safely removed.
The [EEPROM-Keymap](plugins/Kaleidoscope-EEPROM-Keymap.md) plugin had its `setup()` method changed, the formerly optional `method` argument is now obsolete and unused. It can be safely removed.
##### keymaps array and KEYMAPS and KEYMAPS_STACKED macros
@ -42,8 +42,6 @@ If something goes wrong, the status bar turns orange and displays an error messa
![](images/arduino-setup/verify-failed.png)
If you see errors, refer to [Getting help](Getting-help) for troubleshooting tips and useful resources.
# Install the firmware
If your keyboard has a programming interlock key, you'll need to hold it down now. On the Keyboardio Model 01, this is the `Prog` key. On the Keyboardio Atreus, this is the `Esc` key.
@ -258,6 +261,12 @@ There are rare cases where a file designed to be included is not self-contained.
All header files should have `#pragma once` guards at the top to prevent multiple inclusion.
### Include What You Use
If a source or header file refers to a symbol defined elsewhere, the file should directly include a header file which provides a declaration or definition of that symbol.
Do not rely on transitive inclusions. This allows maintainers to remove no-longer-needed `#include` statements from their headers without breaking clients code. This also applies to directly associated headers - `foo.cpp` should include `bar.h` if it uses a symbol defined there, even if `foo.h` (currently) includes `bar.h`.
### Forward Declarations
> Avoid using forward declarations where possible. Just `#include` the headers you need.
@ -311,66 +320,80 @@ Another useful rule of thumb: it's typically not cost effective to inline functi
It is important to know that functions are not always inlined even if they are declared as such; for example, virtual and recursive functions are not normally inlined. Usually recursive functions should not be inline. The main reason for making a virtual function inline is to place its definition in the class, either for convenience or to document its behavior, e.g., for accessors and mutators.
### Names and Order of Includes
### Organization of Includes
<!-- TODO: This section could be simplified, and clarified, I believe. -->
> Use standard order for readability and to avoid hidden dependencies:
> - The header associated with this source file, if any
> - System headers and Arduino library headers (including other Kaleidoscope plugins, but not Kaleidoscope itself)
> - Kaleidoscope headers and headers for the individual plugin (other than the associated header above)
> Use standard order for readability and to avoid hidden dependencies: Related header, Arduino libraries, other libraries' `.h`, your project's `.h`.
These three sections should be separated by single blank lines, and should be sorted alphabetically.
All libraries must have at least one header in their top-level `src/` directory, to be included without any path components. This is the way Arduino finds libraries, and a limitation we must adhere to. These headers should - in general - include any other headers they may need, so that the consumer of the library only has to include one header. The name of this header must be the same as the name of the library.
When including system headers and Arduino library headers (including Kaleidoscope plugins), use angle brackets to indicate that those sources are external.
For headers inside the current library and for Kaleidoscope core headers, use double quotes and a full pathname (starting below the `src/` directory). This applies to the source file's associated header, as well; don't use a pathname relative to the source file's directory.
The recommended naming is to prefix the library name with `Kaleidoscope-`.
For the sake of clarity, the above sections can be further divided to make it clear where each included header file can be found, but this is probably not necessary in most cases, because the path name of a header usually indicates which library it is located in.
If there is more than one header, they should be listed as descendants of the project's source directory without use of UNIX directory shortcuts `.` (the current directory) or `..` (the parent directory), and live in a `Kaleidoscope` subdirectory. For example, if we have a plugin named `Kaleidoscope-Something`, which has an additional header file other than `Kaleidoscope-Something.h`, it should be in `src/Kaleidoscope/Something-Other.h`, and be included as:
For example, the includes in `Kaleidoscope-Something/src/kaleidoscope/Something.cpp` might look like this:
```c++
#include "Kaleidoscope-Something.h"
#include "Kaleidoscope/Something-Other.h"
#include "kaleidoscope/Something.h"
#include<Arduino.h>
#include<Kaleidoscope-Ranges.h>
#include<stdint.h>
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/plugin/something/utils.h"
```
Having more than one level of subdirectories is not recommended.
**Exception**
In `dir/foo.cpp` or `dir/foo_test.cpp`, whose main purpose is to implement or test the stuff in `dir2/foo2.h`, order your includes as follows:
Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:
1. `dir2/foo2.h`
2. Arduino libraries.
3. Other libraries' `.h` files.
4. Your project's `.h` files.
```c++
#if defined(ARDUINO_AVR_MODEL01)
#include "kaleidoscope/Something-AVR-Model01.h"
#endif
With the preferred ordering, if `dir2/foo2.h` omits any necessary includes, the build of `dir/foo.cpp` or `dir/foo_test.cpp` will break. Thus, this rule ensures that build breakages show up first for the people working on these files, not for innocent people in other packages.
#if defined(ARDUINO_AVR_SHORTCUT)
#include "kaleidoscope/Something-AVR-Shortcut.h"
#endif
```
`dir/foo.cc` and `dir2/foo2.h` are usually in the same directory (e.g. `Kaleidoscope/Something_test.cpp` and `Kaleidoscope/Something.h`), but may sometimes be in different directories too.
### Top-level Arduinio Library Headers
Within each section the includes should be ordered alphabetically.
All libraries must have at least one header in their top-level `src/` directory, to be included without any path components. This is the way Arduino finds libraries, and a limitation we must adhere to. These headers should - in general - include any other headers they may need, so that the consumer of the library only has to include one header. The name of this header must be the same as the name of the library.
You should include all the headers that define the symbols you rely upon, except in the unusual case of [forward declarations](#forward-declarations). If you rely on symbols from `bar.h`, don't count on the fact that you included `foo.h` which (currently) includes `bar.h`: include `bar.h` yourself, unless `foo.h` explicitly demonstrates its intent to provide you the symbols of `bar.h`. However, any includes present in the related header do not need to be included again in the related `cc` (i.e., `foo.cc` can rely on `foo.h`'s includes).
The naming convention for Kaleidoscope plugins is to use the `Kaleidoscope-` prefix: e.g. `Kaleidoscope-Something`, which would have a top-level header named `Kaleidoscope-Something.h` in its `src/` directory.
For example, the includes in `Kaleidoscope-Something/src/Kaleidoscope/Something.cpp` might look like this:
In the case of Kaleidoscope plugin libraries, the number of source and header files tends to be very small (usually just one `*.cpp` file and its associated header, in addition to the library's top-level header). When one plugin depends on another, we therefore only include the top-level header of the dependency. For example, if `Kaleidoscope-OtherThing` depends on `Kaleidoscope-Something`, the file `kaleidoscope/plugin/OtherThing.h` will contain the line:
```c++
#include "Kaleidoscope/Something.h"
#include<Kaleidoscope-Something.h>
```
#include "Arduino.h"
…and `Kaleidoscope-Something.h` will look like this:
#include "Kaleidoscope-LEDControl.h"
#include "Kaleidoscope-Focus.h"
```c++
#include "kaleidoscope/plugin/Something.h"
```
**Exception**
This both makes it clearer where to find the included code, and allows the restructuring of that code without breaking the dependent library (assuming the symbols haven't changed as well).
Sometimes, system-specific code needs conditional includes. Such code can put conditional includes after other includes. Of course, keep your system-specific code small and localized. Example:
If a plugin library has symbols meant to be exported, and more than one header file in which those symbols are defined, all such header files should be included in the top-level header for the library. For example, if `Kaleidoscope-Something` defines types `kaleidoscope::plugin::Something` and `kaleidoscope::plugin::something::Helper`, both of which are meant to be accessible by `Kaleidoscope-OtherThing`, the top-level header `Kaleidoscope-Something.h` should look like this:
```c++
#include "Kaleidoscope.h"
#include "kaleidoscope/plugin/Something.h"
#include "kaleidoscope/plugin/something/Helper.h"
```
#if defined(ARDUINO_AVR_MODEL01)
#include "Kaleidoscope/Something-AVR-Model01.h"
#endif
### Automated header includes checking
#if defined(ARDUINO_AVR_SHORTCUT)
#include "Kaleidoscope/Something-AVR-Shortcut.h"
#endif
```
We have an automated wrapper for the `include-what-you-use` program from LLVM that processes most Kaleidoscope source files and updates their header includes to comply with the style guide. It requires at least version 0.18 of `include-what-you-use` in order to function properly (because earlier versions do not return a useful exit code, so determining if there was an error was difficult). It can be run by using the `make check-includes` target in the Kaleidoscope Makefile.
<!-- TODO: Finish converting the rest... -->
@ -2380,7 +2403,7 @@ New code should not contain calls to deprecated interface points. Use the new in
Coding style and formatting are pretty arbitrary, but a project is much easier to follow if everyone uses the same style. Individuals may not agree with every aspect of the formatting rules, and some of the rules may take some getting used to, but it is important that all project contributors follow the style rules so that they can all read and understand everyone's code easily.
To help you format code correctly, we use "Artistic Style" 3.0. The `make astyle` target is available in the Kaleidoscope and plugin Makefiles. Our CI infrastructure enforces `astyle`'s decisions.
To help format code in compliance with this style guide, we use `clang-format`, which many editors can be configured to call automatically. There is also `make format` target available in the Kaleidoscope Makefile that will use `clang-format` to format all the core and plugin code. Our CI infrastructure checks to ensure that code has been formatted to these specifications.
### Line Length
@ -3133,6 +3156,40 @@ The coding conventions described above are mandatory. However, like all good rul
If you find yourself modifying code that was written to specifications other than those presented by this guide, you may have to diverge from these rules in order to stay consistent with the local conventions in that code. If you are in doubt about how to do this, ask the original author or the person currently responsible for the code. Remember that *consistency* includes local consistency, too.
## Maintenance Tools
Kaleidoscope uses some automated tools to enforce compliance with this code style guide. Primarily, we use `clang-format` to format source files, `cpplint` to check for potential problems, and `include-what-you-use` to update header includes. These are invoked using python scripts that supply all of the necessary command-line parameters to both utilities. For convenience, there are also some shell scripts and makefile targets that further simplify the process of running these utilities properly.
### Code Formatting
We use `clang-format`(version 12 or higher) to automatically format Kaleidoscope source files. There is a top-level `.clang-format` config file that contains the settings that best match the style described in this guide. However, there are some files in the repository that are, for one reason or another, exempt from formatting in this way, so we use a wrapper script, `format-code.py`, instead of invoking it directly.
`format-code.py` takes a list of target filenames, either as command-line parameters or from standard input (if reading from a pipe, with each line treated as a filename), and formats those files. If given a directory target, it will recursively search that directory for source files, and format all of the ones it finds.
By default, `format-code.py` will first check for unstaged changes in the Kaleidoscope git working tree, and exit before formatting source files if any are found. This is meant to make it easier for developers to see the changes made by the formatter in isolation. It can be given the `--force` option to skip this check.
It also has a `--check` option, which will cause `format-code.py` to check for unstaged git working tree changes after running `clang-format` on the target source files, and return an error code if there are any, allowing us to automatically verify that code submitted complies with the formatting rules.
The easiest way to invoke the formatter on the whole repository is by running `make format` at the top level of the Kaleidoscope repository.
For automated checking of PRs in a CI tool, there is also `make check-code-style`.
### Linting
We use a copy of `cpplint.py` (with a modification that allows us to set the config file) to check for potential problems in the code. This can be invoked by running `make cpplint` at the top level of the Kaleidoscope repository.
### Header Includes
We use `include-what-you-use` (version 18 or higher) to automatically manage header includes in Kaleidoscope source files. Because of the peculiarities of Kaleidoscope's build system, we use a wrapper script, `iwyu.py`, instead of invoking it directly.
`iwyu.py` takes a list of target filenames, either as command-line parameters or from standard input (if reading from a pipe, with each line treated as a filename), and makes changes to the header includes. If given a directory target, it will recursively search that directory for source files, and run `include-what-you-use` on all of the ones it finds.
A number of files can't be processed this way, and are enumerated in `.iwyu_ignore`. Files in the `testing` directory (for the test simulator) require different include path items, so cannot be combined in the same call to `iwyu.py`.
The easiest way to invoke `iwyu.py` is by running `make check-includes`, which will check all files that differ between the git working tree and the current master branch of the main Kaleidoscope repository, update their headers, and return a non-zero exit code if there were any errors processing the file(s) or any changes made.
For automated checking of header includes in a CI tool, there is also `make check-all-includes`, that checks the whole repository, not just the current branch's changes.
@ -17,7 +17,7 @@ A single physical input, such as a keyswitch or other input like a knob or a sli
### Key number
An integer representing a Keyswitch’s position in the “Physical Layout”
An integer representing a Keyswitch’s position in the “Physical Layout”. Represented in the code by the `KeyAddr` type.
### Physical Layout
@ -33,7 +33,7 @@ A representation of a specific behavior. Most often a representation of a specif
### Keymap
A list of key bindings for all keyswitchess on the Physical Layout
A list of key bindings for all keyswitchess on the Physical Layout. Represented in the code by the `KeyMap` type.
### Keymaps
@ -47,11 +47,17 @@ An entry in that ordered list of keymaps. Each layer has a unique id number that
An ordered list of all the currently-active layers, in the order they should be evaluated when figuring out what a key does.
### Override Layer
### Live keys
A special layer that’s always active and evaluated before checking keys in the “Active layer stack”
A representation of the current state of the keyboard's keys, where non-transparent entries indicate keys that are active (logically—usually, but not necessarily, physically held). Represented in the code by the `LiveKeys` type (and the `live_keys` object).
#### Active/inactive keys
In the `live_keys[]` array, an _active_ key usually corresponds to a keyswitch that is physically pressed. In the common case of HID Keyboard keys, an active key will result in one or more keycodes being inserted in any new HID report. In some cases, an key can be active when its physical keyswitch is not pressed (e.g. OneShot keys that have been tapped), and in other cases a key might be _inactive_ even though its keyswitch is pressed (e.g. a Qukeys key whose value has not yet been resolved). Inactive keys are represented in the `live_keys[]` array by the special value `Key_Inactive`.
#### Masked keys
In the `live_keys[]` array, a _masked_ key is one whose next key press (either physical or logical) will be ignored. A masked key is automatically unmasked the next time it toggles off. Masked keys are represented by the special value `Key_Masked`.
This is an annotated list of some of Kaleidoscope's most important core plugins. You may also want to consult the [automatically generated list of all plugins bundled with Kaleidoscope](../plugin_list).
You can find a list of third-party plugins not distributed as part of Kaleidoscope at https://community.keyboard.io/c/programming/Discuss-Plugins-one-thread-per-plugin
You can find a list of third-party plugins not distributed as part of Kaleidoscope [on the forums][forum:plugin-list].
While keyboards usually ship with a keymap programmed in, to be able to change that keymap, without flashing new firmware, we need a way to place the keymap into a place we can update at run-time, and which persists across reboots. Fortunately, we have a bit of EEPROM on the keyboard, and can use it to store either the full keymap (and saving space in the firmware then), or store an overlay there. In the latter case, whenever there is a non-transparent key on the overlay, we will use that instead of the keyboard default.
@ -15,21 +17,13 @@ In short, this plugin allows us to change our keymaps, without having to compile
Turn the Esc key into a special key, that can cancel any active OneShot effect - or act as the normal Esc key if none are active. For those times when one accidentally presses a one-shot key, or change their minds.
The KeyLogger plugin, as the name suggests, implements a key logger for the Kaleidoscope firmware. It logs the row and column of every key press and release, along with the event, and the layer number, in a format that is reasonably easy to parse, to the Serial interface.
**A word of warning**: Having a key logger is as dangerous as it sounds. Anyone who can read the serial events from the keyboard, will know exactly what keys you press, and when. Unless you know what you are doing, and can secure your keyboard, do not enable this plugin.
Leader keys are a kind of key where when they are tapped, all following keys are swallowed, until the plugin finds a matching sequence in the dictionary, it times out, or fails to find any possibilities. When a sequence is found, the corresponding action is executed, but the processing still continues. If any key is pressed that is not the continuation of the existing sequence, processing aborts, and the key is handled normally.
@ -39,7 +33,7 @@ So we put ``LEAD u`` and ``LEAD u h e a r t`` in the dictionary only. The first
Macros are a standard feature on many keyboards and powered ones are no exceptions. Macros are a way to have a single key-press do a whole lot of things under the hood: conventionally, macros play back a key sequence, but with Kaleidoscope, there is much more we can do. Nevertheless, playing back a sequence of events is still the primary use of macros.
@ -47,7 +41,7 @@ Playing back a sequence means that when we press a macro key, we can have it pla
The MagicCombo extension provides a way to perform custom actions when a particular set of keys are held down together. The functionality assigned to these keys are not changed, and the custom action triggers as long as all keys within the set are pressed. The order in which they were pressed do not matter.
@ -55,7 +49,7 @@ This can be used to tie complex actions to key chords.
One-shots are a new kind of behaviour for your standard modifier and momentary layer keys: instead of having to hold them while pressing other keys, they can be tapped and released, and will remain active until any other key is pressed. In short, they turn ``Shift, A`` into ``Shift+A``, and ``Fn, 1`` to ``Fn+1``. The main advantage is that this allows us to place the modifiers and layer keys to positions that would otherwise be awkward when chording. Nevertheless, they still act as normal when held, that behaviour is not lost.
@ -65,7 +59,7 @@ To make multi-modifier, or multi-layer shortcuts possible, one-shot keys remain
A Qukey is a key that has two possible values, usually a modifier and a printable character. The name is a play on the term "qubit" (short for "quantum bit") from quantum computing. The value produced depends on how long the key press lasts, and how it is used in combination with other keys (roughly speaking, whether the key is "tapped" or "held").
@ -77,7 +71,7 @@ It is also possible to use Qukeys like SpaceCadet (see below), by setting the pr
ShapeShifter is a plugin that makes it considerably easier to change what symbol is input when a key is pressed together with ``Shift``. If one wants to rearrange the symbols on the number row for example, without modifying the layout on the operating system side, this plugin is where one can turn to.
@ -85,7 +79,7 @@ What it does, is very simple: if any key in its dictionary is found pressed whil
Space Cadet is a way to make it more convenient to input parens - those ``(`` and ``)`` things -, symbols that a lot of programming languages use frequently. If you are working with Lisp, you are using these all the time.
@ -97,7 +91,7 @@ After getting used to the Space Cadet style of typing, you may wish to enable th
Tap-dance keys are general purpose, multi-use keys, which trigger a different action based on the number of times they were tapped in sequence. As an example to make this clearer, one can have a key that inputs ``A`` when tapped once, inputs ``B`` when tapped twice, and lights up the keyboard in Christmas colors when tapped a third time.
@ -119,7 +113,6 @@ There is one additional value the tapDanceAction parameter can ``take: kaleidosc
TopsyTurvy is a plugin that inverts the behaviour of the Shift key for some selected keys. That is, if configured so, it will input ``!`` when pressing the ``1`` key without ``Shift``, but with the modifier pressed, it will input the original ``1`` symbol.
This is a brief guide intended for those who want to write custom Kaleidoscope plugins. It covers basic things you'll need to know about how Kaleidoscope calls plugin event handlers, and how it will respond to actions taken by those plugins.
## What can a plugin do?
There are many things that Kaleidoscope plugins are capable of, from LED effects, serial communication with the host, altering HID reports, and interacting with other plugins. It's useful to break these capabilities down into some broad categories, based on the types of input a plugin can respond to.
- Key events (key switches toggling on and off)
- Focus commands (sent to the keyboard from software on the host via the serial port)
- LED updates
- Keymap layer changes
- Timers
## An example plugin
To make a Kaleidoscope plugin, we create a subclass of the `kaleidoscope::Plugin` class, usually in the `kaleidoscope::plugin` namespace:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {};
} // namespace kaleidoscope
} // namespace plugin
```
This code can be placed in a separate C++ source file, but it's simplest to just define it right in the sketch's \*.ino file for now.
By convention, we create a singleton object named like the plugin's class in the global namespace. This is typical of Arduino code.
```c++
kaleidoscope::plugin::MyPlugin MyPlugin;
```
Next, in order to connect that plugin to the Kaleidoscope event handler system, we need to register it in the call to the preprocessor macro `KALEIDOSCOPE_INIT_PLUGINS()` in the sketch:
```c++
KALEIDOSCOPE_INIT_PLUGINS(MyPlugin, OtherPlugin);
```
To make our plugin do anything useful, we need to add [[event-handler-hooks]] to it. This is how Kaleidoscope delivers input events to its registered plugins. Here's an example:
```c++
class MyPlugin : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event);
};
```
This will result in `MyPlugin.onKeyEvent()` being called (along with other plugins' `onKeyEvent()` methods) when Kaleidoscope detects a key state change. This function returns one of three `EventHandlerResult` values:
- `EventHandlerResult::OK` indicates that Kaleidoscope should proceed on to the event handler for the next plugin in the chain.
- `EventHandlerResult::ABORT` indicates that Kaleidoscope should stop processing immediately, and treat the event as if it didn't happen.
- `EventHandlerResult::EVENT_CONSUMED` stops event processing like `ABORT`, but records that the key is being held.
The `onKeyEvent()` method takes one argument: a reference to a `KeyEvent` object, which is a simple container for these essential bits of information:
- `event.addr` — the physical location of the keyswitch, if any
- `event.state` — a bitfield containing information on the current and previous state of the keyswitch (from which we can find out if it just toggled on or toggled off)
- `event.key` — a 16-bit `Key` value containing the contents looked up from the sketch's current keymap (if the key just toggled on) or the current live value of the key (if the key just toggled off)
Because the `KeyEvent` parameter is passed by (mutable) reference, our plugin's `onKeyEvent()` method can alter the components of the event, causing subsequent plugins (and, eventually, Kaleidoscope itself) to treat it as if it was a different event. In practice, except in very rare cases, the only member of a `KeyEvent` that a plugin should alter is `event.key`. Here's a very simple `onKeyEvent()` handler that changes all `X` keys into `Y` keys:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
event.key = Key_Y;
return EventHandlerResult::OK;
}
```
### The difference between `ABORT`&`EVENT_CONSUMED`
Here's a plugin that will suppress all `X` key events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Here's an almost identical plugin that has an odd failure mode:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::OK;
}
```
In this case, when an `X` key is pressed, no Keyboard HID report will be generated and sent to the host, but the key will still be recorded by Kaleidoscope as "live". If we hold that key down and press a `Y` key, we will suddenly see both `x`_and_`y` in the output on the host. This is because returning `ABORT` suppresses the key event entirely, as if it never happened, whereas `EVENT_CONSUMED` signals to Kaleidoscope that the key should still become "live", but that no further processing is necessary. In this case, since we want to suppress all `X` keys entirely, we should return `ABORT`.
### A complete in-sketch plugin
Here's an example of a very simple plugin, defined as it would be in a firmware sketch (e.g. a `*.ino` file):
```c++
namespace kaleidoscope {
namespace plugin {
class KillX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::KillX;
```
On its own, this plugin won't have any effect unless we register it later in the sketch like this:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX);
```
Note: `KALEIDOSCOPE_INIT_PLUGINS()` should only appear once in a sketch, with a list of all the plugins to be registered.
## Plugin registration order
Obviously, the `KillX` plugin isn't very useful. But more important, it's got a potential problem. Suppose we had another plugin defined, like so:
```c++
namespace kaleidoscope {
namespace plugin {
class YtoX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_Y)
event.key = Key_X;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::YtoX;
```
`YtoX` changes any `Y` key to an `X` key. These two plugins both work fine on their own, but when we put them together, we get some undesirable behavior. Let's try it this way first:
```c++
KALEIDOSCOPE_INIT_PLUGINS(YtoX, KillX);
```
This registers both plugins' event handlers with Kaleidoscope, in order, so for each `KeyEvent` generated in response to a keyswitch toggling on or off, `YtoX.onKeyEvent(event)` will get called first, then `KillX.onKeyEvent(event)` will get called.
If we press `X`, the `YtoX` plugin will effectively ignore the event, allowing it to pass through to `KillX`, which will abort the event.
If we press `Y`, `YtoX.onKeyEvent()` will change `event.key` from `Key_Y` to `Key_X`. Then, `KillX.onKeyEvent()` will abort the event. As a result, both `X` and `Y` keys will be suppressed by the combination of the two plugins.
---
Now, let's try the same two plugins in the other order:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX, YtoX);
```
If we press `X`, its keypress event will get aborted by `KillX.onKeyEvent()`, and that key will not become live, so when it gets released, the event generated won't have the value `Key_X`, but will instead by `Key_Inactive`, which will not result in anything happening, either from the plugins or from Kaleidoscope itself.
Things get interesting if we press and release `Y`, though. First, `KillX.onKeyEvent()` will simply return `OK`, allowing `YtoX.onKeyEvent()` to change `event.key` from `Key_Y` to `Key_X`, causing that `Key_X` to become live, and sending its keycode to the host in the Keyboard USB HID report. That's all as expected, but then we release the key, and that's were it goes wrong.
`KillX.onKeyEvent()` doesn't distinguish between presses and releases. When a key toggles off, rather than looking up that key's value in the keymap, Kaleidoscope takes it from the live keys array. That means that `event.key` will be `Key_X` when `KillX.onKeyEvent()` is called, which will result in that event being aborted. And when an event is aborted, the key's entry in the live keys array doesn't get updated, so Kaleidoscope will treat it as if the key is still held after release. Thus, far from preventing the keycode for `X` getting to the host, it keeps that key pressed forever! The `X` key becomes "stuck on" because the plugin suppresses both key _presses_ and key _releases_.
### Differentiating between press and release events
There is a solution to this problem, which is to have `KillX` suppress `Key_X` toggle-on events, but not toggle-off events:
if (event.key == Key_X && keyToggledOn(event.state))
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Kaleidoscope provides `keyToggledOn()` and `keyToggledOff()` functions that operate on the `event.state` bitfield, allowing plugins to differentiate between the two different event states. With this new version of the `KillX` plugin, it won't keep an `X` key live, but it will stop one from _becoming_ live.
Our two plugins still yield results that depend on registration order in `KALEIDOSCOPE_INIT_PLUGINS()`, but the bug where the `X` key becomes "stuck on" is gone.
It is very common for plugins to only act on key toggle-on events, or to respond differently to toggle-on and toggle-off events.
## Timers
Another thing that many plugins need to do is handle timeouts. For example, the OneShot plugin keeps certain keys live for a period of time after those keys are released. Kaleidoscope provides some infrastructure to help us keep track of time, starting with the `afterEachCycle()` "event" handler function.
The `onKeyEvent()` handlers only get called in response to keyswitches toggling on and off (or as a result of plugins calling `Runtime.handleKeyEvent()`). If the user isn't actively typing for a period, its `onKeyEvent()` handler won't get called at all, so it's not very useful to check timers in that function. Instead, if we need to know if a timer has expired, we need to do it in a function that gets called regularly, regardless of input. The `afterEachCycle()` handler gets called once per cycle, guaranteed.
This is what an `afterEachCycle()` handler looks like:
```c++
EventHandlerResult afterEachCycle() {
return EventHandlerResult::OK;
}
```
It returns an `EventHandlerResult`, like other event handlers, but this one's return value is ignored by Kaleidoscope; returning `ABORT` or `EVENT_CONSUMED` has no effect on other plugins.
In addition to this, we need a way to keep track of time. For this, Kaleidoscope provides the function `Runtime.millisAtCycleStart()`, which returns an unsigned integer representing the number of milliseconds that have elapsed since the keyboard started. It's a 32-bit integer, so it won't overflow until about one month has elapsed, but we usually want to use as few bytes of RAM as possible on our MCU, so most timers store only as many bytes as needed, usually a `uint16_t`, which overflows after about one minute, or even a `uint8_t`, which is good for up to a quarter of a second.
We need to use an integer type that's at least as big as the longest timeout we expect to be used, but integer overflow can still give us the wrong answer if we check it by naïvely comparing the current time to the time at expiration, so Kaleidoscope provides a timeout-checking service that's handles the integer overflow properly: `Runtime.hasTimeExpired(start_time, timeout)`. To use it, your plugin should store a timestamp when the timer begins, using `Runtime.millisAtCycleStart()` (usually set in response to an event in `onKeyEvent()`). Then, in its `afterEachCycle()` call `hasTimeExpired()`:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {
public:
constexpr uint16_t timeout = 500;
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X && keyToggledOn(event.state)) {
start_time_ = Runtime.millisAtCycleStart();
timer_running_ = true;
}
return EventHandlerResult::OK;
}
EventHandlerResult afterEachCycle() {
if (Runtime.hasTimeExpired(start_time_, timeout)) {
timer_running_ = false;
// do something...
}
return EventHandlerResult::OK;
}
private:
bool timer_running_ = false;
uint16_t start_time_;
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::MyPlugin;
```
In the above example, the private member variable `start_time_` and the constant `timeout` are the same type of unsigned integer (`uint16_t`), and we've used the additional boolean `timer_running_` to keep from checking for timeouts when `start_time_` isn't valid. This plugin does something (unspecified) 500 milliseconds after a `Key_X` toggles on.
## Creating additional events
Another thing we might want a plugin to do is generate "extra" events that don't correspond to physical state changes. An example of this is the Macros plugin, which might turn a single keypress into a series of HID reports sent to the host. Let's build a simple plugin to illustrate how this is done, by making a key type a string of characters, rather than a single one.
For the sake of simplicity, let's make the key `H` result in the string `Hi!` being typed (from the point of view of the host computer). To do this, we'll make a plugin with an `onKeyEvent()` handler (because we want it to respond to a particular keypress event), which will call `Runtime.handleKeyEvent()` to generate new events sent to the host.
The first thing we need to understand to do this is how to use the `KeyEvent()` constructor to create a new `KeyEvent` object. For example:
This creates a `KeyEvent` where `event.addr` is an invalid address that doesn't correspond to a physical keyswitch, `event.state` has only the `IS_PRESSED` bit set, but not `WAS_PRESSED`, which corresponds to a key toggle-on event, and `event.key` is set to `Key_H`.
We can then cause Kaleidoscope to process this event, including calling plugin handlers, by calling `Runtime.handleKeyEvent(event)`:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
A few shortcuts were taken with this plugin that are worth pointing out. First, you may have noticed that we didn't send any key _release_ events, just three presses. This works, but there's a small chance that it could cause problems for some plugin that's trying to match key presses and releases. To be nice (or pedantic, if you will), we could also send the matching release events, but this is probably not necessary in this case, because we've used an invalid key address (`KeyAddr::none()`) for these generated events. This means that Kaleidoscope will not be recording these events as held keys. If we had used valid key addresses (corresponding to physical keyswitches) instead, it would be more important to send matching release events to keep keys from getting "stuck" on. For example, we could just use the address of the `H` key that was pressed:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
This new version has the curious property that if the `H` key is held long enough, it will result in repeating `!!!!` characters on the host, until the key is released, which will clear it. In fact, instead of creating a whole new `KeyEvent` object, we could further simplify this plugin by simply modifying the `event` object that we already have, instead:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
Note that, with this version, we've only sent two extra events, then changed the `event.key` value, and returned `OK` instead of `ABORT`. This is basically the same as the above pluging that turned `Y` into `X`, but with two extra events sent first.
As one extra precaution, it would be wise to mark the generated event(s) as "injected" to let other plugins know that these events should be ignored. This is a convention that is used by many existing Kaleidoscope plugins. We do this by setting the `INJECTED` bit in the `event.state` variable:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.state |= INJECTED;
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
If we wanted to be especially careful, we could also add the corresponding release events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_H && keyToggledOn(event.state)) {
event.key = LSHIFT(Key_H);
event.state = INJECTED | IS_PRESSED;
Runtime.handleKeyEvent(event);
event.state = INJECTED | WAS_PRESSED;
Runtime.handleKeyEvent(event);
event.key = Key_I;
event.state = INJECTED | IS_PRESSED;
Runtime.handleKeyEvent(event);
event.state = INJECTED | WAS_PRESSED;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
event.state = INJECTED | IS_PRESSED;
}
return EventHandlerResult::OK;
}
```
### Avoiding infinite loops
One very important consideration for any plugin that calls `Runtime.handleKeyEvent()` from an `onKeyEvent()` handler is recursion. `Runtime.handleKeyEvent()` will call all plugins' `onKeyEvent()` handlers, including the one that generated the event. Therefore, we need to take some measures to short-circuit the resulting recursive call so that our plugin doesn't cause an infinite loop.
Suppose the example plugin above was changed to type the string `hi!` instead of `Hi!`. When sending the first generated event, with `event.key` set to `Key_H`, our plugin would recognize that event as one that should be acted on, and make another call to `Runtime.handleKeyEvent()`, which would again call `MyPlugin.onKeyEvent()`, and so on until the MCU ran out of memory on the stack.
The simplest mechanism used by many plugins that mark their generated events "injected" is to simply ignore all generated events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if ((event.state & INJECTED) != 0)
return EventHandlerResult::OK;
if (event.key == Key_H && keyToggledOn(event.state)) {
event.state |= INJECTED;
event.key = LSHIFT(Key_H);
Runtime.handleKeyEvent(event);
event.key = Key_I;
Runtime.handleKeyEvent(event);
event.key = LSHIFT(Key_1);
}
return EventHandlerResult::OK;
}
```
There are other techniques to avoid inifinite loops, employed by plugins whose injected events should be processed by other plugins, but since most of those will be using the `onKeyswitchEvent()` handler instead of `onKeyEvent()`, we'll cover that later in this guide.
## Physical keyswitch events
Most plugins that respond to key events can do their work using the `onKeyEvent()` handler, but in some cases, it's necessary to use the `onKeyswitchEvent()` handler instead. These event handlers are strictly intended for physical keyswitch events, and plugins that implement the `onKeyswitchEvent()` handler must abide by certain rules in order to work well with each other. As a result, such a plugin is a bit more complex, but there are helper mechanisms to make things easier:
We've just added a `KeyEventTracker` object to our plugin, and made the first line of its `onKeyswitchEvent()` handler call that event tracker's `shouldIgnore()` method, returning `OK` if it returns `true` (thereby ignoring the event). Every plugin that implements `onKeyswitchEvent()` should follow this template to avoid plugin interaction bugs, including possible infinite loops.
The main reason for this event tracker mechanism is that plugins with `onKeyswitchEvent()` handlers often delay events because some aspect of those events (usually `event.key`) needs to be determined by subsequent events or timeouts. To do this, event information is stored, and the event is later regenerated by the plugin, which calls `Runtime.handleKeyswitchEvent()` so that the other `onKeyswitchEvent()` handlers can process it.
We need to prevent infinite loops, but simply marking the regenerated event `INJECTED` is no good, because it would prevent the other plugins from acting on it, so we instead keep track of a monotonically increasing event id number and use the `KeyEventTracker` helper class to ignore events that our plugin has already recieved, so that when the plugin regenerates an event with the same event id, it (and all the plugins before it) can ignore that event, but the subsequent plugins, which haven't seen that event yet, will recongize it as new and process the event accordingly.
### Regenerating stored events
When a plugin that implements `onKeyswitchEvent()` regenerates a stored event later so that it can be processed by the next plugin in the chain, it must use the correct event id value (the same one used by the original event). This is an object of type `EventId`, and is retrieved by calling `event.id()` (unlike the other properties of a `KeyEvent` object the event id is not directly accessible).
```c++
KeyEventId stored_id = event.id();
```
When reconstructing an event to allow it to proceed, we then use the four-argument version of the `KeyEvent` constructor:
In the above, `addr` and `state` are usually also the same as the original event's values, and `key` is most often the thing that changes. If your plugin wants a keymap lookup to take place, the value `Key_Undefined` can be used instead of explicitly doing the lookup itself.
@ -47,7 +47,7 @@ Next step: [Add keyboard support to Arduino](#add-keyboard-support-to-arduino)
Snap: https://snapcraft.io/arduino
Arch: sudo pacman -S arduino
```
Unfortunately, the version packaged in Ubuntu is too ancient to support Arduino's new way of doing 3rd-party hardware.
Unfortunately, the version of the Arduino IDE packaged in Ubuntu is unmaintained and too old to use, and the version packaged in Debian has been heavily modified and might not be able to compile your keyboard's firmware.
2. Assuming you're using the tar archive, and untarring in the download directory:
@ -67,13 +67,13 @@ Next step: [Add keyboard support to Arduino](#add-keyboard-support-to-arduino)
$ sudo /etc/init.d/udev reload
```
For Arch based distributions the following command will be used instead of `sudo /etc/init.d/udev reload`
For Arch based distributions use the following command instead of `sudo /etc/init.d/udev reload`
```sh
$ sudo udevadm control --reload-rules && udevadm trigger
```
4. Then disconnect and reconnect the keyboard for that change to take effect.
4. Next, disconnect and reconnect your keyboard so that your computer will apply the changes.
## <aname="Arduino-Windows"></a>Install Arduino on Windows 10
@ -148,10 +148,18 @@ Next step: [Add keyboard support to Arduino](#add-keyboard-support-to-arduino)
![](images/arduino-setup/open-preferences.png)
3. Paste the following url into the box labeled 'Additional Board Manager URLs':
3. To use released versions of Kaleidoscope, paste the following url into the box labeled 'Additional Board Manager URLs':