It was failing to exclude `MoveToLayer()` keys, so it would return `true`
incorrectly for them.
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>
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 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>
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>
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>
In the process of moving towards a single repository for everything
Kaleidoscope, integrate KeyboardioScanner as a driver. This is a direct copy of
KeyboardioScanner as of 2090cd426cae25b07c0ce3a6b7365b95c21dd87b, renamed and
namespaced to fit into Kaleidoscope.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
We're going to merge KeyboardioScanner into Kaleidoscope as a driver, but we
can't name that model01::Hand, because then we have a filename conflict due to
our use of static archiving during linking. To avoid that, both will use a
similar, but unique naming pattern, and raise::Hand becomes raise::RaiseSide
instead.
Signed-off-by: Gergely Nagy <algernon@keyboard.io>
This change adds test methods to the `Key` class for the builtin variants for
the different HID types (Keyboard, Consumer Control, and System Control), and
layer change keys:
- `key.isKeyboardKey()`
- `key.isConsumerControlKey()`
- `key.isSystemControlKey()`
- `key.isLayerKey()`
In addition, a few useful sub-variants are called out. These will probably be
more commonly used by plugins:
- `key.isKeyboardModifier()` returns `true` if `key` is a HID Keyboard key, and
its keycode is a modifier. Note that this includes modifiers with modifier
flags, so it will also return `true` for `Key_Meh`, for example.
- `key.isKeyboardShift()` returns true if `key` both above tests pass, and
either the base keycode is a modifier, or the shift mod flag is set.
Shift is a special case, even among modifiers, because it is more often of
concern by plugins (e.g. TopsyTurvy, ShapeShifter) than others.
- `key.isLayerShift()` returns `true` if `key` is a layer shift key.
Layer shifts are tested more often than other types of layer changing keys
because they, like HID Keyboard modifiers, are used chorded, rather than simply
having an effect that completes when they toggle on.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
This is a convenience only, but it's much more straightforward than the
expression `key_addr = KeyAddr(KeyAddr::invalide_state)` or the overly enigmatic
`key_addr = KeyAddr()`.
`KeyAddr::none()` is meant to be useful for initialization of variables:
`KeyAddr my_addr = KeyAddr::none()`
`KeyAddr::clear()` is meant to be the counterpart of the `isValid()` test, to
set an existing variable to an invalid address:
`my_addr.clear()` vs `if (my_addr.isValid()) ...`
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The virtual hardware device, unlike others, was calling `handleKeyswitchEvent()`
for every keyswitch, every cycle. It should suppress calls corresponding to idle
keyswitches, just like the other devices.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
The code for guarding against integer overflow on the prior interval timestamp
was in the wrong place, and wouldn't get executed on cycles when the keyboard
was idle, leading to a very slim chance of getting the wrong qukey value if all
keys were idle long enough (65 seconds).
Also fixed the same problem in the first quarter-second after the keyboard power
on. Not likely to ever be observed, but costs nothing extra to fix.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Macros was still using its own bit in the `Key.flags_` byte to define Macros
keys, unlike all the other plugins that define their own special `Key`
values. This standardizes Macros to make it more like other plugins.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Some users have pointed out that certain keys (in particular, `space` &
`backspace`) are inconvenient to require the minimum prior interval to make a
qukey resolve to a modifier (especially `shift`). We could blacklist those keys,
but it's hard to predict what they all might be. The problem is mainly one for
very fast typists, and therefore I expect it to show up when following the
"normal" printable keys, not often other keys.
This could also be made into a configurable list, but I'd prefer not to do so
unless there's serious demand for it, as Qukeys already has too many settings.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
Unintended modifiers are becoming an increasingly big problem among users of
Qukeys. This change adds yet another configuration option to prevent this from
happening while users are typing fast. It introduces a new requirement to make a
qukey eligible to become a qukey; a minimum amount of time that must pass
between the keypress event for a non-modifier key and the subsequent keypress
event of the qukey.
Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>