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>
$(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).
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"
XFER_DIR="$(pwd)/.docker_xfer"
mkdir -p "${XFER_DIR}"
mkdir -p "${XFER_DIR}"
if [ -z "$_NO_SYNC_KALEIDOSCOPE" ]; then
echo "Preparing Kaleidoscope..."
echo "The bundle is coming from ${ARDUINO_DIRECTORIES_USER}/hardware/keyboardio"
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
### 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.
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.
@ -633,38 +998,6 @@ Older versions of the plugin were based on `Key` values; OneShot is now based on
`KeyAddr` coordinates instead, in order to improve reliability and
`KeyAddr` coordinates instead, in order to improve reliability and
functionality.
functionality.
The following deprecated functions and variables will be removed after
**2021-04-31**.
#### Deprecated functions
- `OneShot.inject(key, key_state)`: This `Key`-based function still works, but
because OneShot keys are now required to have a valid `KeyAddr`, it will now
look for an idle key, and use that, masking whatever value was mapped to that
key. Most of the reasons for using this function are better addressed by using
the newer features of the plugin, such as automatic one-shot modifiers. Use is
very strongly discouraged.
- `OneShot.isActive(key)`: This `Key`-based function no longer makes sense now
that OneShot is `KeyAddr`-based. There is a `OneShot.isActive(key_addr)`
function that should be used instead. The deprecated function still works, but
its use is discouraged.
- `OneShot.isSticky(key)`: This `Key`-based function no longer makes sense now
that OneShot is `KeyAddr`-based. There is a `OneShot.isSticky(key_addr)`
function that should be used instead. The deprecated function still works, but
its use is discouraged.
- `OneShot.isPressed()`: This function no longer has any reason for existing. In
older versions, the Escape-OneShot companion plugin used it to solve a problem
that no longer exists. It now always returns `false`.
- `OneShot.isModifierActive(key)`: This function still works, but is not
perfectly reliable, because it now returns positive results for keys other
than OneShot modifiers. It should not be used.
#### Deprecated variables
- `OneShot.time_out`: Use `OneShot.setTimeout()` instead.
- `OneShot.hold_time_out`: Use `OneShot.setHoldTimeout()` instead.
- `OneShot.double_tap_time_out`: Use `OneShot.setDoubleTapTimeout()` instead.
### Qukeys
### Qukeys
Older versions of the plugin used `row` and `col` indexing for defining `Qukey`
Older versions of the plugin used `row` and `col` indexing for defining `Qukey`
@ -711,6 +1044,12 @@ The masking API has been removed on **2021-01-01**
## Deprecated APIs and their replacements
## 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
### 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.
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.
@ -732,23 +1071,107 @@ The following headers and names have changed:
- [Syster](plugins/Kaleidoscope-Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`.
- [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`.
- [TapDance](plugins/Kaleidoscope-TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`.
### Live Composite Keymap Cache
# Removed APIs
The live composite keymap, which contained a lazily-updated version of the current keymap, has been replaced. The `Layer.updateLiveCompositeKeymap()` functions have been deprecated, and depending on the purpose of the caller, it might be appropriate to use `live_keys.activate()` instead.
### Removed on 2022-03-03
When `handleKeyswitchEvent()` is looking up a `Key` value for an event, it first checks the value in the active keys cache before calling `Layer.lookup()` to get the value from the keymap. In the vast majority of cases, it won't be necessary to call `live_keys.activate()` manually, however, because simply changing the value of the `Key` parameter of an `onKeyswitchEvent()` handler will have the same effect.
#### Pre-`KeyEvent` event handler hooks
Second, the `Layer.eventHandler()` function has been deprecated. There wasn't much need for this to be available to plugins, and it's possible to call `Layer.handleKeymapKeyswitchEvent()` directly instead.
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).
# Removed APIs
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
### 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.
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.
The deprecated functions were removed on 2020-10-10.
@ -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.
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
### Forward Declarations
> Avoid using forward declarations where possible. Just `#include` the headers you need.
> 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.
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++
```c++
#include "Kaleidoscope-Something.h"
#include "kaleidoscope/Something.h"
#include "Kaleidoscope/Something-Other.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`
```c++
2. Arduino libraries.
#if defined(ARDUINO_AVR_MODEL01)
3. Other libraries' `.h` files.
#include "kaleidoscope/Something-AVR-Model01.h"
4. Your project's `.h` files.
#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++
```c++
#include "Kaleidoscope/Something.h"
#include<Kaleidoscope-Something.h>
```
#include "Arduino.h"
…and `Kaleidoscope-Something.h` will look like this:
#include "Kaleidoscope-LEDControl.h"
```c++
#include "Kaleidoscope-Focus.h"
#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++
```c++
#include "Kaleidoscope.h"
#include "kaleidoscope/plugin/Something.h"
#include "kaleidoscope/plugin/something/Helper.h"
```
#if defined(ARDUINO_AVR_MODEL01)
### Automated header includes checking
#include "Kaleidoscope/Something-AVR-Model01.h"
#endif
#if defined(ARDUINO_AVR_SHORTCUT)
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.
#include "Kaleidoscope/Something-AVR-Shortcut.h"
#endif
```
<!-- TODO: Finish converting the rest... -->
<!-- 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.
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
### 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.
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.
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).
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].
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.
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.
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.
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 artificial events
## Creating additional events
## Controlling LEDs
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.
## HID reports
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
## 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
Snap: https://snapcraft.io/arduino
Arch: sudo pacman -S arduino
Arch: sudo pacman -S arduino
```
```
Unfortunately, the version of the Arduino IDE packaged in Ubuntu is unmaintained and too old to use.
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:
2. Assuming you're using the tar archive, and untarring in the download directory: