pull/1072/head
Gareth Hancock 3 years ago
commit d28a9fdcf1

@ -12,6 +12,13 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading
## New features ## New features
### SpaceCadet "no-delay" mode
SpaceCadet can now be enabled in "no-delay" mode, wherein the primary (modifier)
value of the key will be sent to the host immediately when the key is pressed.
If the SpaceCadet key is released before the timeout, the modifier is released,
and then the alternate (symbol) value is sent. To activate "no-delay" mode, call `SpaceCadet.enableWithoutDelay()`.
### New Qukeys features ### New Qukeys features
#### Tap-repeat #### Tap-repeat
@ -208,6 +215,10 @@ To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base
## New plugins ## New plugins
### AutoShift
The [AutoShift](plugins/Kaleidoscope-AutoShift.md) plugin provides an alternative way to get shifted symbols, by long-pressing keys instead of using a separate `shift` key.
### DynamicMacros ### DynamicMacros
The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis. The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis.

@ -7,6 +7,7 @@ If any of this does not make sense to you, or you have trouble updating your .in
* [Upgrade notes](#upgrade-notes) * [Upgrade notes](#upgrade-notes)
+ [New features](#new-features) + [New features](#new-features)
- [New event handler](#new-event-handler)
- [Event-driven main loop](#event-driven-main-loop) - [Event-driven main loop](#event-driven-main-loop)
- [Keyboard state array](#keyboard-state-array) - [Keyboard state array](#keyboard-state-array)
- [New build system](#new-build-system) - [New build system](#new-build-system)
@ -37,6 +38,10 @@ any API we've included in a release. Typically, this means that any code that us
## New features ## New features
### New event handler
One more `KeyEvent` handler has been added: `afterReportingState(const KeyEvent &event)`. This handler gets called after HID reports are sent for an event, providing a point for plugins to act after an event has been fully processed by `Runtime.handleKeyEvent()`.
### Event-driven main loop ### Event-driven main loop
Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events. Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events.

@ -27,13 +27,13 @@ amount of custom code one has to write will be minimal.
A `Device` is the topmost level component, it is the interface the rest of A `Device` is the topmost level component, it is the interface the rest of
Kaleidoscope will work with. The [`kaleidoscope::device::Base`][k:d:Base] class Kaleidoscope will work with. The [`kaleidoscope::device::Base`][k:d:Base] class
is the ancestor of _all_ devices, everything derives from this. Devices that use is the ancestor of _all_ devices, everything derives from this. Devices that use
an `ATMega32U4` MCU we also have the an `ATmega32U4` MCU we also have the
[`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4] class, which sets up [`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4] class, which sets up
some of the components that is common to all `ATMega32U4`-based devices (such as some of the components that is common to all `ATmega32U4`-based devices (such as
the _MCU_ and the _Storage_). the _MCU_ and the _Storage_).
[k:d:Base]:../src/kaleidoscope/device/Base.h [k:d:Base]: ../../src/kaleidoscope/device/Base.h
[k:d:a32u4]: ../src/kaleidoscope/device/ATMega32U4.h [k:d:a32u4]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h
As hinted at above, a device - or rather, it's `Props` - describe the components As hinted at above, a device - or rather, it's `Props` - describe the components
used for the device, such as the MCU, the Bootloader, the Storage driver, LEDs, used for the device, such as the MCU, the Bootloader, the Storage driver, LEDs,
@ -42,7 +42,7 @@ in `Props` - the defaults are all no-ops.
All devices must also come with a `Props` struct, deriving from [`kaleidoscope::device::BaseProps`][k:d:BaseProps]. All devices must also come with a `Props` struct, deriving from [`kaleidoscope::device::BaseProps`][k:d:BaseProps].
[k:d:BaseProps]: ../src/kaleidoscope/device/Base.h [k:d:BaseProps]: ../../src/kaleidoscope/device/Base.h
As an example, the most basic device we can have, that does nothing, would look As an example, the most basic device we can have, that does nothing, would look
like this: like this:
@ -66,16 +66,16 @@ components the device ends up using.
The heart of any device will be the main controller unit, or _MCU_ for short. The heart of any device will be the main controller unit, or _MCU_ for short.
The [`kaleidoscope::driver::mcu::Base`][k:d:m:Base] class is the ancestor of our The [`kaleidoscope::driver::mcu::Base`][k:d:m:Base] class is the ancestor of our
MCU drivers, including [`mcu::ATMega32U4`][k:d:m:a32u4]. MCU drivers, including [`kaleidoscope::driver::mcu::ATmega32U4`][k:d:m:a32u4].
[k:d:m:Base]: ../src/kaleidoscope/driver/mcu/Base.h [k:d:m:Base]: ../../src/kaleidoscope/driver/mcu/Base.h
[k:d:m:a32u4]: ../src/kaleidoscope/driver/mcu/ATMega32U4.h [k:d:m:a32u4]: ../../src/kaleidoscope/driver/mcu/ATmega32U4.h
The core firmware will use the `detachFromHost()` and `attachToHost()` methods The core firmware will use the `detachFromHost()` and `attachToHost()` methods
of the MCU driver, along with `setup()`, but the driver - like any other of the MCU driver, along with `setup()`, but the driver - like any other
driver - is free to have other methods, to be used by individual devices. driver - is free to have other methods, to be used by individual devices.
For example, the [`ATMega32U4`][k:d:m:a32u4] driver implements a `disableJTAG()` For example, the [`ATmega32U4`][k:d:m:a32u4] driver implements a `disableJTAG()`
and a `disableClockDivision()` method, which some of our devices use in their and a `disableClockDivision()` method, which some of our devices use in their
constructors. constructors.
@ -88,16 +88,16 @@ thing that allows us to re-program the keyboard without additional hardware
(aptly called a programmer). As such, the [`base class`][k:d:b:Base] has a (aptly called a programmer). As such, the [`base class`][k:d:b:Base] has a
single method, `rebootBootloader()`, which our bootloader components implement. single method, `rebootBootloader()`, which our bootloader components implement.
[k:d:b:Base]: ../src/kaleidoscope/bootloader/Base.h [k:d:b:Base]: ../../src/kaleidoscope/driver/bootloader/Base.h
Kaleidoscope currently supports [`Catalina`][k:d:b:Catalina], Kaleidoscope currently supports [`Caterina`][k:d:b:Caterina],
[`HalfKay`][k:d:b:HalfKay], and [`FLIP`][k:d:b:FLIP] bootloaders. Please consult [`HalfKay`][k:d:b:HalfKay], and [`FLIP`][k:d:b:FLIP] bootloaders. Please consult
them for more information. In many cases, setting up the bootloader in the them for more information. In many cases, setting up the bootloader in the
device props is all one needs to do. device props is all one needs to do.
[k:d:b:Catalina]: ../src/kaleidoscope/driver/bootloader/avr/Catalina.h [k:d:b:Caterina]: ../../src/kaleidoscope/driver/bootloader/avr/Caterina.h
[k:d:b:HalfKay]: ../src/kaleidoscope/driver/bootloader/avr/HalfKay.h [k:d:b:HalfKay]: ../../src/kaleidoscope/driver/bootloader/avr/HalfKay.h
[k:d:b:FLIP]: ../src/kaleidoscope/driver/bootloader/avr/FLIP.h [k:d:b:FLIP]: ../../src/kaleidoscope/driver/bootloader/avr/FLIP.h
Like the _MCU_ component, the _bootloader_ does not use Props, either. Like the _MCU_ component, the _bootloader_ does not use Props, either.
@ -113,40 +113,40 @@ to flash new firmware.
The Storage API resembles the Arduino EEPROM API very closely. In fact, our The Storage API resembles the Arduino EEPROM API very closely. In fact, our
[`AVREEPROM`][k:d:s:AVREEPROM] class is but a thin wrapper around that! [`AVREEPROM`][k:d:s:AVREEPROM] class is but a thin wrapper around that!
[k:d:s:Base]: ../src/kaleidoscope/driver/storage/Base.h [k:d:s:Base]: ../../src/kaleidoscope/driver/storage/Base.h
[chrysalis]: https://github.com/keyboardio/Chrysalis [chrysalis]: https://github.com/keyboardio/Chrysalis
[k:d:s:AVREEPROM]: ../src/kaleidoscope/driver/storage/AVREEPROM.h [k:d:s:AVREEPROM]: ../../src/kaleidoscope/driver/storage/AVREEPROM.h
The `Storage` component does use Props, one that describes the length - or The `Storage` component does use Props, one that describes the length - or
size - of it. We provide an [`ATMega32U4EEPROMProps`][k:d:s:a32u4props] helper, size - of it. We provide an [`ATmega32U4EEPROMProps`][k:d:s:a32u4props] helper,
which is preconfigured for the 1k EEPROM size of the ATMega32U4. which is preconfigured for the 1k EEPROM size of the ATmega32U4.
[k:d:s:a32u4props]: ../src/kaleidoscope/driver/storage/ATMega32U4EEPROMProps.h [k:d:s:a32u4props]: ../../src/kaleidoscope/driver/storage/ATmega32U4EEPROMProps.h
### LEDs ### LEDs
[`kaleidoscope::driver::led::Base`][k:d:l:Base] [`kaleidoscope::driver::led::Base`][k:d:l:Base]
[k:d:l:Base]: ../src/kaleidoscope/driver/led/Base.h [k:d:l:Base]: ../../src/kaleidoscope/driver/led/Base.h
### Keyscanner ### Keyscanner
[`kaleidoscope::driver::keyscanner::Base`][k:d:ks:Base] [`kaleidoscope::driver::keyscanner::Base`][k:d:ks:Base]
[k:d:ks:Base]: ../src/kaleidoscope/driver/keyscanner/Base.h [k:d:ks:Base]: ../../src/kaleidoscope/driver/keyscanner/Base.h
## Helpers ## Helpers
[`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4k] [`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4k]
[`kaleidoscope::driver::keyscanner::AVR`][k:d:ks:avr] [`kaleidoscope::driver::keyscanner::ATmega`][k:d:ks:atm]
[k:d:a32u4k]: ../src/kaleidoscope/device/ATMega32U4Keyboard.h [k:d:a32u4k]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h
[k:d:ks:avr]: ../src/kaleidoscope/driver/keyscanner/AVR.h [k:d:ks:atm]: ../../src/kaleidoscope/driver/keyscanner/ATmega.h
## Putting it all together ## Putting it all together
To put things into perspective, and show a simple example, we'll build an To put things into perspective, and show a simple example, we'll build an
imaginary mini keypad: `ATMega32U4` with `Caterina` as bootloader, no LEDs, and imaginary mini keypad: `ATmega32U4` with `Caterina` as bootloader, no LEDs, and
four keys only. four keys only.
### `ImaginaryKeypad.h` ### `ImaginaryKeypad.h`
@ -157,9 +157,9 @@ four keys only.
#ifdef ARDUINO_AVR_IMAGINARY_KEYPAD #ifdef ARDUINO_AVR_IMAGINARY_KEYPAD
#include <Arduino.h> #include <Arduino.h>
#include "kaleidoscope/driver/keyscanner/AVR.h" #include "kaleidoscope/driver/keyscanner/ATmega.h"
#include "kaleidoscope/driver/bootloader/avr/Caterina.h" #include "kaleidoscope/driver/bootloader/avr/Caterina.h"
#include "kaleidoscope/device/ATMega32U4Keyboard.h" #include "kaleidoscope/device/ATmega32U4Keyboard.h"
namespace kaleidoscope { namespace kaleidoscope {
namespace device { namespace device {

@ -183,6 +183,14 @@ abortable. That is, if it returns a result other than `OK` it will stop the
subsequent handlers from getting called, and if it returns `ABORT`, it will also subsequent handlers from getting called, and if it returns `ABORT`, it will also
stop the report from being sent.] stop the report from being sent.]
### `afterReportingState(const KeyEvent &event)`
This gets called after the HID report is sent. This handler allows a plugin to
react to an event, but wait until after that event has been fully processed to
do so. For example, the OneShot plugin releases keys that are in the "one-shot"
state in response to key press events, but it does so after those triggering
press events take place.
## Other events ## Other events
### `onLayerChange()` ### `onLayerChange()`

@ -0,0 +1,261 @@
# How to write a Kaleidoscope plugin
This is a brief guide intended for those who want to write custom Kaleidoscope plugins. It covers basic things you'll need to know about how Kaleidoscope calls plugin event handlers, and how it will respond to actions taken by those plugins.
## What can a plugin do?
There are many things that Kaleidoscope plugins are capable of, from LED effects, serial communication with the host, altering HID reports, and interacting with other plugins. It's useful to break these capabilities down into some broad categories, based on the types of input a plugin can respond to.
- Key events (key switches toggling on and off)
- Focus commands (sent to the keyboard from software on the host via the serial port)
- LED updates
- Keymap layer changes
- Timers
## An example plugin
To make a Kaleidoscope plugin, we create a subclass of the `kaleidoscope::Plugin` class, usually in the `kaleidoscope::plugin` namespace:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {};
} // namespace kaleidoscope
} // namespace plugin
```
This code can be placed in a separate C++ source file, but it's simplest to just define it right in the sketch's \*.ino file for now.
By convention, we create a singleton object named like the plugin's class in the global namespace. This is typical of Arduino code.
```c++
kaleidoscope::plugin::MyPlugin MyPlugin;
```
Next, in order to connect that plugin to the Kaleidoscope event handler system, we need to register it in the call to the preprocessor macro `KALEIDOSCOPE_INIT_PLUGINS()` in the sketch:
```c++
KALEIDOSCOPE_INIT_PLUGINS(MyPlugin, OtherPlugin);
```
To make our plugin do anything useful, we need to add [[event-handler-hooks]] to it. This is how Kaleidoscope delivers input events to its registered plugins. Here's an example:
```c++
class MyPlugin : public Plugin {
public:
EventHanderResult onKeyEvent(KeyEvent &event);
};
```
This will result in `MyPlugin.onKeyEvent()` being called (along with other plugins' `onKeyEvent()` methods) when Kaleidoscope detects a key state change. This function returns one of three `EventHandlerResult` values:
- `EventHandlerResult::OK` indicates that Kaleidoscope should proceed on to the event handler for the next plugin in the chain.
- `EventHandlerResult::ABORT` indicates that Kaleidoscope should stop processing immediately, and treat the event as if it didn't happen.
- `EventHandlerResult::EVENT_CONSUMED` stops event processing like `ABORT`, but records that the key is being held.
The `onKeyEvent()` method takes one argument: a reference to a `KeyEvent` object, which is a simple container for these essential bits of information:
- `event.addr` — the physical location of the keyswitch, if any
- `event.state` — a bitfield containing information on the current and previous state of the keyswitch (from which we can find out if it just toggled on or toggled off)
- `event.key` — a 16-bit `Key` value containing the contents looked up from the sketch's current keymap (if the key just toggled on) or the current live value of the key (if the key just toggled off)
Because the `KeyEvent` parameter is passed by (mutable) reference, our plugin's `onKeyEvent()` method can alter the components of the event, causing subsequent plugins (and, eventually, Kaleidoscope itself) to treat it as if it was a different event. In practice, except in very rare cases, the only member of a `KeyEvent` that a plugin should alter is `event.key`. Here's a very simple `onKeyEvent()` handler that changes all `X` keys into `Y` keys:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
event.key = Key_Y;
return EventHandlerResult::OK;
}
```
### The difference between `ABORT` & `EVENT_CONSUMED`
Here's a plugin that will suppress all `X` key events:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Here's an almost identical plugin that has an odd failure mode:
```c++
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::EVENT_CONSUMED;
return EventHandlerResult::OK;
}
```
In this case, when an `X` key is pressed, no Keyboard HID report will be generated and sent to the host, but the key will still be recorded by Kaleidoscope as "live". If we hold that key down and press a `Y` key, we will suddenly see both `x` _and_ `y` in the output on the host. This is because returning `ABORT` suppresses the key event entirely, as if it never happened, whereas `EVENT_CONSUMED` signals to Kaleidoscope that the key should still become "live", but that no further processing is necessary. In this case, since we want to suppress all `X` keys entirely, we should return `ABORT`.
### A complete in-sketch plugin
Here's an example of a very simple plugin, defined as it would be in a firmware sketch (e.g. a `*.ino` file):
```c++
namespace kaleidoscope {
namespace plugin {
class KillX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::KillX;
```
On its own, this plugin won't have any effect unless we register it later in the sketch like this:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX);
```
Note: `KALEIDOSCOPE_INIT_PLUGINS()` should only appear once in a sketch, with a list of all the plugins to be registered.
## Plugin registration order
Obviously, the `KillX` plugin isn't very useful. But more important, it's got a potential problem. Suppose we had another plugin defined, like so:
```c++
namespace kaleidoscope {
namespace plugin {
class YtoX : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_Y)
event.key = Key_X;
return EventHandlerResult::OK;
}
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::YtoX;
```
`YtoX` changes any `Y` key to an `X` key. These two plugins both work fine on their own, but when we put them together, we get some undesirable behavior. Let's try it this way first:
```c++
KALEIDOSCOPE_INIT_PLUGINS(YtoX, KillX);
```
This registers both plugins' event handlers with Kaleidoscope, in order, so for each `KeyEvent` generated in response to a keyswitch toggling on or off, `YtoX.onKeyEvent(event)` will get called first, then `KillX.onKeyEvent(event)` will get called.
If we press `X`, the `YtoX` plugin will effectively ignore the event, allowing it to pass through to `KillX`, which will abort the event.
If we press `Y`, `YtoX.onKeyEvent()` will change `event.key` from `Key_Y` to `Key_X`. Then, `KillX.onKeyEvent()` will abort the event. As a result, both `X` and `Y` keys will be suppressed by the combination of the two plugins.
---
Now, let's try the same two plugins in the other order:
```c++
KALEIDOSCOPE_INIT_PLUGINS(KillX, YtoX);
```
If we press `X`, its keypress event will get aborted by `KillX.onKeyEvent()`, and that key will not become live, so when it gets released, the event generated won't have the value `Key_X`, but will instead by `Key_Inactive`, which will not result in anything happening, either from the plugins or from Kaleidoscope itself.
Things get interesting if we press and release `Y`, though. First, `KillX.onKeyEvent()` will simply return `OK`, allowing `YtoX.onKeyEvent()` to change `event.key` from `Key_Y` to `Key_X`, causing that `Key_X` to become live, and sending its keycode to the host in the Keyboard USB HID report. That's all as expected, but then we release the key, and that's were it goes wrong.
`KillX.onKeyEvent()` doesn't distinguish between presses and releases. When a key toggles off, rather than looking up that key's value in the keymap, Kaleidoscope takes it from the live keys array. That means that `event.key` will be `Key_X` when `KillX.onKeyEvent()` is called, which will result in that event being aborted. And when an event is aborted, the key's entry in the live keys array doesn't get updated, so Kaleidoscope will treat it as if the key is still held after release. Thus, far from preventing the keycode for `X` getting to the host, it keeps that key pressed forever! The `X` key becomes "stuck on" because the plugin suppresses both key _presses_ and key _releases_.
### Differentiating between press and release events
There is a solution to this problem, which is to have `KillX` suppress `Key_X` toggle-on events, but not toggle-off events:
```c++
EventHandlerResult KillX::onKeyEvent(KeyEvent &event) {
if (event.key == Key_X && keyToggledOn(event.state))
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
```
Kaleidoscope provides `keyToggledOn()` and `keyToggledOff()` functions that operate on the `event.state` bitfield, allowing plugins to differentiate between the two different event states. With this new version of the `KillX` plugin, it won't keep an `X` key live, but it will stop one from _becoming_ live.
Our two plugins still yield results that depend on registration order in `KALEIDOSCOPE_INIT_PLUGINS()`, but the bug where the `X` key becomes "stuck on" is gone.
It is very common for plugins to only act on key toggle-on events, or to respond differently to toggle-on and toggle-off events.
## Timers
Another thing that many plugins need to do is handle timeouts. For example, the OneShot plugin keeps certain keys live for a period of time after those keys are released. Kaleidoscope provides some infrastructure to help us keep track of time, starting with the `afterEachCycle()` "event" handler function.
The `onKeyEvent()` handlers only get called in response to keyswitches toggling on and off (or as a result of plugins calling `Runtime.handleKeyEvent()`). If the user isn't actively typing for a period, its `onKeyEvent()` handler won't get called at all, so it's not very useful to check timers in that function. Instead, if we need to know if a timer has expired, we need to do it in a function that gets called regularly, regardless of input. The `afterEachCycle()` handler gets called once per cycle, guaranteed.
This is what an `afterEachCycle()` handler looks like:
```c++
EventHandlerResult afterEachCycle() {
return EventHandlerResult::OK;
}
```
It returns an `EventHandlerResult`, like other event handlers, but this one's return value is ignored by Kaleidoscope; returning `ABORT` or `EVENT_CONSUMED` has no effect on other plugins.
In addition to this, we need a way to keep track of time. For this, Kaleidoscope provides the function `Runtime.millisAtCycleStart()`, which returns an unsigned integer representing the number of milliseconds that have elapsed since the keyboard started. It's a 32-bit integer, so it won't overflow until about one month has elapsed, but we usually want to use as few bytes of RAM as possible on our MCU, so most timers store only as many bytes as needed, usually a `uint16_t`, which overflows after about one minute, or even a `uint8_t`, which is good for up to a quarter of a second.
We need to use an integer type that's at least as big as the longest timeout we expect to be used, but integer overflow can still give us the wrong answer if we check it by naïvely comparing the current time to the time at expiration, so Kaleidoscope provides a timeout-checking service that's handles the integer overflow properly: `Runtime.hasTimeExpired(start_time, timeout)`. To use it, your plugin should store a timestamp when the timer begins, using `Runtime.millisAtCycleStart()` (usually set in response to an event in `onKeyEvent()`). Then, in its `afterEachCycle()` call `hasTimeExpired()`:
```c++
namespace kaleidoscope {
namespace plugin {
class MyPlugin : public Plugin {
public:
constexpr uint16_t timeout = 500;
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (event.key == Key_X && keyToggledOn(event.state)) {
start_time_ = Runtime.millisAtCycleStart();
timer_running_ = true;
}
return EventHandlerResult::OK;
}
EventHandlerResult afterEachCycle() {
if (Runtime.hasTimeExpired(start_time_, timeout)) {
timer_running_ = false;
// do something...
}
return EventHandlerResult::OK;
}
private:
bool timer_running_ = false;
uint16_t start_time_;
};
} // namespace kaleidoscope
} // namespace plugin
kaleidoscope::plugin::MyPlugin;
```
In the above example, the private member variable `start_time_` and the constant `timeout` are the same type of unsigned integer (`uint16_t`), and we've used the additional boolean `timer_running_` to keep from checking for timeouts when `start_time_` isn't valid. This plugin does something (unspecified) 500 milliseconds after a `Key_X` toggles on.
## Creating artificial events
## Controlling LEDs
## HID reports
## Physical keyswitch events
## Layer changes

@ -0,0 +1,75 @@
// -*- mode: c++ -*-
#include <Kaleidoscope.h>
#include <Kaleidoscope-AutoShift.h>
#include <Kaleidoscope-EEPROM-Settings.h>
#include <Kaleidoscope-EEPROM-Keymap.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-Macros.h>
enum {
TOGGLE_AUTOSHIFT,
};
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
XXX,
M(TOGGLE_AUTOSHIFT), Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
XXX
),
)
// *INDENT-ON*
// Defining a macro (on the "any" key: see above) to turn AutoShift on and off
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
switch (macro_id) {
case TOGGLE_AUTOSHIFT:
if (keyToggledOn(event.state))
AutoShift.toggle();
break;
}
return MACRO_NONE;
}
KALEIDOSCOPE_INIT_PLUGINS(
EEPROMSettings, // for AutoShiftConfig
EEPROMKeymap, // for AutoShiftConfig
Focus, // for AutoShiftConfig
FocusEEPROMCommand, // for AutoShiftConfig
FocusSettingsCommand, // for AutoShiftConfig
AutoShift,
AutoShiftConfig, // for AutoShiftConfig
Macros // for toggle AutoShift Macro
);
void setup() {
// Enable AutoShift for letter keys and number keys only:
AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys());
// Add symbol keys to the enabled categories:
AutoShift.enable(AutoShift.symbolKeys());
// Set the AutoShift long-press time to 150ms:
AutoShift.setTimeout(150);
// Start with AutoShift turned off:
AutoShift.disable();
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:avr:model01",
"port": ""
}
}

@ -0,0 +1,192 @@
/* -*- mode: c++ -*-
* Kaleidoscope-LeaderPrefix -- Prefix arg for Leader plugin
* Copyright (C) 2021 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Leader.h>
#include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-Ranges.h>
#include "kaleidoscope/KeyEventTracker.h"
#include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope/plugin.h"
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
LEAD(0),
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
LEAD(0)),
)
// *INDENT-ON*
namespace kaleidoscope {
namespace plugin {
// =============================================================================
/// Plugin to supply a numeric prefix argument to Leader key functions
///
/// This plugin lets the user type a numeric prefix after a Leader key is
/// pressed, but before the rest of the Leader sequence is begun, storing the
/// "prefix argument" and making it available to functions called from the
/// leader dictionary. LeaderPrefix allows us to define keys other than the
/// ones on the number row to be interpreted as the "digit" keys, because
/// whatever we use will need to be accessed without a layer change.
class LeaderPrefix : public Plugin {
public:
// We need to define `onKeyswitchEvent()` instead of `onKeyEvent()` because we
// need to intercept events before Leader sees them, and the Leader plugin
// uses the former.
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
// Every `onKeyswitchEvent()` function should begin with this to prevent
// re-processing events that it has already seen.
if (event_tracker_.shouldIgnore(event))
return EventHandlerResult::OK;
// `Active` means that we're actively building the prefix argument. If the
// plugin is not active, we're looking for a Leader key toggling on.
if (!active_) {
if (keyToggledOn(event.state) && isLeaderKey(event.key)) {
// A Leader key toggled on, so we set our state to "active", and set the
// arg value to zero.
active_ = true;
leader_arg_ = 0;
}
// Whether or not the plugin just became active, there's nothing more to
// do for this event.
return EventHandlerResult::OK;
}
// The plugin is "active", so we're looking for a "digit" key that just
// toggled on.
if (keyToggledOn(event.state)) {
// We search our array of digit keys to find one that matches the event.
// These "digit keys" are defined by their `KeyAddr` because they're
// probably independent of keymap and layer, and because a `KeyAddr` only
// takes one byte, whereas a `Key` takes two.
for (uint8_t i{0}; i < 10; ++i) {
if (digit_addrs_[i] == event.addr) {
// We found a match, which means that one of our "digit keys" toggled
// on. If this happens more than once, the user is typing a number
// with multiple digits, so we multiply the current value by ten
// before adding the new digit to the total.
leader_arg_ *= 10;
leader_arg_ += i;
// Next, we mask the key that was just pressed, so that nothing will
// happen when it is released.
live_keys.mask(event.addr);
// We return `ABORT` so that no other plugins (i.e. Leader) will see
// this keypress event.
return EventHandlerResult::ABORT;
}
}
}
// No match was found, so the key that toggled on was not one of our "digit
// keys". Presumably, this is the first key in the Leader sequence that is
// being typed. We leave the prefix argument at its current value so that
// it will still be set when the sequence is finished, and allow the event
// to pass through to the next plugin (i.e. Leader).
active_ = false;
return EventHandlerResult::OK;
}
uint16_t arg() const {
return leader_arg_;
}
private:
// The "digit keys": these are the keys on the number row of the Model01.
KeyAddr digit_addrs_[10] = {
KeyAddr(0, 14), KeyAddr(0, 1), KeyAddr(0, 2), KeyAddr(0, 3), KeyAddr(0, 4),
KeyAddr(0, 5), KeyAddr(0, 10), KeyAddr(0, 11), KeyAddr(0, 12), KeyAddr(0, 13),
};
// This event tracker is necessary to prevent re-processing events. Any
// plugin that defines `onKeyswitchEvent()` should use one.
KeyEventTracker event_tracker_;
// The current state of the plugin. It determines whether we're looking for a
// Leader keypress or building a prefix argument.
bool active_{false};
// The prefix argument itself.
uint16_t leader_arg_{0};
// Leader should probably provide this test, but since it doesn't, we add it
// here to determine if a key is a Leader key.
bool isLeaderKey(Key key) {
return (key >= ranges::LEAD_FIRST && key <= ranges::LEAD_LAST);
}
};
} // namespace plugin
} // namespace kaleidoscope
// This creates our plugin object.
kaleidoscope::plugin::LeaderPrefix LeaderPrefix;
auto &serial_port = Kaleidoscope.serialPort();
static void leaderTestX(uint8_t seq_index) {
serial_port.println(F("leaderTestX"));
}
static void leaderTestXX(uint8_t seq_index) {
serial_port.println(F("leaderTestXX"));
}
// This demonstrates how to use the prefix argument in a Leader function. In
// this case, our function just types as many `x` characters as specified by the
// prefix arg.
void leaderTestPrefix(uint8_t seq_index) {
// Read the prefix argument into a temporary variable:
uint8_t prefix_arg = LeaderPrefix.arg();
// Use a Macros helper function to tap the `X` key repeatedly.
while (prefix_arg-- > 0)
Macros.tap(Key_X);
}
static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM =
LEADER_DICT({LEADER_SEQ(LEAD(0), Key_X), leaderTestX},
{LEADER_SEQ(LEAD(0), Key_X, Key_X), leaderTestXX},
{LEADER_SEQ(LEAD(0), Key_Z), leaderTestPrefix});
// The order matters here; LeaderPrefix won't work unless it precedes Leader in
// this list. If there are other plugins in the list, these two should ideally
// be next to each other, but that's not necessary.
KALEIDOSCOPE_INIT_PLUGINS(LeaderPrefix, Leader);
void setup() {
Kaleidoscope.setup();
Leader.dictionary = leader_dictionary;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:avr:model01",
"port": ""
}
}

@ -6,6 +6,11 @@
enum { MACRO_TOGGLE_QUKEYS }; enum { MACRO_TOGGLE_QUKEYS };
// To define DualUse Qukeys in the keymap itself, we can use `SFT_T(key)`,
// `CTL_T(key)`, `ALT_T(key)`, `GUI_T(key)`, and `LT(layer, key)`, as defined
// for the home row on the right side of the keymap (and one of the palm keys)
// below.
// *INDENT-OFF* // *INDENT-OFF*
KEYMAPS( KEYMAPS(
[0] = KEYMAP_STACKED [0] = KEYMAP_STACKED
@ -63,6 +68,9 @@ const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, Macros); KALEIDOSCOPE_INIT_PLUGINS(Qukeys, Macros);
void setup() { void setup() {
// The following Qukey definitions are for the left side of the home row (and
// the left palm key) of the Keyboardio Model01 keyboard. For other
// keyboards, the `KeyAddr(row, col)` coordinates will need adjustment.
QUKEYS( QUKEYS(
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 1), Key_LeftGui), // A/cmd kaleidoscope::plugin::Qukey(0, KeyAddr(2, 1), Key_LeftGui), // A/cmd
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 2), Key_LeftAlt), // S/alt kaleidoscope::plugin::Qukey(0, KeyAddr(2, 2), Key_LeftAlt), // S/alt

@ -0,0 +1,86 @@
# AutoShift
AutoShift allows you to type shifted characters by long-pressing a key, rather
than chording it with a modifier key.
## Using the plugin
Using the plugin with its defaults is as simple as including the header, and
enabling the plugin:
```c++
#include <Kaleidoscope.h>
#include <Kaleidoscope-AutoShift.h>
KALEIDOSCOPE_INIT_PLUGINS(AutoShift);
```
With AutoShift enabled, when you first press a key that AutoShift acts on, its
output will be delayed. If you hold the key down long enough, you will see the
shifted symbol appear in the output. If you release the key before the timeout,
the output will be unshifted.
## Turning AutoShift on and off
The `AutoShift` object provides three methods for turning itself on and off:
- To turn the plugin off, call `AutoShift.enable()`.
- To turn the plugin on, call `AutoShift.disable()`.
- To toggle the plugin's state, call `AutoShift.toggle()`.
Note: Disabling the AutoShift plugin does not affect which `Key` categories it
will affect when it is re-enabled.
## Setting the AutoShift long-press delay
To set the length of time AutoShift will wait before adding the `shift` modifier
to the key's output, use `AutoShift.setTimeout(t)`, where `t` is a number of
milliseconds.
## Configuring which keys get auto-shifted
AutoShift provides a set of key categories that can be independently added or
removed from the set of keys that will be auto-shifted when long-pressed:
- `AutoShift.letterKeys()`: Letter keys
- `AutoShift.numberKeys()`: Number keys (number row, not numeric keypad)
- `AutoShift.symbolKeys()`: Other printable symbols
- `AutoShift.arrowKeys()`: Navigational arrow keys
- `AutoShift.functionKeys()`: All function keys (F1-F24)
- `AutoShift.printableKeys()`: Letters, numbers, and symbols
- `AutoShift.allKeys()`: All non-modifier USB Keyboard keys
These categories are restricted to USB Keyboard-type keys, and any modifier
flags attached to the key is ignored when determining if it will be
auto-shifted. Any of the above expressions can be used as the `category` parameter in the functions described below.
- To include a category in the set that will be auto-shifted, call `AutoShift.enable(category)`
- To remove a category from the set that will be auto-shifted, call `AutoShift.disable(category)`
- To set the full list of categories that will be auto-shifted, call `AutoShift.setEnabled(categories)`, where `categories` can be either a single category from the above list, or list of categories combined using the `|` (bitwise-or) operator (e.g. `AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys())`).
## Advanced customization of which keys get auto-shifted
If the above categories are not sufficient for your auto-shifting needs, it is
possible to get even finer-grained control of which keys are affected by
AutoShift, by overriding the `isAutoShiftable()` method in your sketch. For
example, to make AutoShift only act on keys `A` and `Z`, include the following
code in your sketch:
```c++
bool AutoShift::isAutoShiftable(Key key) {
if (key == Key_A || key == key_Z)
return true;
return false;
}
```
As you can see, this method takes a `Key` as its input and returns either `true`
(for keys eligible to be auto-shifted) or `false` (for keys AutoShift will leave
alone).
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: /examples/Keystrokes/AutoShift/AutoShift.ino

@ -0,0 +1,7 @@
name=Kaleidoscope-AutoShift
version=0.0.0
sentence=Automatic shift on long press
maintainer=Kaleidoscope's Developers <jesse@keyboard.io>
url=https://github.com/keyboardio/Kaleidoscope
author=Michael Richters <gedankenexperimenter@gmail.com>
paragraph=

@ -0,0 +1,20 @@
/* -*- mode: c++ -*-
* Kaleidoscope-AutoShift -- Automatic shift on long press
* Copyright (C) 2021 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <kaleidoscope/plugin/AutoShift.h>

@ -0,0 +1,212 @@
/* -*- mode: c++ -*-
* Kaleidoscope-AutoShift -- Automatic shift on long press
* Copyright (C) 2021 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kaleidoscope/plugin/AutoShift.h"
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/Runtime.h"
namespace kaleidoscope {
namespace plugin {
// =============================================================================
// AutoShift static class variables
// Configuration settings that can be saved to persistent storage.
AutoShift::Settings AutoShift::settings_ = {
.enabled = true,
.timeout = 175,
.enabled_categories = AutoShift::Categories::printableKeys(),
};
// The event tracker ensures that the `onKeyswitchEvent()` handler will follow
// the rules in order to avoid interference with other plugins and prevent
// processing the same event more than once.
KeyEventTracker AutoShift::event_tracker_;
// If there's a delayed keypress from AutoShift, this stored event will contain
// a valid `KeyAddr`. The default constructor produces an event addr of
// `KeyAddr::none()`, so the plugin will start in an inactive state.
KeyEvent pending_event_;
// =============================================================================
// AutoShift functions
void AutoShift::disable() {
settings_.enabled = false;
if (pending_event_.addr.isValid()) {
Runtime.handleKeyswitchEvent(pending_event_);
}
}
// -----------------------------------------------------------------------------
// Test for whether or not to apply AutoShift to a given `Key`. This function
// can be overridden from the user sketch.
__attribute__((weak))
bool AutoShift::isAutoShiftable(Key key) {
return enabledForKey(key);
}
// The default method that determines whether a particular key is an auto-shift
// candidate. Used by `isAutoShiftable()`, separate to allow re-use when the
// caller is overridden.
bool AutoShift::enabledForKey(Key key) {
// We only support auto-shifting keyboard keys. We could also explicitly
// ignore modifier keys, but there's no need to do so, as none of the code
// below matches modifiers.
if (!key.isKeyboardKey())
return false;
// We compare only the keycode, and disregard any modifier flags applied to
// the key. This simplifies the comparison, and also allows AutoShift to
// apply to keys like `RALT(Key_E)`.
uint8_t keycode = key.getKeyCode();
if (isEnabled(Categories::allKeys())) {
if (keycode < HID_KEYBOARD_FIRST_MODIFIER)
return true;
}
if (isEnabled(Categories::letterKeys())) {
if (keycode >= Key_A.getKeyCode() && keycode <= Key_Z.getKeyCode())
return true;
}
if (isEnabled(Categories::numberKeys())) {
if (keycode >= Key_1.getKeyCode() && keycode <= Key_0.getKeyCode())
return true;
}
if (isEnabled(Categories::symbolKeys())) {
if ((keycode >= Key_Minus.getKeyCode() && keycode <= Key_Slash.getKeyCode()) ||
(keycode == Key_NonUsBackslashAndPipe.getKeyCode()))
return true;
}
if (isEnabled(Categories::arrowKeys())) {
if (keycode >= Key_RightArrow.getKeyCode() &&
keycode <= Key_LeftArrow.getKeyCode())
return true;
}
if (isEnabled(Categories::functionKeys())) {
if ((keycode >= Key_F1.getKeyCode() && keycode <= Key_F12.getKeyCode()) ||
(keycode >= Key_F13.getKeyCode() && keycode <= Key_F24.getKeyCode()))
return true;
}
return false;
}
// =============================================================================
// Event handler hook functions
// -----------------------------------------------------------------------------
EventHandlerResult AutoShift::onKeyswitchEvent(KeyEvent &event) {
// If AutoShift has already processed and released this event, ignore it.
// There's no need to update the event tracker in this one case.
if (event_tracker_.shouldIgnore(event)) {
// We should never get an event that's in our queue here, but just in case
// some other plugin sends one, abort.
if (queue_.shouldAbort(event))
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
// If event.addr is not a physical key, ignore it; some other plugin injected
// it. This check should be unnecessary.
if (!event.addr.isValid() || (event.state & INJECTED) != 0) {
return EventHandlerResult::OK;
}
// Do nothing if disabled.
if (!settings_.enabled)
return EventHandlerResult::OK;
if (!queue_.isEmpty()) {
// There's an unresolved AutoShift key press.
if (queue_.isFull()) {
flushQueue();
} else if (event.addr == queue_.addr(0)) {
flushEvent(false);
flushQueue();
}
if (queue_.isEmpty())
return EventHandlerResult::OK;
queue_.append(event);
return EventHandlerResult::ABORT;
}
if (keyToggledOn(event.state) && isAutoShiftable(event.key)) {
queue_.append(event);
return EventHandlerResult::ABORT;
}
return EventHandlerResult::OK;
}
// -----------------------------------------------------------------------------
EventHandlerResult AutoShift::afterEachCycle() {
// If there's a pending AutoShift event, and it has timed out, we need to
// release the event with the `shift` flag applied.
if (!queue_.isEmpty() &&
Runtime.hasTimeExpired(queue_.timestamp(0), settings_.timeout)) {
// Toggle the state of the `SHIFT_HELD` bit in the modifier flags for the
// key for the pending event.
flushEvent(true);
flushQueue();
}
return EventHandlerResult::OK;
}
void AutoShift::flushQueue() {
while (!queue_.isEmpty()) {
if (queue_.isRelease(0) || checkForRelease()) {
flushEvent(false);
} else {
return;
}
}
}
bool AutoShift::checkForRelease() const {
KeyAddr queue_head_addr = queue_.addr(0);
for (uint8_t i = 1; i < queue_.length(); ++i) {
if (queue_.addr(i) == queue_head_addr) {
// This key's release is also in the queue
return true;
}
}
return false;
}
void AutoShift::flushEvent(bool is_long_press) {
if (queue_.isEmpty())
return;
KeyEvent event = queue_.event(0);
if (is_long_press) {
event.key = Runtime.lookupKey(event.addr);
uint8_t flags = event.key.getFlags();
flags ^= SHIFT_HELD;
event.key.setFlags(flags);
}
queue_.shift();
Runtime.handleKeyswitchEvent(event);
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::AutoShift AutoShift;

@ -0,0 +1,278 @@
/* -*- mode: c++ -*-
* Kaleidoscope-AutoShift -- Automatic shift on long press
* Copyright (C) 2021 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "kaleidoscope/Runtime.h"
#include "kaleidoscope/KeyEventTracker.h"
#include "kaleidoscope/KeyAddrEventQueue.h"
namespace kaleidoscope {
namespace plugin {
// =============================================================================
/// Kaleidoscope plugin for long-press auto-shift keys
///
/// This plugin allows the user to "long-press" keys to automatically apply the
/// `shift` modifier to the keypress. By enabling AutoShift, it's possible to
/// produce capital letters (for example) without holding a separate modifier
/// key first.
class AutoShift : public Plugin {
public:
// ---------------------------------------------------------------------------
// Inner class for `Key` categories that can be configured to be auto-shifted
// by long-pressing. Most of this class is purely internal, but user code
// that enables or disables these auto-shift categories might use the
// following as constants:
//
// - `AutoShift::Categories::letterKeys()`
// - `AutoShift::Categories::numberKeys()`
// - `AutoShift::Categories::symbolKeys()`
// - `AutoShift::Categories::arrowKeys()`
// - `AutoShift::Categories::functionKeys()`
// - `AutoShift::Categories::printableKeys()`
// - `AutoShift::Categories::allKeys()`
//
// The first two ("letter keys" and "number keys") are self-explanatory. The
// third ("symbol keys") includes keys that produce symbols other than letters
// and numbers, but not whitespace characters, modifiers, et cetera. We could
// perhaps add another category for function keys.
class Categories {
private:
// raw bitfield data
uint8_t raw_bits_{0};
// constants for bits in the `raw_bits_` bitfield
static constexpr uint8_t LETTERS = 1 << 0;
static constexpr uint8_t NUMBERS = 1 << 1;
static constexpr uint8_t SYMBOLS = 1 << 2;
static constexpr uint8_t ARROWS = 1 << 3;
static constexpr uint8_t FUNCTIONS = 1 << 4;
static constexpr uint8_t ALL = 1 << 7;
public:
// Basic un-checked constructor
constexpr Categories(uint8_t raw_bits) : raw_bits_(raw_bits) {}
static constexpr Categories letterKeys() {
return Categories(LETTERS);
}
static constexpr Categories numberKeys() {
return Categories(NUMBERS);
}
static constexpr Categories symbolKeys() {
return Categories(SYMBOLS);
}
static constexpr Categories arrowKeys() {
return Categories(ARROWS);
}
static constexpr Categories functionKeys() {
return Categories(FUNCTIONS);
}
static constexpr Categories printableKeys() {
return Categories(LETTERS | NUMBERS | SYMBOLS);
}
static constexpr Categories allKeys() {
return Categories(ALL);
}
constexpr void set(uint8_t raw_bits) {
raw_bits_ = raw_bits;
}
constexpr void add(Categories categories) {
this->raw_bits_ |= categories.raw_bits_;
}
constexpr void remove(Categories categories) {
this->raw_bits_ &= ~(categories.raw_bits_);
}
constexpr bool contains(Categories categories) const {
return (this->raw_bits_ & categories.raw_bits_) != 0;
// More correct test:
// return (~(this->raw_bits_) & categories.raw_bits_) == 0;
}
// Shorthand for combining categories:
// e.g. `Categories::letterKeys() | Categories::numberKeys()`
constexpr Categories operator|(Categories other) const {
return Categories(this->raw_bits_ | other.raw_bits_);
}
// A conversion to integer is provided for the sake of interactions with the
// Focus plugin.
explicit constexpr operator uint8_t() {
return raw_bits_;
}
};
// ---------------------------------------------------------------------------
// This lets the AutoShiftConfig plugin access the internal config variables
// directly. Mainly useful for calls to `Runtime.storage.get()`.
friend class AutoShiftConfig;
// ---------------------------------------------------------------------------
// Configuration functions
/// Returns `true` if AutoShift is active, `false` otherwise
static bool enabled() {
return settings_.enabled;
}
/// Activates the AutoShift plugin (held keys will trigger auto-shift)
static void enable() {
settings_.enabled = true;
}
/// Deactivates the AutoShift plugin (held keys will not trigger auto-shift)
static void disable();
/// Turns AutoShift on if it's off, and vice versa
static void toggle() {
if (settings_.enabled) {
disable();
} else {
enable();
}
}
/// Returns the hold time required to trigger auto-shift (in ms)
static uint16_t timeout() {
return settings_.timeout;
}
/// Sets the hold time required to trigger auto-shift (in ms)
static void setTimeout(uint16_t new_timeout) {
settings_.timeout = new_timeout;
}
/// Returns the set of categories currently eligible for auto-shift
static Categories enabledCategories() {
return settings_.enabled_categories;
}
/// Adds `category` to the set eligible for auto-shift
///
/// Possible values for `category` are:
/// - `AutoShift::Categories::letterKeys()`
/// - `AutoShift::Categories::numberKeys()`
/// - `AutoShift::Categories::symbolKeys()`
/// - `AutoShift::Categories::arrowKeys()`
/// - `AutoShift::Categories::functionKeys()`
/// - `AutoShift::Categories::printableKeys()`
/// - `AutoShift::Categories::allKeys()`
static void enable(Categories category) {
settings_.enabled_categories.add(category);
}
/// Removes a `Key` category from the set eligible for auto-shift
static void disable(Categories category) {
settings_.enabled_categories.remove(category);
}
/// Replaces the list of `Key` categories eligible for auto-shift
static void setEnabled(Categories categories) {
settings_.enabled_categories = categories;
}
/// Returns `true` if the given category is eligible for auto-shift
static bool isEnabled(Categories category) {
return settings_.enabled_categories.contains(category);
}
/// The category representing letter keys
static constexpr Categories letterKeys() {
return Categories::letterKeys();
}
/// The category representing number keys (on the number row)
static constexpr Categories numberKeys() {
return Categories::numberKeys();
}
/// The category representing other printable symbol keys
static constexpr Categories symbolKeys() {
return Categories::symbolKeys();
}
/// The category representing arrow keys
static constexpr Categories arrowKeys() {
return Categories::arrowKeys();
}
/// The category representing function keys
static constexpr Categories functionKeys() {
return Categories::functionKeys();
}
/// Letters, numbers, and other symbols
static constexpr Categories printableKeys() {
return Categories::printableKeys();
}
/// All non-modifier keyboard keys
static constexpr Categories allKeys() {
return Categories::allKeys();
}
// ---------------------------------------------------------------------------
/// Determines which keys will trigger auto-shift if held long enough
///
/// This function can be overridden by the user sketch to configure which keys
/// can trigger auto-shift.
static bool isAutoShiftable(Key key);
// ---------------------------------------------------------------------------
// Event handlers
EventHandlerResult onKeyswitchEvent(KeyEvent &event);
EventHandlerResult afterEachCycle();
private:
// ---------------------------------------------------------------------------
/// A container for AutoShift configuration settings
struct Settings {
/// The overall state of the plugin (on/off)
bool enabled;
/// The length of time (ms) a key must be held to trigger auto-shift
uint16_t timeout;
/// The set of `Key` categories eligible to be auto-shifted
Categories enabled_categories;
};
static Settings settings_;
// ---------------------------------------------------------------------------
// Key event queue state variables
// A device for processing only new events
static KeyEventTracker event_tracker_;
// The maximum number of events in the queue at a time.
static constexpr uint8_t queue_capacity_{4};
// The event queue stores a series of press and release events.
KeyAddrEventQueue<queue_capacity_> queue_;
void flushQueue();
void flushEvent(bool is_long_press = false);
bool checkForRelease() const;
/// The default function for `isAutoShiftable()`
static bool enabledForKey(Key key);
};
// =============================================================================
/// Configuration plugin for persistent storage of settings
class AutoShiftConfig : public Plugin {
public:
EventHandlerResult onSetup();
EventHandlerResult onFocusEvent(const char *command);
private:
// The base address in persistent storage for configuration data
static uint16_t settings_base_;
};
} // namespace plugin
} // namespace kaleidoscope
extern kaleidoscope::plugin::AutoShift AutoShift;
extern kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig;

@ -0,0 +1,121 @@
/* -*- mode: c++ -*-
* Kaleidoscope-AutoShift -- Automatic shift on long press
* Copyright (C) 2021 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kaleidoscope/plugin/AutoShift.h"
#include <Kaleidoscope-EEPROM-Settings.h>
#include <Kaleidoscope-FocusSerial.h>
#include "kaleidoscope/Runtime.h"
namespace kaleidoscope {
namespace plugin {
// =============================================================================
// AutoShift configurator
uint16_t AutoShiftConfig::settings_base_;
EventHandlerResult AutoShiftConfig::onSetup() {
settings_base_ = ::EEPROMSettings.requestSlice(sizeof(AutoShift::settings_));
uint32_t checker;
Runtime.storage().get(settings_base_, checker);
// Check if we have an empty eeprom...
if (checker == 0xffffffff) {
// ...if the eeprom was empty, store the default settings.
Runtime.storage().put(settings_base_, AutoShift::settings_);
Runtime.storage().commit();
}
Runtime.storage().get(settings_base_, AutoShift::settings_);
return EventHandlerResult::OK;
}
EventHandlerResult AutoShiftConfig::onFocusEvent(const char *command) {
enum {
ENABLED,
TIMEOUT,
CATEGORIES,
} subCommand;
if (::Focus.handleHelp(command, PSTR("autoshift.enabled\n"
"autoshift.timeout\n"
"autoshift.categories")))
return EventHandlerResult::OK;
if (strncmp_P(command, PSTR("autoshift."), 10) != 0)
return EventHandlerResult::OK;
if (strcmp_P(command + 10, PSTR("enabled")) == 0)
subCommand = ENABLED;
else if (strcmp_P(command + 10, PSTR("timeout")) == 0)
subCommand = TIMEOUT;
else if (strcmp_P(command + 10, PSTR("categories")) == 0)
subCommand = CATEGORIES;
else
return EventHandlerResult::OK;
switch (subCommand) {
case ENABLED:
if (::Focus.isEOL()) {
::Focus.send(::AutoShift.enabled());
} else {
uint8_t v;
::Focus.read(v);
// if (::Focus.readUint8() != 0) {
if (v != 0) {
::AutoShift.enable();
} else {
::AutoShift.disable();
}
}
break;
case TIMEOUT:
if (::Focus.isEOL()) {
::Focus.send(::AutoShift.timeout());
} else {
uint16_t t;
::Focus.read(t);
// auto t = ::Focus.readUint16();
::AutoShift.setTimeout(t);
}
break;
case CATEGORIES:
if (::Focus.isEOL()) {
::Focus.send(uint8_t(::AutoShift.enabledCategories()));
} else {
uint8_t v;
::Focus.read(v);
auto categories = AutoShift::Categories(v);
// auto categories = AutoShift::Categories(::Focus.readUint8());
::AutoShift.setEnabled(categories);
}
break;
}
Runtime.storage().put(settings_base_, AutoShift::settings_);
Runtime.storage().commit();
return EventHandlerResult::EVENT_CONSUMED;
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig;

@ -26,6 +26,7 @@
#define CRGB(r,g,b) (cRGB){b, g, r} #define CRGB(r,g,b) (cRGB){b, g, r}
#include "kaleidoscope/driver/keyscanner/Base.h" #include "kaleidoscope/driver/keyscanner/Base.h"
#include "kaleidoscope/driver/hid/Keyboardio.h"
#include "kaleidoscope/driver/led/Base.h" #include "kaleidoscope/driver/led/Base.h"
#include "kaleidoscope/driver/bootloader/samd/Bossac.h" #include "kaleidoscope/driver/bootloader/samd/Bossac.h"
#include "kaleidoscope/driver/storage/Flash.h" #include "kaleidoscope/driver/storage/Flash.h"
@ -156,6 +157,8 @@ struct RaiseStorageProps : public kaleidoscope::driver::storage::FlashProps {
struct RaiseSideFlasherProps : public kaleidoscope::util::flasher::BaseProps {}; struct RaiseSideFlasherProps : public kaleidoscope::util::flasher::BaseProps {};
struct RaiseProps : kaleidoscope::device::BaseProps { struct RaiseProps : kaleidoscope::device::BaseProps {
typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps;
typedef kaleidoscope::driver::hid::Keyboardio<HIDProps> HID;
typedef RaiseLEDDriverProps LEDDriverProps; typedef RaiseLEDDriverProps LEDDriverProps;
typedef RaiseLEDDriver LEDDriver; typedef RaiseLEDDriver LEDDriver;
typedef RaiseKeyScannerProps KeyScannerProps; typedef RaiseKeyScannerProps KeyScannerProps;

@ -22,7 +22,8 @@
#define _DEPRECATED_MESSAGE_ENABLEWAKEUP \ #define _DEPRECATED_MESSAGE_ENABLEWAKEUP \
"The HostPowerManagement.enableWakeup() call is not necessary anymore,\n" \ "The HostPowerManagement.enableWakeup() call is not necessary anymore,\n" \
"the firmware supports wakeup by default now. The line can be safely\n" \ "the firmware supports wakeup by default now. The line can be safely\n" \
"removed." "removed.\n" \
"This function will be removed after 2021-08-01."
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {

@ -29,7 +29,8 @@
"Please use the following methods instead: \n" \ "Please use the following methods instead: \n" \
" - for `highlight_color` => `setHighlightColor(color)` \n" \ " - for `highlight_color` => `setHighlightColor(color)` \n" \
" - for `oneshot_color` => `setOneShotColor(color)` \n" \ " - for `oneshot_color` => `setOneShotColor(color)` \n" \
" - for `sticky_color` => `setStickyColor(color)`" " - for `sticky_color` => `setStickyColor(color)` \n" \
"These variables will be removed after 2021-08-01."
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {

@ -321,6 +321,11 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) {
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
// ----------------------------------------------------------------------------
EventHandlerResult OneShot::afterReportingState(const KeyEvent& event) {
return afterEachCycle();
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
EventHandlerResult OneShot::afterEachCycle() { EventHandlerResult OneShot::afterEachCycle() {

@ -26,33 +26,41 @@
// Deprecation warning messages // Deprecation warning messages
#define _DEPRECATED_MESSAGE_ONESHOT_TIMEOUT \ #define _DEPRECATED_MESSAGE_ONESHOT_TIMEOUT \
"The `OneShot.time_out` variable is deprecated. Please use the\n" \ "The `OneShot.time_out` variable is deprecated. Please use the\n" \
"`OneShot.setTimeout()` function instead." "`OneShot.setTimeout()` function instead.\n" \
"This variable will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_HOLD_TIMEOUT \ #define _DEPRECATED_MESSAGE_ONESHOT_HOLD_TIMEOUT \
"The `OneShot.hold_time_out` variable is deprecated. Please use the\n" \ "The `OneShot.hold_time_out` variable is deprecated. Please use the\n" \
"`OneShot.setHoldTimeout()` function instead." "`OneShot.setHoldTimeout()` function instead.\n" \
"This variable will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_DOUBLE_TAP_TIMEOUT \ #define _DEPRECATED_MESSAGE_ONESHOT_DOUBLE_TAP_TIMEOUT \
"The `OneShot.double_tap_time_out` variable is deprecated. Please use the\n" \ "The `OneShot.double_tap_time_out` variable is deprecated. Please use the\n" \
"`OneShot.setDoubleTapTimeout()` function instead." "`OneShot.setDoubleTapTimeout()` function instead.\n" \
"This variable will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_INJECT \ #define _DEPRECATED_MESSAGE_ONESHOT_INJECT \
"The `OneShot.inject(key, key_state)` function has been deprecated." "The `OneShot.inject(key, key_state)` function has been deprecated.\n" \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_ISACTIVE_KEY \ #define _DEPRECATED_MESSAGE_ONESHOT_ISACTIVE_KEY \
"The `OneShot.isActive(key)` function is deprecated. Please use\n" \ "The `OneShot.isActive(key)` function is deprecated. Please use\n" \
"`OneShot.isActive(key_addr)` instead, if possible." "`OneShot.isActive(key_addr)` instead, if possible.\n" \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_ISSTICKY_KEY \ #define _DEPRECATED_MESSAGE_ONESHOT_ISSTICKY_KEY \
"The `OneShot.isSticky(key)` function is deprecated. Please use\n" \ "The `OneShot.isSticky(key)` function is deprecated. Please use\n" \
"`OneShot.isSticky(key_addr)` instead, if possible." "`OneShot.isSticky(key_addr)` instead, if possible.\n" \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_ISPRESSED \ #define _DEPRECATED_MESSAGE_ONESHOT_ISPRESSED \
"The `OneShot.isPressed()` function is deprecated. This function now\n" \ "The `OneShot.isPressed()` function is deprecated. This function now\n" \
"always returns false." "always returns false.\n" \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ONESHOT_ISMODIFIERACTIVE \ #define _DEPRECATED_MESSAGE_ONESHOT_ISMODIFIERACTIVE \
"The `OneShot.isModifierActive()` function is deprecated." "The `OneShot.isModifierActive()` function is deprecated.\n" \
"This function will be removed after 2021-08-01."
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Keymap macros // Keymap macros
@ -222,6 +230,7 @@ class OneShot : public kaleidoscope::Plugin {
EventHandlerResult onNameQuery(); EventHandlerResult onNameQuery();
EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult onKeyEvent(KeyEvent &event);
EventHandlerResult afterReportingState(const KeyEvent &event);
EventHandlerResult afterEachCycle(); EventHandlerResult afterEachCycle();
private: private:

@ -133,6 +133,13 @@ properties:
> is disabled. This is useful for interfacing with other plugins or macros, > is disabled. This is useful for interfacing with other plugins or macros,
> especially where SpaceCadet functionality isn't always desired. > especially where SpaceCadet functionality isn't always desired.
### `.enableWithoutDelay()`
> This method enables the SpaceCadet plugin in "no-delay" mode. In this mode,
> SpaceCadet immediately sends the primary (modifier) value of the SpaceCadet
> key when it is pressed. If it is then released before timing out, it sends the
> alternate "tap" value, replacing the modifier.
### `Key_SpaceCadetEnable` ### `Key_SpaceCadetEnable`
> This provides a key for placing on a keymap for enabling the SpaceCadet > This provides a key for placing on a keymap for enabling the SpaceCadet

@ -46,7 +46,7 @@ uint16_t SpaceCadet::time_out = 200;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// State variables // State variables
bool SpaceCadet::disabled = false; uint8_t SpaceCadet::mode_ = SpaceCadet::Mode::ON;
// These variables are used to keep track of any pending unresolved SpaceCadet // These variables are used to keep track of any pending unresolved SpaceCadet
// key that has been pressed. If `pending_map_index_` is negative, it means // key that has been pressed. If `pending_map_index_` is negative, it means
@ -78,23 +78,6 @@ SpaceCadet::SpaceCadet() {
map = initialmap; map = initialmap;
} }
// -----------------------------------------------------------------------------
// Function to determine whether SpaceCadet is active (useful for Macros and
// other plugins).
bool SpaceCadet::active() {
return !disabled;
}
// Function to enable SpaceCadet behavior
void SpaceCadet::enable() {
disabled = false;
}
// Function to disable SpaceCadet behavior
void SpaceCadet::disable() {
disabled = true;
}
// ============================================================================= // =============================================================================
// Event handler hook functions // Event handler hook functions
@ -134,7 +117,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
} }
// Do nothing if disabled, but keep the event tracker current. // Do nothing if disabled, but keep the event tracker current.
if (disabled) if (mode_ == Mode::OFF)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (!event_queue_.isEmpty()) { if (!event_queue_.isEmpty()) {
@ -162,7 +145,13 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
// Check for a SpaceCadet key // Check for a SpaceCadet key
pending_map_index_ = getSpaceCadetKeyIndex(event.key); pending_map_index_ = getSpaceCadetKeyIndex(event.key);
if (pending_map_index_ >= 0) { if (pending_map_index_ >= 0) {
// A SpaceCadet key was pressed // A SpaceCadet key has just toggled on. First, if we're in no-delay mode,
// we need to send the event unchanged (with the primary `Key` value),
// bypassing other `onKeyswitchEvent()` handlers.
if (mode_ == Mode::NO_DELAY)
Runtime.handleKeyEvent(event);
// Queue the press event and abort; this press event will be resolved
// later.
event_queue_.append(event); event_queue_.append(event);
return EventHandlerResult::ABORT; return EventHandlerResult::ABORT;
} }
@ -211,6 +200,11 @@ void SpaceCadet::flushQueue() {
void SpaceCadet::flushEvent(bool is_tap) { void SpaceCadet::flushEvent(bool is_tap) {
KeyEvent event = event_queue_.event(0); KeyEvent event = event_queue_.event(0);
if (is_tap && pending_map_index_ >= 0) { if (is_tap && pending_map_index_ >= 0) {
// If we're in no-delay mode, we should first send the release of the
// modifier key as a courtesy before sending the tap event.
if (mode_ == Mode::NO_DELAY) {
Runtime.handleKeyEvent(KeyEvent(event.addr, WAS_PRESSED));
}
event.key = map[pending_map_index_].output; event.key = map[pending_map_index_].output;
} }
event_queue_.shift(); event_queue_.shift();

@ -60,9 +60,18 @@ class SpaceCadet : public kaleidoscope::Plugin {
SpaceCadet(void); SpaceCadet(void);
// Methods // Methods
static void enable(void); static void enable() {
static void disable(void); mode_ = Mode::ON;
static bool active(void); }
static void disable() {
mode_ = Mode::OFF;
}
static void enableWithoutDelay() {
mode_ = Mode::NO_DELAY;
}
static bool active() {
return (mode_ == Mode::ON || mode_ == Mode::NO_DELAY);
}
// Publically accessible variables // Publically accessible variables
static uint16_t time_out; // The global timeout in milliseconds static uint16_t time_out; // The global timeout in milliseconds
@ -73,7 +82,12 @@ class SpaceCadet : public kaleidoscope::Plugin {
EventHandlerResult afterEachCycle(); EventHandlerResult afterEachCycle();
private: private:
static bool disabled; enum Mode : uint8_t {
ON,
OFF,
NO_DELAY,
};
static uint8_t mode_;
static KeyEventTracker event_tracker_; static KeyEventTracker event_tracker_;

@ -169,8 +169,10 @@ __attribute__((weak)) const char keyToChar(Key key) {
switch (key.getKeyCode()) { switch (key.getKeyCode()) {
case Key_A.getKeyCode() ... Key_Z.getKeyCode(): case Key_A.getKeyCode() ... Key_Z.getKeyCode():
return 'a' + (key.getKeyCode() - Key_A.getKeyCode()); return 'a' + (key.getKeyCode() - Key_A.getKeyCode());
case Key_1.getKeyCode() ... Key_0.getKeyCode(): case Key_1.getKeyCode() ... Key_9.getKeyCode():
return '1' + (key.getKeyCode() - Key_1.getKeyCode()); return '1' + (key.getKeyCode() - Key_1.getKeyCode());
case Key_0.getKeyCode():
return '0';
} }
return 0; return 0;

@ -102,6 +102,11 @@ Runtime_::handleKeyswitchEvent(KeyEvent event) {
// When a key toggles off, set the event's key value to whatever the key's // When a key toggles off, set the event's key value to whatever the key's
// current value is in the live keys state array. // current value is in the live keys state array.
event.key = live_keys[event.addr]; event.key = live_keys[event.addr];
// If that key was masked, unmask it and return.
if (event.key == Key_Masked) {
live_keys.clear(event.addr);
return;
}
} else if (event.key == Key_NoKey) { } else if (event.key == Key_NoKey) {
// When a key toggles on, unless the event already has a key value (i.e. we // When a key toggles on, unless the event already has a key value (i.e. we
// were called by a plugin rather than `actOnMatrixScan()`), we look up the // were called by a plugin rather than `actOnMatrixScan()`), we look up the
@ -214,6 +219,11 @@ Runtime_::handleKeyEvent(KeyEvent event) {
// Finally, send the new keyboard report // Finally, send the new keyboard report
sendKeyboardReport(event); sendKeyboardReport(event);
// Now that the report has been sent, let plugins act on it after the fact.
// This is useful for plugins that need to react to an event, but must wait
// until after that event is processed to do so.
Hooks::afterReportingState(event);
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

@ -23,6 +23,7 @@
#include "kaleidoscope/device/Base.h" #include "kaleidoscope/device/Base.h"
#include "kaleidoscope/driver/mcu/ATmega32U4.h" #include "kaleidoscope/driver/mcu/ATmega32U4.h"
#include "kaleidoscope/driver/hid/Keyboardio.h"
#include "kaleidoscope/driver/keyscanner/ATmega.h" #include "kaleidoscope/driver/keyscanner/ATmega.h"
#include "kaleidoscope/driver/storage/ATmega32U4EEPROMProps.h" #include "kaleidoscope/driver/storage/ATmega32U4EEPROMProps.h"
#include "kaleidoscope/driver/storage/AVREEPROM.h" #include "kaleidoscope/driver/storage/AVREEPROM.h"
@ -31,6 +32,8 @@ namespace kaleidoscope {
namespace device { namespace device {
struct ATmega32U4KeyboardProps : kaleidoscope::device::BaseProps { struct ATmega32U4KeyboardProps : kaleidoscope::device::BaseProps {
typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps;
typedef kaleidoscope::driver::hid::Keyboardio<HIDProps> HID;
typedef kaleidoscope::driver::mcu::ATmega32U4Props MCUProps; typedef kaleidoscope::driver::mcu::ATmega32U4Props MCUProps;
typedef kaleidoscope::driver::mcu::ATmega32U4<MCUProps> MCU; typedef kaleidoscope::driver::mcu::ATmega32U4<MCUProps> MCU;
typedef kaleidoscope::driver::storage::ATmega32U4EEPROMProps StorageProps; typedef kaleidoscope::driver::storage::ATmega32U4EEPROMProps StorageProps;

@ -25,7 +25,7 @@
#include "kaleidoscope_internal/deprecations.h" #include "kaleidoscope_internal/deprecations.h"
#include "kaleidoscope/macro_helpers.h" #include "kaleidoscope/macro_helpers.h"
#include "kaleidoscope/driver/hid/Keyboardio.h" #include "kaleidoscope/driver/hid/Base.h"
#include "kaleidoscope/driver/keyscanner/None.h" #include "kaleidoscope/driver/keyscanner/None.h"
#include "kaleidoscope/driver/led/None.h" #include "kaleidoscope/driver/led/None.h"
#include "kaleidoscope/driver/mcu/None.h" #include "kaleidoscope/driver/mcu/None.h"
@ -53,8 +53,8 @@ namespace kaleidoscope {
namespace device { namespace device {
struct BaseProps { struct BaseProps {
typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps; typedef kaleidoscope::driver::hid::BaseProps HIDProps;
typedef kaleidoscope::driver::hid::Keyboardio<HIDProps> HID; typedef kaleidoscope::driver::hid::Base<HIDProps> HID;
typedef kaleidoscope::driver::keyscanner::BaseProps KeyScannerProps; typedef kaleidoscope::driver::keyscanner::BaseProps KeyScannerProps;
typedef kaleidoscope::driver::keyscanner::None KeyScanner; typedef kaleidoscope::driver::keyscanner::None KeyScanner;
typedef kaleidoscope::driver::led::BaseProps LEDDriverProps; typedef kaleidoscope::driver::led::BaseProps LEDDriverProps;

@ -22,6 +22,7 @@
#include KALEIDOSCOPE_HARDWARE_H #include KALEIDOSCOPE_HARDWARE_H
#include "kaleidoscope/driver/bootloader/None.h" #include "kaleidoscope/driver/bootloader/None.h"
#include "kaleidoscope/driver/hid/Keyboardio.h"
#include "kaleidoscope/driver/led/Base.h" #include "kaleidoscope/driver/led/Base.h"
namespace kaleidoscope { namespace kaleidoscope {
@ -110,6 +111,8 @@ class VirtualLEDDriver
// the physical keyboard. // the physical keyboard.
// //
struct VirtualProps : public kaleidoscope::DeviceProps { struct VirtualProps : public kaleidoscope::DeviceProps {
typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps;
typedef kaleidoscope::driver::hid::Keyboardio<HIDProps> HID;
typedef typename kaleidoscope::DeviceProps::KeyScannerProps typedef typename kaleidoscope::DeviceProps::KeyScannerProps
KeyScannerProps; KeyScannerProps;
typedef VirtualKeyScanner typedef VirtualKeyScanner

@ -20,6 +20,10 @@
#include "kaleidoscope/key_defs.h" #include "kaleidoscope/key_defs.h"
#ifndef HID_BOOT_PROTOCOL
#define HID_BOOT_PROTOCOL 0
#endif
namespace kaleidoscope { namespace kaleidoscope {
namespace driver { namespace driver {
namespace hid { namespace hid {
@ -54,6 +58,9 @@ class NoBootKeyboard {
bool wasAnyModifierActive() { bool wasAnyModifierActive() {
return false; return false;
} }
bool isKeyPressed(uint8_t code) {
return false;
}
uint8_t getLeds() { uint8_t getLeds() {
return 0; return 0;
@ -83,6 +90,9 @@ class NoNKROKeyboard {
bool wasAnyModifierActive() { bool wasAnyModifierActive() {
return false; return false;
} }
bool isKeyPressed(uint8_t code) {
return false;
}
uint8_t getLeds() { uint8_t getLeds() {
return 0; return 0;

@ -30,14 +30,11 @@ class NoMouse {
void begin() {} void begin() {}
void sendReport() {} void sendReport() {}
void move(int8_t x, int8_t y, int8_t vWheel, int8_t hWheel) {} void move(int8_t x, int8_t y, int8_t vWheel, int8_t hWheel) {}
void stop(bool x, bool y, bool vWheel, bool hWheel) {}
void releaseAll() {} void releaseAll() {}
void press(uint8_t buttons) {} void press(uint8_t buttons) {}
void release(uint8_t buttons) {} void release(uint8_t buttons) {}
void click(uint8_t buttons) {} void click(uint8_t buttons) {}
HID_MouseReport_Data_t getReport() {
static HID_MouseReport_Data_t report;
return report;
}
}; };
struct MouseProps { struct MouseProps {
@ -63,17 +60,7 @@ class Mouse {
mouse_.move(x, y, vWheel, hWheel); mouse_.move(x, y, vWheel, hWheel);
} }
void stop(bool x, bool y, bool vWheel = false, bool hWheel = false) { void stop(bool x, bool y, bool vWheel = false, bool hWheel = false) {
HID_MouseReport_Data_t report = mouse_.getReport(); mouse_.stop(x, y, vWheel, hWheel);
if (x)
report.xAxis = 0;
if (y)
report.yAxis = 0;
if (vWheel)
report.vWheel = 0;
if (hWheel)
report.hWheel = 0;
move(report.xAxis, report.yAxis, report.vWheel, report.hWheel);
} }
void releaseAllButtons() { void releaseAllButtons() {
mouse_.releaseAll(); mouse_.releaseAll();

@ -50,6 +50,20 @@ class MouseWrapper {
void move(int8_t x, int8_t y, int8_t vWheel, int8_t hWheel) { void move(int8_t x, int8_t y, int8_t vWheel, int8_t hWheel) {
Mouse.move(x, y, vWheel, hWheel); Mouse.move(x, y, vWheel, hWheel);
} }
void stop(bool x, bool y, bool vWheel = false, bool hWheel = false) {
HID_MouseReport_Data_t report = Mouse.getReport();
if (x)
report.xAxis = 0;
if (y)
report.yAxis = 0;
if (vWheel)
report.vWheel = 0;
if (hWheel)
report.hWheel = 0;
move(report.xAxis, report.yAxis, report.vWheel, report.hWheel);
}
void releaseAll() { void releaseAll() {
Mouse.releaseAll(); Mouse.releaseAll();
} }
@ -62,9 +76,6 @@ class MouseWrapper {
void click(uint8_t buttons) { void click(uint8_t buttons) {
Mouse.click(buttons); Mouse.click(buttons);
} }
HID_MouseReport_Data_t getReport() {
return Mouse.getReport();
}
}; };
struct MouseProps: public base::MouseProps { struct MouseProps: public base::MouseProps {

@ -264,6 +264,19 @@ class SignatureCheckDummy {};
(const KeyEvent &event), __NL__ \ (const KeyEvent &event), __NL__ \
(event), ##__VA_ARGS__) __NL__ \ (event), ##__VA_ARGS__) __NL__ \
__NL__ \ __NL__ \
/* Called after reporting our state to the host. This is the last */ __NL__ \
/* point at which a plugin can do something in response to an event */ __NL__ \
/* before the next event is processed, if multiple events occur in */ __NL__ \
/* and are processed in a single cycle (usually due to delayed */ __NL__ \
/* events or generated events). */ __NL__ \
OPERATION(afterReportingState, __NL__ \
1, __NL__ \
_CURRENT_IMPLEMENTATION, __NL__ \
_NOT_ABORTABLE, __NL__ \
(),(),(), /* non template */ __NL__ \
(const KeyEvent &event), __NL__ \
(event), ##__VA_ARGS__) __NL__ \
__NL__ \
/* Called at the very end of a cycle, after everything's */ __NL__ \ /* Called at the very end of a cycle, after everything's */ __NL__ \
/* said and done. */ __NL__ \ /* said and done. */ __NL__ \
OPERATION(afterEachCycle, __NL__ \ OPERATION(afterEachCycle, __NL__ \
@ -351,6 +364,10 @@ class SignatureCheckDummy {};
OP(beforeReportingState, 2) __NL__ \ OP(beforeReportingState, 2) __NL__ \
END(beforeReportingState, 1, 2) __NL__ \ END(beforeReportingState, 1, 2) __NL__ \
__NL__ \ __NL__ \
START(afterReportingState, 1) __NL__ \
OP(afterReportingState, 1) __NL__ \
END(afterReportingState, 1) __NL__ \
__NL__ \
START(afterEachCycle, 1) __NL__ \ START(afterEachCycle, 1) __NL__ \
OP(afterEachCycle, 1) __NL__ \ OP(afterEachCycle, 1) __NL__ \
END(afterEachCycle, 1) __NL__ \ END(afterEachCycle, 1) __NL__ \

@ -195,7 +195,7 @@ class Key {
// modifier keys like `LSHIFT(Key_RightAlt)` and `Key_Meh`. It will not match // modifier keys like `LSHIFT(Key_RightAlt)` and `Key_Meh`. It will not match
// a key with only modifier flags (e.g. `LCTRL(RALT(Key_NoKey))`); this is an // a key with only modifier flags (e.g. `LCTRL(RALT(Key_NoKey))`); this is an
// intentional feature so that plugins can distinguish between the two. // intentional feature so that plugins can distinguish between the two.
constexpr bool isKeyboardModifier() const { constexpr bool __attribute__((always_inline)) isKeyboardModifier() const {
return (isKeyboardKey() && return (isKeyboardKey() &&
(keyCode_ >= HID_KEYBOARD_FIRST_MODIFIER && (keyCode_ >= HID_KEYBOARD_FIRST_MODIFIER &&
keyCode_ <= HID_KEYBOARD_LAST_MODIFIER)); keyCode_ <= HID_KEYBOARD_LAST_MODIFIER));
@ -221,7 +221,7 @@ class Key {
// they are used chorded to change the result of typing those other // they are used chorded to change the result of typing those other
// keys. They're even more similar to `shift` keys. For both reasons, it's // keys. They're even more similar to `shift` keys. For both reasons, it's
// worth singling them out. // worth singling them out.
constexpr bool isLayerShift() const { constexpr bool __attribute__((always_inline)) isLayerShift() const {
return (isLayerKey() && return (isLayerKey() &&
keyCode_ >= LAYER_SHIFT_OFFSET && keyCode_ >= LAYER_SHIFT_OFFSET &&
keyCode_ < LAYER_MOVE_OFFSET); keyCode_ < LAYER_MOVE_OFFSET);

@ -21,10 +21,8 @@
#include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/LiveKeys.h"
// The maximum number of layers allowed. `layer_state_`, which stores // The maximum number of layers allowed.
// the on/off status of the layers in a bitfield has only 32 bits, and #define MAX_LAYERS 32;
// that should be enough for almost any layout.
#define MAX_LAYERS sizeof(uint32_t) * 8;
// The following definitions of layer_count and keymaps_linear // The following definitions of layer_count and keymaps_linear
// are used if the user does not define a keymap within the sketch // are used if the user does not define a keymap within the sketch
@ -41,7 +39,6 @@ __attribute__((weak))
extern constexpr Key keymaps_linear[][kaleidoscope_internal::device.matrix_rows * kaleidoscope_internal::device.matrix_columns] = {}; extern constexpr Key keymaps_linear[][kaleidoscope_internal::device.matrix_rows * kaleidoscope_internal::device.matrix_columns] = {};
namespace kaleidoscope { namespace kaleidoscope {
uint32_t Layer_::layer_state_;
uint8_t Layer_::active_layer_count_ = 1; uint8_t Layer_::active_layer_count_ = 1;
int8_t Layer_::active_layers_[31]; int8_t Layer_::active_layers_[31];
@ -49,9 +46,6 @@ uint8_t Layer_::active_layer_keymap_[kaleidoscope_internal::device.numKeys()];
Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM; Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM;
void Layer_::setup() { void Layer_::setup() {
// Explicitly set layer 0's state to 1
bitSet(layer_state_, 0);
// Update the active layer cache (every entry will be `0` to start) // Update the active layer cache (every entry will be `0` to start)
Layer.updateActiveLayers(); Layer.updateActiveLayers();
} }
@ -181,12 +175,9 @@ void Layer_::move(uint8_t layer) {
// We do pretty much what activate() does, except we do everything // We do pretty much what activate() does, except we do everything
// unconditionally, to make sure all parts of the firmware are aware of the // unconditionally, to make sure all parts of the firmware are aware of the
// layer change. // layer change.
layer_state_ = 0;
if (layer >= layer_count) { if (layer >= layer_count) {
layer = 0; layer = 0;
} }
bitSet(layer_state_, layer);
active_layer_count_ = 1; active_layer_count_ = 1;
active_layers_[0] = layer; active_layers_[0] = layer;
@ -206,9 +197,7 @@ void Layer_::activate(uint8_t layer) {
if (isActive(layer)) if (isActive(layer))
return; return;
// Otherwise, turn on its bit in layer_state_, and push it onto the active // Otherwise, push it onto the active layer stack
// layer stack
bitSet(layer_state_, layer);
active_layers_[active_layer_count_++] = layer; active_layers_[active_layer_count_++] = layer;
// Update the keymap cache (but not live_composite_keymap_; that gets // Update the keymap cache (but not live_composite_keymap_; that gets
@ -231,9 +220,6 @@ void Layer_::deactivate(uint8_t layer) {
return; return;
} }
// Turn off its bit in layer_state_
bitClear(layer_state_, layer);
// Remove the target layer from the active layer stack, and shift any layers // Remove the target layer from the active layer stack, and shift any layers
// above it down to fill in the gap // above it down to fill in the gap
for (uint8_t i = 0; i < active_layer_count_; ++i) { for (uint8_t i = 0; i < active_layer_count_; ++i) {
@ -252,7 +238,11 @@ void Layer_::deactivate(uint8_t layer) {
} }
boolean Layer_::isActive(uint8_t layer) { boolean Layer_::isActive(uint8_t layer) {
return bitRead(layer_state_, layer); for (int8_t i = 0; i < active_layer_count_; ++i) {
if (active_layers_[i] == layer)
return true;
}
return false;
} }
void Layer_::activateNext(void) { void Layer_::activateNext(void) {

@ -146,7 +146,6 @@ class Layer_ {
static void forEachActiveLayer(forEachHandler h); static void forEachActiveLayer(forEachHandler h);
private: private:
static uint32_t layer_state_;
static uint8_t active_layer_count_; static uint8_t active_layer_count_;
static int8_t active_layers_[31]; static int8_t active_layers_[31];
static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()];

@ -23,7 +23,7 @@
#include <stddef.h> #include <stddef.h>
#ifdef KALEIDOSCOPE_VIRTUAL_BUILD #if defined(KALEIDOSCOPE_VIRTUAL_BUILD) || defined(ARDUINO_ARCH_STM32)
#include <new> #include <new>
#else #else

@ -16,6 +16,8 @@
#pragma once #pragma once
#include "kaleidoscope/macro_helpers.h"
#define DEPRECATED(tag) \ #define DEPRECATED(tag) \
__attribute__((deprecated(_DEPRECATE(_DEPRECATED_MESSAGE_ ## tag)))) __attribute__((deprecated(_DEPRECATE(_DEPRECATED_MESSAGE_ ## tag))))
@ -33,33 +35,39 @@
"'active keys' cache, which now represents the state of the active\n" __NL__ \ "'active keys' cache, which now represents the state of the active\n" __NL__ \
"keys at any given time. It is probably not necessary to directly\n" __NL__ \ "keys at any given time. It is probably not necessary to directly\n" __NL__ \
"update that cache from a plugin, but if you need to, please use\n" __NL__ \ "update that cache from a plugin, but if you need to, please use\n" __NL__ \
"the `live_keys.activate(key_addr, key)` function instead." "the `live_keys.activate(key_addr, key)` function instead.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \ #define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \
"`Layer.eventHandler()` is deprecated.\n" __NL__ \ "`Layer.eventHandler()` is deprecated.\n" __NL__ \
"Please use `Layer.handleKeymapKeyswitchEvent()` instead." "Please use `Layer.handleKeymapKeyswitchEvent()` instead.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_LAYER_HANDLE_KEYMAP_KEYSWITCH_EVENT __NL__ \ #define _DEPRECATED_MESSAGE_LAYER_HANDLE_KEYMAP_KEYSWITCH_EVENT __NL__ \
"`Layer.handleKeymapKeyswitchEvent()` is deprecated.\n" __NL__ \ "`Layer.handleKeymapKeyswitchEvent()` is deprecated.\n" __NL__ \
"Please use `Layer.handleLayerKeyEvent()` instead." "Please use `Layer.handleLayerKeyEvent()` instead.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_LAYER_LOOKUP __NL__ \ #define _DEPRECATED_MESSAGE_LAYER_LOOKUP __NL__ \
"`Layer.lookup(key_addr)` is deprecated.\n" __NL__ \ "`Layer.lookup(key_addr)` is deprecated.\n" __NL__ \
"Please use `Runtime.lookupKey(key_addr)` instead. Alternatively, if you\n" __NL__ \ "Please use `Runtime.lookupKey(key_addr)` instead. Alternatively, if you\n" __NL__ \
"need to look up the current keymap entry without regard to current live\n" __NL__ \ "need to look up the current keymap entry without regard to current live\n" __NL__ \
"key state(s) (i.e. the `live_keys` array, which normally overrides the\n" __NL__ \ "key state(s) (i.e. the `live_keys` array, which normally overrides the\n" __NL__ \
"keymap), you can use `Layer.lookupOnActiveLayer(key_addr)`." "keymap), you can use `Layer.lookupOnActiveLayer(key_addr)`.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_HANDLE_KEYSWITCH_EVENT __NL__ \ #define _DEPRECATED_MESSAGE_HANDLE_KEYSWITCH_EVENT __NL__ \
"`handleKeyswitchEvent()` has been deprecated.\n" __NL__ \ "`handleKeyswitchEvent()` has been deprecated.\n" __NL__ \
"Please use `Runtime.handleKeyEvent()` instead." "Please use `Runtime.handleKeyEvent()` instead.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_ON_KEYSWITCH_EVENT_V1 __NL__ \ #define _DEPRECATED_MESSAGE_ON_KEYSWITCH_EVENT_V1 __NL__ \
"The `onKeyswitchEvent()` event handler is deprecated.\n" __NL__ \ "The `onKeyswitchEvent()` event handler is deprecated.\n" __NL__ \
"Please replace it with an `onKeyEvent()` handler. See the documentation\n" __NL__ \ "Please replace it with an `onKeyEvent()` handler. See the documentation\n" __NL__ \
"in UPGRADING.md and docs/api-reference/event-handler-hooks.md for more\n" __NL__ \ "in UPGRADING.md and docs/api-reference/event-handler-hooks.md for more\n" __NL__ \
"information on what changes are needed to adapt old plugins to the new\n" __NL__ \ "information on what changes are needed to adapt old plugins to the new\n" __NL__ \
"event handler API." "event handler API.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_BEFORE_REPORTING_STATE_V1 __NL__ \ #define _DEPRECATED_MESSAGE_BEFORE_REPORTING_STATE_V1 __NL__ \
"This `beforeReportingState()` event handler version is deprecated.\n" __NL__ \ "This `beforeReportingState()` event handler version is deprecated.\n" __NL__ \
@ -68,11 +76,13 @@
"report is sent. However, the new handler does not run every cycle, but\n" __NL__ \ "report is sent. However, the new handler does not run every cycle, but\n" __NL__ \
"only in response to key events. If you have code that is intended to run\n" __NL__ \ "only in response to key events. If you have code that is intended to run\n" __NL__ \
"every scan cycle, it should be moved to the `afterEachCycle()` event\n" __NL__ \ "every scan cycle, it should be moved to the `afterEachCycle()` event\n" __NL__ \
"handler instead." "handler instead.\n" __NL__ \
"This function will be removed after 2021-08-01."
#define _DEPRECATED_MESSAGE_HID_KEYBOARD_PRESSKEY_TOGGLEDON __NL__ \ #define _DEPRECATED_MESSAGE_HID_KEYBOARD_PRESSKEY_TOGGLEDON __NL__ \
"The `Keyboard::pressKey(key, toggled_on)` function is deprecated.\n" __NL__ \ "The `Keyboard::pressKey(key, toggled_on)` function is deprecated.\n" __NL__ \
"Please use `Keyboard::pressKey(key)` without the second argument\n" __NL__ \ "Please use `Keyboard::pressKey(key)` without the second argument\n" __NL__ \
"instead. The version with two arguments handled rollover events, and\n" __NL__ \ "instead. The version with two arguments handled rollover events, and\n" __NL__ \
"this is now handled more completely by the event handling functions in\n" __NL__ \ "this is now handled more completely by the event handling functions in\n" __NL__ \
"`Runtime`." "`Runtime`.\n" __NL__ \
"This function will be removed after 2021-08-01."

@ -352,14 +352,14 @@ sub generate_press {
my $e = shift; my $e = shift;
# TODO handle multuple presses # TODO handle multuple presses
cxx( "PressKey(key_addr_" . $e->{data}->{switch} . ");", $e->{comment} ); cxx( "PressKey(key_addr_" . $e->{data}->{switch} . "); // ", $e->{comment} );
} }
sub generate_release { sub generate_release {
my $e = shift; my $e = shift;
# TODO handle multiple releases # TODO handle multiple releases
cxx( "ReleaseKey(key_addr_" . $e->{data}->{switch} . ");", $e->{comment} ); cxx( "ReleaseKey(key_addr_" . $e->{data}->{switch} . "); // ", $e->{comment} );
} }
sub generate_expect_report { sub generate_expect_report {

@ -134,6 +134,12 @@ TEST_F(Issue1010, RangesHaveNotChanged) {
uint16_t(kaleidoscope::ranges::DYNAMIC_MACRO_FIRST)); uint16_t(kaleidoscope::ranges::DYNAMIC_MACRO_FIRST));
ASSERT_EQ(uint16_t(Issue1010::DYNAMIC_MACRO_LAST), ASSERT_EQ(uint16_t(Issue1010::DYNAMIC_MACRO_LAST),
uint16_t(kaleidoscope::ranges::DYNAMIC_MACRO_LAST)); uint16_t(kaleidoscope::ranges::DYNAMIC_MACRO_LAST));
ASSERT_EQ(uint16_t(Issue1010::OS_META_STICKY),
uint16_t(kaleidoscope::ranges::OS_META_STICKY));
ASSERT_EQ(uint16_t(Issue1010::OS_ACTIVE_STICKY),
uint16_t(kaleidoscope::ranges::OS_ACTIVE_STICKY));
ASSERT_EQ(uint16_t(Issue1010::OS_CANCEL),
uint16_t(kaleidoscope::ranges::OS_CANCEL));
ASSERT_EQ(uint16_t(Issue1010::SAFE_START), ASSERT_EQ(uint16_t(Issue1010::SAFE_START),
uint16_t(kaleidoscope::ranges::SAFE_START)); uint16_t(kaleidoscope::ranges::SAFE_START));
ASSERT_EQ(uint16_t(Issue1010::KALEIDOSCOPE_SAFE_START), ASSERT_EQ(uint16_t(Issue1010::KALEIDOSCOPE_SAFE_START),

@ -0,0 +1,69 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Syster.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
SYSTER, ___, ___, ___, ___, ___, ___,
Key_A, Key_B, Key_C, ___, ___, ___, ___,
Key_0, Key_1, ___, ___, ___, ___,
Key_Spacebar, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
void systerAction(kaleidoscope::plugin::Syster::action_t action, const char *symbol) {
switch (action) {
case kaleidoscope::plugin::Syster::StartAction:
break;
case kaleidoscope::plugin::Syster::EndAction:
break;
case kaleidoscope::plugin::Syster::SymbolAction:
if (strcmp(symbol, "abc") == 0) {
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | IS_PRESSED, Key_X));
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | WAS_PRESSED, Key_X));
}
if (strcmp(symbol, "a0") == 0) {
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | IS_PRESSED, Key_Y));
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | WAS_PRESSED, Key_Y));
}
break;
}
}
KALEIDOSCOPE_INIT_PLUGINS(Syster);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,115 @@
VERSION 1
KEYSWITCH SYSTER 0 0
KEYSWITCH A 1 0
KEYSWITCH B 1 1
KEYSWITCH C 1 2
KEYSWITCH ZERO 2 0
KEYSWITCH ONE 2 1
KEYSWITCH SPACE 3 0
# ==============================================================================
NAME Syster sequence without zero
RUN 4 ms
PRESS SYSTER
RUN 1 cycle
RUN 4 ms
RELEASE SYSTER
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # The report should contain only `A`
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
PRESS B
RUN 1 cycle
EXPECT keyboard-report Key_B # The report should contain only `B`
RUN 4 ms
RELEASE B
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
PRESS C
RUN 1 cycle
EXPECT keyboard-report Key_C # The report should contain only `C`
RUN 4 ms
RELEASE C
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
PRESS SPACE
RUN 1 cycle
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
EXPECT keyboard-report empty # The report should be empty
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
EXPECT keyboard-report empty # The report should be empty
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
EXPECT keyboard-report empty # The report should be empty
EXPECT keyboard-report Key_X # The report should contain `X`
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
RELEASE SPACE
RUN 1 cycle
RUN 10 ms
# ==============================================================================
NAME Syster sequence with zero
RUN 4 ms
PRESS SYSTER
RUN 1 cycle
RUN 4 ms
RELEASE SYSTER
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A # The report should contain only `A`
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
PRESS ZERO
RUN 1 cycle
EXPECT keyboard-report Key_0 # The report should contain only `0`
RUN 4 ms
RELEASE ZERO
RUN 1 cycle
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
PRESS SPACE
RUN 1 cycle
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
EXPECT keyboard-report empty # The report should be empty
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
EXPECT keyboard-report empty # The report should be empty
EXPECT keyboard-report Key_Y # The report should contain `Y`
EXPECT keyboard-report empty # The report should be empty
RUN 4 ms
RELEASE SPACE
RUN 1 cycle
RUN 10 ms

@ -0,0 +1,147 @@
/* -*- mode: c++ -*-
* Kaleidoscope-Leader -- VIM-style leader keys
* Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Leader.h>
#include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-Ranges.h>
#include "kaleidoscope/KeyEventTracker.h"
#include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope/plugin.h"
#include <Kaleidoscope-Devel-ArduinoTrace.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(LEAD(0), Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
LEAD(0),
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
LEAD(0)),
)
// *INDENT-ON*
namespace kaleidoscope {
namespace plugin {
class LeaderPrefix : public Plugin {
public:
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
// DUMP(int(event.id()));
// DUMP(int(event.key.getRaw()));
if (event_tracker_.shouldIgnore(event))
return EventHandlerResult::OK;
if (!active_) {
if (keyToggledOn(event.state) && isLeaderKey(event.key)) {
active_ = true;
leader_arg_ = 0;
}
return EventHandlerResult::OK;
}
if (keyToggledOn(event.state)) {
for (uint8_t i{0}; i < 10; ++i) {
if (digit_keys_[i] == event.key) {
leader_arg_ *= 10;
leader_arg_ += i;
live_keys.mask(event.addr);
return EventHandlerResult::ABORT;
}
}
// DUMP(active_);
active_ = false;
}
if (keyToggledOff(event.state) && event.key == Key_Masked)
return EventHandlerResult::ABORT;
return EventHandlerResult::OK;
}
EventHandlerResult afterEachCycle() {
// int timestamp = Runtime.millisAtCycleStart();
// DUMP(timestamp);
return EventHandlerResult::OK;
}
uint16_t arg() const {
return leader_arg_;
}
private:
Key digit_keys_[10] = {
Key_P, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Y, Key_U, Key_I, Key_O,
};
KeyEventTracker event_tracker_;
bool active_{false};
uint16_t leader_arg_{0};
bool isLeaderKey(Key key) {
return (key >= ranges::LEAD_FIRST && key <= ranges::LEAD_LAST);
}
};
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::LeaderPrefix LeaderPrefix;
auto &serial_port = Kaleidoscope.serialPort();
static void leaderTestX(uint8_t seq_index) {
Macros.tap(Key_A);
}
static void leaderTestXX(uint8_t seq_index) {
Macros.tap(Key_B);
}
void leaderTestPrefix(uint8_t seq_index) {
uint8_t prefix_arg = LeaderPrefix.arg();
// DUMP(prefix_arg);
Macros.tap(Key_Y);
while (prefix_arg-- > 0)
Macros.tap(Key_X);
}
static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM =
LEADER_DICT({LEADER_SEQ(LEAD(0), Key_X), leaderTestX},
{LEADER_SEQ(LEAD(0), Key_X, Key_X), leaderTestXX},
{LEADER_SEQ(LEAD(0), Key_Z), leaderTestPrefix});
KALEIDOSCOPE_INIT_PLUGINS(LeaderPrefix, Leader);
void setup() {
Kaleidoscope.setup();
Leader.dictionary = leader_dictionary;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:avr:model01",
"port": ""
}
}

@ -0,0 +1,82 @@
VERSION 1
KEYSWITCH LEAD 0 0
KEYSWITCH Q 1 1
KEYSWITCH W 1 2
KEYSWITCH E 1 3
KEYSWITCH R 1 4
KEYSWITCH T 1 5
KEYSWITCH Z 3 1
# ==============================================================================
NAME Leader prefix sequence
RUN 4 ms
PRESS LEAD
RUN 1 cycle
RUN 4 ms
RELEASE LEAD
RUN 1 cycle
RUN 4 ms
PRESS Q
RUN 1 cycle
RUN 4 ms
RELEASE Q
RUN 1 cycle
RUN 4 ms
PRESS W
RUN 1 cycle
RUN 4 ms
RELEASE W
RUN 1 cycle
RUN 4 ms
PRESS Z
RUN 1 cycle
EXPECT keyboard-report Key_Y
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
EXPECT keyboard-report Key_X
EXPECT keyboard-report empty
RUN 4 ms
RELEASE Z
RUN 1 cycle
RUN 4 ms
PRESS W
RUN 1 cycle
EXPECT keyboard-report Key_W
RUN 4 ms
RELEASE W
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms

@ -0,0 +1,86 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Qukeys.h>
#include <Kaleidoscope-OneShot.h>
#include "./common.h"
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
OSL(1), Key_1, Key_2, Key_3, Key_4, Key_5, XXX,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
Key_Q,
XXX, OSM(LeftGui), LSHIFT(Key_LeftShift), LSHIFT(Key_RightShift), Key_9, Key_0, XXX,
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
Key_H, SFT_T(J), CTL_T(K), ALT_T(L), GUI_T(Semicolon), Key_Quote,
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
LT(1,E)
),
[1] = KEYMAP_STACKED
(
___, SFT_T(A), Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_1, Key_2, Key_3, Key_4,
___,
___, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
Key_1, Key_2, Key_3, Key_4,
___
),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, OneShot);
void setup() {
QUKEYS(
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 1), Key_LeftGui), // A/cmd
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 2), Key_LeftAlt), // S/alt
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 3), Key_LeftControl), // D/ctrl
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 4), Key_LeftShift), // F/shift
kaleidoscope::plugin::Qukey(0, KeyAddr(3, 6), ShiftToLayer(1)) // Q/layer-shift (on `fn`)
)
Qukeys.setHoldTimeout(kaleidoscope::testing::QUKEYS_HOLD_TIMEOUT);
Qukeys.setOverlapThreshold(kaleidoscope::testing::QUKEYS_OVERLAP_THRESHOLD);
Qukeys.setMinimumHoldTime(kaleidoscope::testing::QUKEYS_MINIMUM_HOLD_TIME);
Qukeys.setMinimumPriorInterval(kaleidoscope::testing::QUKEYS_MIN_PRIOR_INTERVAL);
Qukeys.setMaxIntervalForTapRepeat(kaleidoscope::testing::QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT);
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,33 @@
// -*- mode: c++ -*-
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <cstdint>
namespace kaleidoscope {
namespace testing {
constexpr uint16_t QUKEYS_HOLD_TIMEOUT = 200;
constexpr uint8_t QUKEYS_OVERLAP_THRESHOLD = 0;
constexpr uint8_t QUKEYS_MINIMUM_HOLD_TIME = 0;
constexpr uint8_t QUKEYS_MIN_PRIOR_INTERVAL = 0;
constexpr uint8_t QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT = 0;
} // namespace testing
} // namespace kaleidoscope

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,124 @@
VERSION 1
KEYSWITCH OSL 0 0 # 0: OSL(1)
KEYSWITCH QK 0 1 # 1: SFT_T(A)
KEYSWITCH C 0 2 # 1: Key_C
KEYSWITCH A 2 1 # 0: Key_A, Qukey(Key_LeftGui)
KEYSWITCH H 2 10 # 0: Key_H
KEYSWITCH Y 1 10 # 0: Key_Y
KEYSWITCH K 2 12 # 0: CTL_T(K)
KEYSWITCH OSG 0 10 # 0: OSM(LeftGui)
KEYSWITCH LS 0 11 # 0: LSHIFT(Key_LeftShift)
KEYSWITCH RS 0 12 # 0: LSHIFT(Key_RightShift)
# ==============================================================================
NAME Chrysalis 566 and 605
RUN 4 ms
PRESS OSL # OSL(1)
RUN 1 cycle
RUN 4 ms
RELEASE OSL
RUN 1 cycle
RUN 4 ms
PRESS QK # SFT_T(A)
RUN 1 cycle
RUN 4 ms
PRESS C # 1: Key_C
RUN 1 cycle
RUN 4 ms
RELEASE C
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift
EXPECT keyboard-report Key_LeftShift Key_C
EXPECT keyboard-report Key_LeftShift
RUN 4 ms
RELEASE QK
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME Chrysalis 688
# plain key press
RUN 4 ms
PRESS H # Key_H
RUN 1 cycle
EXPECT keyboard-report Key_H
# qukey press
RUN 4 ms
PRESS K # CTL_T(K)
RUN 1 cycle
# plain key release
RUN 9 ms
RELEASE H
RUN 1 cycle
# There should be no report here
# plain key press
RUN 4 ms
PRESS Y # Key_Y
RUN 1 cycle
# plain key release
RUN 4 ms
RELEASE Y
RUN 1 cycle
# This event resolves the qukey's state and flushes the queue
EXPECT keyboard-report Key_H Key_LeftControl
EXPECT keyboard-report Key_LeftControl
EXPECT keyboard-report Key_LeftControl Key_Y
EXPECT keyboard-report Key_LeftControl
# qukey release
RUN 4 ms
RELEASE K
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME Chrysalis 427 workaround
RUN 4 ms
PRESS OSG # OSM(LeftGui)
RUN 1 cycle
EXPECT keyboard-report Key_LeftGui
RUN 4 ms
RELEASE OSG
RUN 1 cycle
RUN 4 ms
PRESS LS # LSHIFT(Key_LeftShift)
RUN 1 cycle
EXPECT keyboard-report Key_LeftGui Key_LeftShift
RUN 4 ms
PRESS H
RUN 1 cycle
EXPECT keyboard-report Key_LeftGui Key_LeftShift Key_H
EXPECT keyboard-report Key_LeftShift Key_H
RUN 4 ms
RELEASE H
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift
RUN 4 ms
RELEASE LS
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms

@ -0,0 +1,78 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-Macros.h>
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-TapDance.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_Spacebar, Key_A, ___, ___, ___, ___, ___,
TD(0), OSM(LeftShift), ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
void tapDanceAction(uint8_t tap_dance_index,
KeyAddr key_addr,
uint8_t tap_count,
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
switch (tap_dance_index) {
case 0:
return tapDanceActionKeys(tap_count, tap_dance_action,
Key_Period, M(0), LSHIFT(Key_1));
default:
break;
}
}
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
if (keyToggledOn(event.state)) {
switch (macro_id) {
case 0:
Macros.type(PSTR("abc"));
break;
default:
break;
}
}
return MACRO_NONE;
}
KALEIDOSCOPE_INIT_PLUGINS(Macros, OneShot, TapDance);
void setup() {
Kaleidoscope.setup();
TapDance.time_out = 25;
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,177 @@
VERSION 1
KEYSWITCH SPACE 0 0
KEYSWITCH A 0 1
KEYSWITCH TD_0 1 0
KEYSWITCH OS_SHIFT 1 1
# ==============================================================================
NAME Back and forth
RUN 4 ms
PRESS TD_0
RUN 1 cycle
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
RUN 4 ms
PRESS OS_SHIFT
RUN 1 cycle
EXPECT keyboard-report Key_Period # report: { 37 }
EXPECT keyboard-report empty
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
RELEASE OS_SHIFT
RUN 1 cycle
RUN 4 ms
PRESS TD_0
RUN 1 cycle
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
# ------------------------------------------------------------------------------
# Next, we press `space`, triggering both the resolution of the TapDance key and
# the release of the OneShot key, in that order.
RUN 4 ms
PRESS SPACE
RUN 1 cycle
# First, the TapDance key is resolved, adding `.` to the report. This event also
# triggers the release of the OneShot key, which shouldn't happen until after
# the `.` press is processed.
EXPECT keyboard-report Key_LeftShift Key_Period # report: { 37 e1 }
# Now the OneShot key is released, removing `shift` from the report.
EXPECT keyboard-report Key_Period # report: { 37 }
# The TapDance `.` key has been released, so its release comes next.
EXPECT keyboard-report empty # report: { }
# Finally, we get the report for the press of the `space` key.
EXPECT keyboard-report Key_Spacebar # report: { 2c }
RUN 4 ms
RELEASE SPACE
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS OS_SHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
RELEASE OS_SHIFT
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
EXPECT keyboard-report Key_A # report: { 4 }
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME Single rollover
RUN 4 ms
PRESS TD_0
RUN 1 cycle
RUN 4 ms
PRESS SPACE
RUN 1 cycle
EXPECT keyboard-report Key_Period # report: { 37 }
EXPECT keyboard-report Key_Period Key_Spacebar # report: { 37 2c }
RUN 4 ms
PRESS OS_SHIFT
RUN 1 cycle
EXPECT keyboard-report Key_Period Key_Spacebar Key_LeftShift # report: { 2c 37 e1 }
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
EXPECT keyboard-report Key_Spacebar Key_LeftShift # report: { 2c e1 }
RUN 4 ms
RELEASE SPACE
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
RUN 4 ms
RELEASE OS_SHIFT
RUN 1 cycle
EXPECT keyboard-report Key_A # report: { 4 }
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OSM applies to whole Macro
RUN 4 ms
PRESS OS_SHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
RELEASE OS_SHIFT
RUN 1 cycle
RUN 4 ms
PRESS TD_0
RUN 1 cycle
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
RUN 4 ms
PRESS TD_0
RUN 1 cycle
RUN 4 ms
RELEASE TD_0
RUN 1 cycle
# ------------------------------------------------------------------------------
# Next, we press `space`, triggering both the resolution of the TapDance key and
# the release of the OneShot key, in that order.
RUN 4 ms
PRESS SPACE
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
EXPECT keyboard-report Key_LeftShift # report: { e1 }
EXPECT keyboard-report Key_LeftShift Key_B # report: { 5 e1 }
EXPECT keyboard-report Key_LeftShift # report: { e1 }
EXPECT keyboard-report Key_LeftShift Key_C # report: { 6 e1 }
EXPECT keyboard-report Key_LeftShift # report: { e1 }
EXPECT keyboard-report empty # report: { }
EXPECT keyboard-report Key_Spacebar # report: { 2c }
RUN 4 ms
RELEASE SPACE
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms

@ -0,0 +1,50 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-Escape-OneShot.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
OSM(LeftShift), ___, ___, ___, ___, ___, ___,
Key_A, Key_B, ___, ___, ___, ___, ___,
Key_Escape, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(OneShot, EscapeOneShot);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,62 @@
VERSION 1
KEYSWITCH OSM 0 0
KEYSWITCH A 1 0
KEYSWITCH B 1 1
KEYSWITCH ESC 2 0
# ==============================================================================
NAME Escape OneShot modifier
RUN 4 ms
PRESS OSM
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
RELEASE OSM
RUN 1 cycle
RUN 4 ms
PRESS ESC
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 4 ms
RELEASE ESC
RUN 1 cycle
RUN 5 ms
# ==============================================================================
NAME Escape sticky OneShot modifier
RUN 4 ms
PRESS OSM
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
RUN 4 ms
RELEASE OSM
RUN 1 cycle
RUN 4 ms
PRESS OSM
RUN 1 cycle
RUN 4 ms
RELEASE OSM
RUN 1 cycle
RUN 5000 ms
RUN 4 ms
PRESS ESC
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 4 ms
RELEASE ESC
RUN 1 cycle
RUN 5 ms

@ -0,0 +1,50 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-AutoShift.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_LeftShift, Key_RightShift, ___, ___, ___, ___, ___,
Key_A, Key_B, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(AutoShift);
void setup() {
Kaleidoscope.setup();
AutoShift.setTimeout(20);
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,41 @@
VERSION 1
KEYSWITCH LSHIFT 0 0
KEYSWITCH RSHIFT 0 1
KEYSWITCH A 1 0
KEYSWITCH B 1 1
# ==============================================================================
NAME AutoShift tap
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_A # report: { 4 }
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME AutoShift long press
RUN 4 ms
PRESS A
RUN 1 cycle
# Timeout is 20ms
RUN 20 ms
EXPECT keyboard-report Key_LeftShift # report: { e1 }
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
EXPECT keyboard-report empty
RUN 5 ms

@ -0,0 +1,68 @@
/* -*- mode: c++ -*-
* Copyright (C) 2021 Keyboard.io, Inc.
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-SpaceCadet.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_LeftShift, Key_RightShift, ___, ___, ___, ___, ___,
Key_A, Key_B, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet);
void setup() {
Kaleidoscope.setup();
//Set the SpaceCadet map
//Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS}
//Note: must end with the SPACECADET_MAP_END delimiter
static kaleidoscope::plugin::SpaceCadet::KeyBinding spacecadetmap[] = {
{Key_LeftShift, Key_X, 10},
{Key_RightShift, Key_Y, 0},
{Key_LeftGui, Key_LeftCurlyBracket, 10},
{Key_RightAlt, Key_RightCurlyBracket, 10},
{Key_LeftAlt, Key_RightCurlyBracket, 10},
{Key_LeftControl, Key_LeftBracket, 10},
{Key_RightControl, Key_RightBracket, 10},
SPACECADET_MAP_END
};
//Set the map.
SpaceCadet.map = spacecadetmap;
SpaceCadet.time_out = 20;
SpaceCadet.enableWithoutDelay();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -0,0 +1,141 @@
VERSION 1
KEYSWITCH LSHIFT 0 0
KEYSWITCH RSHIFT 0 1
KEYSWITCH A 1 0
KEYSWITCH B 1 1
# ==============================================================================
NAME SpaceCadet tap
RUN 4 ms
PRESS LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
RUN 4 ms
RELEASE LSHIFT
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
EXPECT keyboard-report Key_X # Report should contain `X` (0x1B)
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# ==============================================================================
NAME SpaceCadet hold
RUN 4 ms
PRESS LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
RUN 10 ms # timeout = 10 ms (for this key)
RELEASE LSHIFT
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# ==============================================================================
NAME SpaceCadet hold with global timeout
RUN 4 ms
PRESS RSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5)
RUN 20 ms # timeout = 20 ms
RELEASE RSHIFT
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# ==============================================================================
NAME SpaceCadet interrupt
RUN 4 ms
PRESS LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_A # Report should add `A` (0x04, 0xE1)
RUN 4 ms
RELEASE LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_A # Report should contain only `A` (0x04)
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# ==============================================================================
NAME SpaceCadet interrupt SpaceCadet with tap
RUN 4 ms
PRESS LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
RUN 4 ms
PRESS RSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_RightShift # Report should add `shift` (0xE5)
RUN 4 ms
RELEASE RSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
EXPECT keyboard-report Key_LeftShift Key_Y # Report should add `Y` (0x1C)
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
RUN 4 ms
RELEASE LSHIFT
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
# ==============================================================================
NAME SpaceCadet interrupt SpaceCadet with hold
# First, press left shift. It takes effect immediately, because SpaceCadet is in
# "no-delay" mode.
RUN 4 ms
PRESS LSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift # report: { e1 }
# Before left shift times out (timeout=10ms), press right shift, which also
# takes effect without delay.
RUN 4 ms
PRESS RSHIFT
RUN 1 cycle
EXPECT keyboard-report Key_LeftShift Key_RightShift # report: { e1 e5 }
# Next, release left shift after it times out (so it's not a "tap"), but before
# the right shift times out. This does not generate a report, because the right
# shift might still become a "tap" if it's released soon enough.
RUN 10 ms
RELEASE LSHIFT
# Next, the right shift times out, resolving to its modifier state. This allows
# the left shift to be released, because the right shift can't be a "tap".
RUN 10 ms
EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5)
# Last, release the right shift as normal.
RUN 4 ms
RELEASE RSHIFT
RUN 1 cycle
EXPECT keyboard-report empty # Report should be empty
RUN 5 ms
Loading…
Cancel
Save