diff --git a/docs/NEWS.md b/docs/NEWS.md index b00e3872..c28fefdd 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,13 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## 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 #### Tap-repeat @@ -208,6 +215,10 @@ To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base ## 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 The [DynamicMacros](plugins/Kaleidoscope-DynamicMacros.md) plugin provides a way to use and update macros via the Focus API, through Chrysalis. diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index ae3963a7..49b21c38 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -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) + [New features](#new-features) + - [New event handler](#new-event-handler) - [Event-driven main loop](#event-driven-main-loop) - [Keyboard state array](#keyboard-state-array) - [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 event handler + +One more `KeyEvent` handler has been added: `afterReportingState(const KeyEvent &event)`. This handler gets called after HID reports are sent for an event, providing a point for plugins to act after an event has been fully processed by `Runtime.handleKeyEvent()`. + ### Event-driven main loop Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events. diff --git a/docs/api-reference/device-apis.md b/docs/api-reference/device-apis.md index 33128647..bdbabd05 100644 --- a/docs/api-reference/device-apis.md +++ b/docs/api-reference/device-apis.md @@ -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 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 -an `ATMega32U4` MCU we also have the -[`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4] class, which sets up -some of the components that is common to all `ATMega32U4`-based devices (such as +an `ATmega32U4` MCU we also have the +[`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4] class, which sets up +some of the components that is common to all `ATmega32U4`-based devices (such as the _MCU_ and the _Storage_). - [k:d:Base]:../src/kaleidoscope/device/Base.h - [k:d:a32u4]: ../src/kaleidoscope/device/ATMega32U4.h + [k:d:Base]: ../../src/kaleidoscope/device/Base.h + [k:d:a32u4]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h 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, @@ -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]. - [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 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 [`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:a32u4]: ../src/kaleidoscope/driver/mcu/ATMega32U4.h + [k:d:m:Base]: ../../src/kaleidoscope/driver/mcu/Base.h + [k:d:m:a32u4]: ../../src/kaleidoscope/driver/mcu/ATmega32U4.h The core firmware will use the `detachFromHost()` and `attachToHost()` methods 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. -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 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 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 them for more information. In many cases, setting up the bootloader in the device props is all one needs to do. - [k:d:b:Catalina]: ../src/kaleidoscope/driver/bootloader/avr/Catalina.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:Caterina]: ../../src/kaleidoscope/driver/bootloader/avr/Caterina.h + [k:d:b:HalfKay]: ../../src/kaleidoscope/driver/bootloader/avr/HalfKay.h + [k:d:b:FLIP]: ../../src/kaleidoscope/driver/bootloader/avr/FLIP.h 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 [`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 - [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 -size - of it. We provide an [`ATMega32U4EEPROMProps`][k:d:s:a32u4props] helper, -which is preconfigured for the 1k EEPROM size of the ATMega32U4. +size - of it. We provide an [`ATmega32U4EEPROMProps`][k:d:s:a32u4props] helper, +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 [`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 [`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 -[`kaleidoscope::device::ATMega32U4Keyboard`][k:d:a32u4k] -[`kaleidoscope::driver::keyscanner::AVR`][k:d:ks:avr] +[`kaleidoscope::device::ATmega32U4Keyboard`][k:d:a32u4k] +[`kaleidoscope::driver::keyscanner::ATmega`][k:d:ks:atm] - [k:d:a32u4k]: ../src/kaleidoscope/device/ATMega32U4Keyboard.h - [k:d:ks:avr]: ../src/kaleidoscope/driver/keyscanner/AVR.h + [k:d:a32u4k]: ../../src/kaleidoscope/device/ATmega32U4Keyboard.h + [k:d:ks:atm]: ../../src/kaleidoscope/driver/keyscanner/ATmega.h ## Putting it all together 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. ### `ImaginaryKeypad.h` @@ -157,9 +157,9 @@ four keys only. #ifdef ARDUINO_AVR_IMAGINARY_KEYPAD #include -#include "kaleidoscope/driver/keyscanner/AVR.h" +#include "kaleidoscope/driver/keyscanner/ATmega.h" #include "kaleidoscope/driver/bootloader/avr/Caterina.h" -#include "kaleidoscope/device/ATMega32U4Keyboard.h" +#include "kaleidoscope/device/ATmega32U4Keyboard.h" namespace kaleidoscope { namespace device { diff --git a/docs/api-reference/event-handler-hooks.md b/docs/api-reference/event-handler-hooks.md index b86f0837..707c67ed 100644 --- a/docs/api-reference/event-handler-hooks.md +++ b/docs/api-reference/event-handler-hooks.md @@ -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 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 ### `onLayerChange()` diff --git a/docs/customization/plugin-authors-guide.md b/docs/customization/plugin-authors-guide.md new file mode 100644 index 00000000..579a31ca --- /dev/null +++ b/docs/customization/plugin-authors-guide.md @@ -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 diff --git a/examples/Keystrokes/AutoShift/AutoShift.ino b/examples/Keystrokes/AutoShift/AutoShift.ino new file mode 100644 index 00000000..4cc2ce88 --- /dev/null +++ b/examples/Keystrokes/AutoShift/AutoShift.ino @@ -0,0 +1,75 @@ +// -*- mode: c++ -*- + +#include + +#include +#include +#include +#include +#include + +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(); +} diff --git a/examples/Keystrokes/AutoShift/sketch.json b/examples/Keystrokes/AutoShift/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/AutoShift/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/examples/Keystrokes/LeaderPrefix/LeaderPrefix.ino b/examples/Keystrokes/LeaderPrefix/LeaderPrefix.ino new file mode 100644 index 00000000..6de00bf6 --- /dev/null +++ b/examples/Keystrokes/LeaderPrefix/LeaderPrefix.ino @@ -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 . + */ + +#include +#include +#include + +#include +#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(); +} diff --git a/examples/Keystrokes/LeaderPrefix/sketch.json b/examples/Keystrokes/LeaderPrefix/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/LeaderPrefix/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/examples/Keystrokes/Qukeys/Qukeys.ino b/examples/Keystrokes/Qukeys/Qukeys.ino index da74acdd..bee8538e 100644 --- a/examples/Keystrokes/Qukeys/Qukeys.ino +++ b/examples/Keystrokes/Qukeys/Qukeys.ino @@ -6,6 +6,11 @@ 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* KEYMAPS( [0] = KEYMAP_STACKED @@ -63,6 +68,9 @@ const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { KALEIDOSCOPE_INIT_PLUGINS(Qukeys, Macros); 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( kaleidoscope::plugin::Qukey(0, KeyAddr(2, 1), Key_LeftGui), // A/cmd kaleidoscope::plugin::Qukey(0, KeyAddr(2, 2), Key_LeftAlt), // S/alt diff --git a/plugins/Kaleidoscope-AutoShift/README.md b/plugins/Kaleidoscope-AutoShift/README.md new file mode 100644 index 00000000..650c23ce --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/README.md @@ -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 +#include + +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 diff --git a/plugins/Kaleidoscope-AutoShift/library.properties b/plugins/Kaleidoscope-AutoShift/library.properties new file mode 100644 index 00000000..3fe6f349 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/library.properties @@ -0,0 +1,7 @@ +name=Kaleidoscope-AutoShift +version=0.0.0 +sentence=Automatic shift on long press +maintainer=Kaleidoscope's Developers +url=https://github.com/keyboardio/Kaleidoscope +author=Michael Richters +paragraph= diff --git a/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h b/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h new file mode 100644 index 00000000..3a7adee3 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/Kaleidoscope-AutoShift.h @@ -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 . + */ + +#pragma once + +#include diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp new file mode 100644 index 00000000..2c0c4df4 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.cpp @@ -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 . + */ + +#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; diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h new file mode 100644 index 00000000..98749997 --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShift.h @@ -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 . + */ + +#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_; + + 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; diff --git a/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp new file mode 100644 index 00000000..90f57dcc --- /dev/null +++ b/plugins/Kaleidoscope-AutoShift/src/kaleidoscope/plugin/AutoShiftConfig.cpp @@ -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 . + */ + +#include "kaleidoscope/plugin/AutoShift.h" + +#include +#include + +#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; diff --git a/plugins/Kaleidoscope-Hardware-Dygma-Raise/src/kaleidoscope/device/dygma/Raise.h b/plugins/Kaleidoscope-Hardware-Dygma-Raise/src/kaleidoscope/device/dygma/Raise.h index 80f1e5ed..28beb92b 100644 --- a/plugins/Kaleidoscope-Hardware-Dygma-Raise/src/kaleidoscope/device/dygma/Raise.h +++ b/plugins/Kaleidoscope-Hardware-Dygma-Raise/src/kaleidoscope/device/dygma/Raise.h @@ -26,6 +26,7 @@ #define CRGB(r,g,b) (cRGB){b, g, r} #include "kaleidoscope/driver/keyscanner/Base.h" +#include "kaleidoscope/driver/hid/Keyboardio.h" #include "kaleidoscope/driver/led/Base.h" #include "kaleidoscope/driver/bootloader/samd/Bossac.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 RaiseProps : kaleidoscope::device::BaseProps { + typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps; + typedef kaleidoscope::driver::hid::Keyboardio HID; typedef RaiseLEDDriverProps LEDDriverProps; typedef RaiseLEDDriver LEDDriver; typedef RaiseKeyScannerProps KeyScannerProps; diff --git a/plugins/Kaleidoscope-HostPowerManagement/src/kaleidoscope/plugin/HostPowerManagement.h b/plugins/Kaleidoscope-HostPowerManagement/src/kaleidoscope/plugin/HostPowerManagement.h index 3d2ebe09..6b8b9676 100644 --- a/plugins/Kaleidoscope-HostPowerManagement/src/kaleidoscope/plugin/HostPowerManagement.h +++ b/plugins/Kaleidoscope-HostPowerManagement/src/kaleidoscope/plugin/HostPowerManagement.h @@ -22,7 +22,8 @@ #define _DEPRECATED_MESSAGE_ENABLEWAKEUP \ "The HostPowerManagement.enableWakeup() call is not necessary anymore,\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 plugin { diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h index bed341be..3e5124b1 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h @@ -29,7 +29,8 @@ "Please use the following methods instead: \n" \ " - for `highlight_color` => `setHighlightColor(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 plugin { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 46234785..63b6b851 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -321,6 +321,11 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { return EventHandlerResult::OK; } +// ---------------------------------------------------------------------------- +EventHandlerResult OneShot::afterReportingState(const KeyEvent& event) { + return afterEachCycle(); +} + // ---------------------------------------------------------------------------- EventHandlerResult OneShot::afterEachCycle() { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 5ad18b1d..6072a1ed 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -26,33 +26,41 @@ // Deprecation warning messages #define _DEPRECATED_MESSAGE_ONESHOT_TIMEOUT \ "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 \ "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 \ "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 \ - "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 \ "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 \ "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 \ "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 \ - "The `OneShot.isModifierActive()` function is deprecated." + "The `OneShot.isModifierActive()` function is deprecated.\n" \ + "This function will be removed after 2021-08-01." // ---------------------------------------------------------------------------- // Keymap macros @@ -222,6 +230,7 @@ class OneShot : public kaleidoscope::Plugin { EventHandlerResult onNameQuery(); EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult afterReportingState(const KeyEvent &event); EventHandlerResult afterEachCycle(); private: diff --git a/plugins/Kaleidoscope-SpaceCadet/README.md b/plugins/Kaleidoscope-SpaceCadet/README.md index 4b6a0574..bae8ea0c 100644 --- a/plugins/Kaleidoscope-SpaceCadet/README.md +++ b/plugins/Kaleidoscope-SpaceCadet/README.md @@ -133,6 +133,13 @@ properties: > is disabled. This is useful for interfacing with other plugins or macros, > 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` > This provides a key for placing on a keymap for enabling the SpaceCadet diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index 6135c320..b3fd2edb 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -46,7 +46,7 @@ uint16_t SpaceCadet::time_out = 200; // ----------------------------------------------------------------------------- // 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 // key that has been pressed. If `pending_map_index_` is negative, it means @@ -78,23 +78,6 @@ SpaceCadet::SpaceCadet() { 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 @@ -134,7 +117,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { } // Do nothing if disabled, but keep the event tracker current. - if (disabled) + if (mode_ == Mode::OFF) return EventHandlerResult::OK; if (!event_queue_.isEmpty()) { @@ -162,7 +145,13 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { // Check for a SpaceCadet key pending_map_index_ = getSpaceCadetKeyIndex(event.key); 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); return EventHandlerResult::ABORT; } @@ -211,6 +200,11 @@ void SpaceCadet::flushQueue() { void SpaceCadet::flushEvent(bool is_tap) { KeyEvent event = event_queue_.event(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_queue_.shift(); diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 971bb540..3365d755 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -60,9 +60,18 @@ class SpaceCadet : public kaleidoscope::Plugin { SpaceCadet(void); // Methods - static void enable(void); - static void disable(void); - static bool active(void); + static void enable() { + mode_ = Mode::ON; + } + 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 static uint16_t time_out; // The global timeout in milliseconds @@ -73,7 +82,12 @@ class SpaceCadet : public kaleidoscope::Plugin { EventHandlerResult afterEachCycle(); private: - static bool disabled; + enum Mode : uint8_t { + ON, + OFF, + NO_DELAY, + }; + static uint8_t mode_; static KeyEventTracker event_tracker_; diff --git a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp index 3eb487c6..48278f24 100644 --- a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp +++ b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp @@ -169,8 +169,10 @@ __attribute__((weak)) const char keyToChar(Key key) { switch (key.getKeyCode()) { case Key_A.getKeyCode() ... Key_Z.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()); + case Key_0.getKeyCode(): + return '0'; } return 0; diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index 6d534cbe..82b0df83 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -102,6 +102,11 @@ Runtime_::handleKeyswitchEvent(KeyEvent event) { // 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. 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) { // 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 @@ -214,6 +219,11 @@ Runtime_::handleKeyEvent(KeyEvent event) { // Finally, send the new keyboard report 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); } // ---------------------------------------------------------------------------- diff --git a/src/kaleidoscope/device/ATmega32U4Keyboard.h b/src/kaleidoscope/device/ATmega32U4Keyboard.h index b9ae02ff..f710c12d 100644 --- a/src/kaleidoscope/device/ATmega32U4Keyboard.h +++ b/src/kaleidoscope/device/ATmega32U4Keyboard.h @@ -23,6 +23,7 @@ #include "kaleidoscope/device/Base.h" #include "kaleidoscope/driver/mcu/ATmega32U4.h" +#include "kaleidoscope/driver/hid/Keyboardio.h" #include "kaleidoscope/driver/keyscanner/ATmega.h" #include "kaleidoscope/driver/storage/ATmega32U4EEPROMProps.h" #include "kaleidoscope/driver/storage/AVREEPROM.h" @@ -31,6 +32,8 @@ namespace kaleidoscope { namespace device { struct ATmega32U4KeyboardProps : kaleidoscope::device::BaseProps { + typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps; + typedef kaleidoscope::driver::hid::Keyboardio HID; typedef kaleidoscope::driver::mcu::ATmega32U4Props MCUProps; typedef kaleidoscope::driver::mcu::ATmega32U4 MCU; typedef kaleidoscope::driver::storage::ATmega32U4EEPROMProps StorageProps; diff --git a/src/kaleidoscope/device/Base.h b/src/kaleidoscope/device/Base.h index 5841d780..826a1cb1 100644 --- a/src/kaleidoscope/device/Base.h +++ b/src/kaleidoscope/device/Base.h @@ -25,7 +25,7 @@ #include "kaleidoscope_internal/deprecations.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/led/None.h" #include "kaleidoscope/driver/mcu/None.h" @@ -53,8 +53,8 @@ namespace kaleidoscope { namespace device { struct BaseProps { - typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps; - typedef kaleidoscope::driver::hid::Keyboardio HID; + typedef kaleidoscope::driver::hid::BaseProps HIDProps; + typedef kaleidoscope::driver::hid::Base HID; typedef kaleidoscope::driver::keyscanner::BaseProps KeyScannerProps; typedef kaleidoscope::driver::keyscanner::None KeyScanner; typedef kaleidoscope::driver::led::BaseProps LEDDriverProps; diff --git a/src/kaleidoscope/device/virtual/Virtual.h b/src/kaleidoscope/device/virtual/Virtual.h index 9bc576fc..a6ea731f 100644 --- a/src/kaleidoscope/device/virtual/Virtual.h +++ b/src/kaleidoscope/device/virtual/Virtual.h @@ -22,6 +22,7 @@ #include KALEIDOSCOPE_HARDWARE_H #include "kaleidoscope/driver/bootloader/None.h" +#include "kaleidoscope/driver/hid/Keyboardio.h" #include "kaleidoscope/driver/led/Base.h" namespace kaleidoscope { @@ -110,6 +111,8 @@ class VirtualLEDDriver // the physical keyboard. // struct VirtualProps : public kaleidoscope::DeviceProps { + typedef kaleidoscope::driver::hid::KeyboardioProps HIDProps; + typedef kaleidoscope::driver::hid::Keyboardio HID; typedef typename kaleidoscope::DeviceProps::KeyScannerProps KeyScannerProps; typedef VirtualKeyScanner diff --git a/src/kaleidoscope/driver/hid/base/Keyboard.h b/src/kaleidoscope/driver/hid/base/Keyboard.h index 0b2a02cb..8363ea6b 100644 --- a/src/kaleidoscope/driver/hid/base/Keyboard.h +++ b/src/kaleidoscope/driver/hid/base/Keyboard.h @@ -20,6 +20,10 @@ #include "kaleidoscope/key_defs.h" +#ifndef HID_BOOT_PROTOCOL +#define HID_BOOT_PROTOCOL 0 +#endif + namespace kaleidoscope { namespace driver { namespace hid { @@ -54,6 +58,9 @@ class NoBootKeyboard { bool wasAnyModifierActive() { return false; } + bool isKeyPressed(uint8_t code) { + return false; + } uint8_t getLeds() { return 0; @@ -83,6 +90,9 @@ class NoNKROKeyboard { bool wasAnyModifierActive() { return false; } + bool isKeyPressed(uint8_t code) { + return false; + } uint8_t getLeds() { return 0; diff --git a/src/kaleidoscope/driver/hid/base/Mouse.h b/src/kaleidoscope/driver/hid/base/Mouse.h index 4ff40d8f..ebd71d3c 100644 --- a/src/kaleidoscope/driver/hid/base/Mouse.h +++ b/src/kaleidoscope/driver/hid/base/Mouse.h @@ -30,14 +30,11 @@ class NoMouse { void begin() {} void sendReport() {} 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 press(uint8_t buttons) {} void release(uint8_t buttons) {} void click(uint8_t buttons) {} - HID_MouseReport_Data_t getReport() { - static HID_MouseReport_Data_t report; - return report; - } }; struct MouseProps { @@ -63,17 +60,7 @@ class Mouse { 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); + mouse_.stop(x, y, vWheel, hWheel); } void releaseAllButtons() { mouse_.releaseAll(); diff --git a/src/kaleidoscope/driver/hid/keyboardio/Mouse.h b/src/kaleidoscope/driver/hid/keyboardio/Mouse.h index c9328368..f8773cd5 100644 --- a/src/kaleidoscope/driver/hid/keyboardio/Mouse.h +++ b/src/kaleidoscope/driver/hid/keyboardio/Mouse.h @@ -50,6 +50,20 @@ class MouseWrapper { void move(int8_t x, int8_t y, int8_t vWheel, int8_t 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() { Mouse.releaseAll(); } @@ -62,9 +76,6 @@ class MouseWrapper { void click(uint8_t buttons) { Mouse.click(buttons); } - HID_MouseReport_Data_t getReport() { - return Mouse.getReport(); - } }; struct MouseProps: public base::MouseProps { diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index b3765601..11ca03e9 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -264,6 +264,19 @@ class SignatureCheckDummy {}; (const KeyEvent &event), __NL__ \ (event), ##__VA_ARGS__) __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__ \ /* said and done. */ __NL__ \ OPERATION(afterEachCycle, __NL__ \ @@ -351,6 +364,10 @@ class SignatureCheckDummy {}; OP(beforeReportingState, 2) __NL__ \ END(beforeReportingState, 1, 2) __NL__ \ __NL__ \ + START(afterReportingState, 1) __NL__ \ + OP(afterReportingState, 1) __NL__ \ + END(afterReportingState, 1) __NL__ \ + __NL__ \ START(afterEachCycle, 1) __NL__ \ OP(afterEachCycle, 1) __NL__ \ END(afterEachCycle, 1) __NL__ \ diff --git a/src/kaleidoscope/key_defs.h b/src/kaleidoscope/key_defs.h index ef8358f3..4d2a3c14 100644 --- a/src/kaleidoscope/key_defs.h +++ b/src/kaleidoscope/key_defs.h @@ -195,7 +195,7 @@ class Key { // 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 // intentional feature so that plugins can distinguish between the two. - constexpr bool isKeyboardModifier() const { + constexpr bool __attribute__((always_inline)) isKeyboardModifier() const { return (isKeyboardKey() && (keyCode_ >= HID_KEYBOARD_FIRST_MODIFIER && keyCode_ <= HID_KEYBOARD_LAST_MODIFIER)); @@ -221,7 +221,7 @@ class Key { // 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 // worth singling them out. - constexpr bool isLayerShift() const { + constexpr bool __attribute__((always_inline)) isLayerShift() const { return (isLayerKey() && keyCode_ >= LAYER_SHIFT_OFFSET && keyCode_ < LAYER_MOVE_OFFSET); diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index 0f9b492a..68676e61 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -21,10 +21,8 @@ #include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/LiveKeys.h" -// The maximum number of layers allowed. `layer_state_`, which stores -// the on/off status of the layers in a bitfield has only 32 bits, and -// that should be enough for almost any layout. -#define MAX_LAYERS sizeof(uint32_t) * 8; +// The maximum number of layers allowed. +#define MAX_LAYERS 32; // The following definitions of layer_count and keymaps_linear // 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] = {}; namespace kaleidoscope { -uint32_t Layer_::layer_state_; uint8_t Layer_::active_layer_count_ = 1; 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; 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) Layer.updateActiveLayers(); } @@ -181,12 +175,9 @@ void Layer_::move(uint8_t layer) { // We do pretty much what activate() does, except we do everything // unconditionally, to make sure all parts of the firmware are aware of the // layer change. - layer_state_ = 0; - if (layer >= layer_count) { layer = 0; } - bitSet(layer_state_, layer); active_layer_count_ = 1; active_layers_[0] = layer; @@ -206,9 +197,7 @@ void Layer_::activate(uint8_t layer) { if (isActive(layer)) return; - // Otherwise, turn on its bit in layer_state_, and push it onto the active - // layer stack - bitSet(layer_state_, layer); + // Otherwise, push it onto the active layer stack active_layers_[active_layer_count_++] = layer; // Update the keymap cache (but not live_composite_keymap_; that gets @@ -231,9 +220,6 @@ void Layer_::deactivate(uint8_t layer) { 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 // above it down to fill in the gap 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) { - 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) { diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index 0aa995a1..3f38cdd2 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -146,7 +146,6 @@ class Layer_ { static void forEachActiveLayer(forEachHandler h); private: - static uint32_t layer_state_; static uint8_t active_layer_count_; static int8_t active_layers_[31]; static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; diff --git a/src/kaleidoscope_internal/LEDModeManager.h b/src/kaleidoscope_internal/LEDModeManager.h index 28ea5f49..835ffca6 100644 --- a/src/kaleidoscope_internal/LEDModeManager.h +++ b/src/kaleidoscope_internal/LEDModeManager.h @@ -23,7 +23,7 @@ #include -#ifdef KALEIDOSCOPE_VIRTUAL_BUILD +#if defined(KALEIDOSCOPE_VIRTUAL_BUILD) || defined(ARDUINO_ARCH_STM32) #include #else diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index c3b8b8c1..09748cd9 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -16,6 +16,8 @@ #pragma once +#include "kaleidoscope/macro_helpers.h" + #define DEPRECATED(tag) \ __attribute__((deprecated(_DEPRECATE(_DEPRECATED_MESSAGE_ ## tag)))) @@ -27,39 +29,45 @@ /* Messages */ -#define _DEPRECATED_MESSAGE_LAYER_UPDATELIVECOMPOSITEKEYMAP __NL__ \ - "`Layer.updateLiveCompositeKeymap()` is deprecated.\n" __NL__ \ - "The 'live composite keymap' cache has been replaced with the\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__ \ - "update that cache from a plugin, but if you need to, please use\n" __NL__ \ - "the `live_keys.activate(key_addr, key)` function instead." +#define _DEPRECATED_MESSAGE_LAYER_UPDATELIVECOMPOSITEKEYMAP __NL__ \ + "`Layer.updateLiveCompositeKeymap()` is deprecated.\n" __NL__ \ + "The 'live composite keymap' cache has been replaced with the\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__ \ + "update that cache from a plugin, but if you need to, please use\n" __NL__ \ + "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__ \ - "`Layer.eventHandler()` is deprecated.\n" __NL__ \ - "Please use `Layer.handleKeymapKeyswitchEvent()` instead." +#define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \ + "`Layer.eventHandler()` is deprecated.\n" __NL__ \ + "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__ \ - "`Layer.handleKeymapKeyswitchEvent()` is deprecated.\n" __NL__ \ - "Please use `Layer.handleLayerKeyEvent()` instead." +#define _DEPRECATED_MESSAGE_LAYER_HANDLE_KEYMAP_KEYSWITCH_EVENT __NL__ \ + "`Layer.handleKeymapKeyswitchEvent()` is deprecated.\n" __NL__ \ + "Please use `Layer.handleLayerKeyEvent()` instead.\n" __NL__ \ + "This function will be removed after 2021-08-01." #define _DEPRECATED_MESSAGE_LAYER_LOOKUP __NL__ \ "`Layer.lookup(key_addr)` is deprecated.\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__ \ "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__ \ - "`handleKeyswitchEvent()` has been deprecated.\n" __NL__ \ - "Please use `Runtime.handleKeyEvent()` instead." +#define _DEPRECATED_MESSAGE_HANDLE_KEYSWITCH_EVENT __NL__ \ + "`handleKeyswitchEvent()` has been deprecated.\n" __NL__ \ + "Please use `Runtime.handleKeyEvent()` instead.\n" __NL__ \ + "This function will be removed after 2021-08-01." #define _DEPRECATED_MESSAGE_ON_KEYSWITCH_EVENT_V1 __NL__ \ "The `onKeyswitchEvent()` event handler is deprecated.\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__ \ "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__ \ "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__ \ "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__ \ - "handler instead." + "handler instead.\n" __NL__ \ + "This function will be removed after 2021-08-01." #define _DEPRECATED_MESSAGE_HID_KEYBOARD_PRESSKEY_TOGGLEDON __NL__ \ "The `Keyboard::pressKey(key, toggled_on)` function is deprecated.\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__ \ "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." diff --git a/testing/bin/ktest-to-cxx b/testing/bin/ktest-to-cxx index 3b7d6f0e..77a5bbe4 100644 --- a/testing/bin/ktest-to-cxx +++ b/testing/bin/ktest-to-cxx @@ -352,14 +352,14 @@ sub generate_press { my $e = shift; # 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 { my $e = shift; # 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 { diff --git a/tests/issues/1010/test/testcase.cpp b/tests/issues/1010/test/testcase.cpp index 8d35b679..a4874eda 100644 --- a/tests/issues/1010/test/testcase.cpp +++ b/tests/issues/1010/test/testcase.cpp @@ -134,6 +134,12 @@ TEST_F(Issue1010, RangesHaveNotChanged) { uint16_t(kaleidoscope::ranges::DYNAMIC_MACRO_FIRST)); ASSERT_EQ(uint16_t(Issue1010::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), uint16_t(kaleidoscope::ranges::SAFE_START)); ASSERT_EQ(uint16_t(Issue1010::KALEIDOSCOPE_SAFE_START), diff --git a/tests/issues/1032/1032.ino b/tests/issues/1032/1032.ino new file mode 100644 index 00000000..13f4a086 --- /dev/null +++ b/tests/issues/1032/1032.ino @@ -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 . + */ + +#include + +#include + +// *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(); +} diff --git a/tests/issues/1032/sketch.json b/tests/issues/1032/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/issues/1032/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/issues/1032/test.ktest b/tests/issues/1032/test.ktest new file mode 100644 index 00000000..87b14861 --- /dev/null +++ b/tests/issues/1032/test.ktest @@ -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 diff --git a/tests/issues/1042/1042.ino b/tests/issues/1042/1042.ino new file mode 100644 index 00000000..9d961a20 --- /dev/null +++ b/tests/issues/1042/1042.ino @@ -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 . + */ + +#include +#include +#include + +#include +#include "kaleidoscope/KeyEventTracker.h" +#include "kaleidoscope/LiveKeys.h" +#include "kaleidoscope/plugin.h" + +#include + +// *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(); +} diff --git a/tests/issues/1042/sketch.json b/tests/issues/1042/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/tests/issues/1042/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/tests/issues/1042/test.ktest b/tests/issues/1042/test.ktest new file mode 100644 index 00000000..fdaa6066 --- /dev/null +++ b/tests/issues/1042/test.ktest @@ -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 diff --git a/tests/issues/1057/1057.ino b/tests/issues/1057/1057.ino new file mode 100644 index 00000000..885277da --- /dev/null +++ b/tests/issues/1057/1057.ino @@ -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 . + */ + +#include +#include +#include + +#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(); +} diff --git a/tests/issues/1057/common.h b/tests/issues/1057/common.h new file mode 100644 index 00000000..424d2ce4 --- /dev/null +++ b/tests/issues/1057/common.h @@ -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 . + */ + +#pragma once + +#include + +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 diff --git a/tests/issues/1057/sketch.json b/tests/issues/1057/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/issues/1057/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/issues/1057/test.ktest b/tests/issues/1057/test.ktest new file mode 100644 index 00000000..7c4d583a --- /dev/null +++ b/tests/issues/1057/test.ktest @@ -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 diff --git a/tests/issues/423/423.ino b/tests/issues/423/423.ino new file mode 100644 index 00000000..0cf1d584 --- /dev/null +++ b/tests/issues/423/423.ino @@ -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 . + */ + +#include +#include +#include +#include + +// *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(); +} diff --git a/tests/issues/423/sketch.json b/tests/issues/423/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/423/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/issues/423/test.ktest b/tests/issues/423/test.ktest new file mode 100644 index 00000000..fb5d1075 --- /dev/null +++ b/tests/issues/423/test.ktest @@ -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 diff --git a/tests/issues/984/984.ino b/tests/issues/984/984.ino new file mode 100644 index 00000000..5250d1e5 --- /dev/null +++ b/tests/issues/984/984.ino @@ -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 . + */ + +#include +#include +#include + +// *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(); +} diff --git a/tests/issues/984/sketch.json b/tests/issues/984/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/issues/984/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/issues/984/test.ktest b/tests/issues/984/test.ktest new file mode 100644 index 00000000..9231fe6b --- /dev/null +++ b/tests/issues/984/test.ktest @@ -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 diff --git a/tests/plugins/AutoShift/basic/basic.ino b/tests/plugins/AutoShift/basic/basic.ino new file mode 100644 index 00000000..67118a12 --- /dev/null +++ b/tests/plugins/AutoShift/basic/basic.ino @@ -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 . + */ + +#include +#include + +// *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(); +} diff --git a/tests/plugins/AutoShift/basic/sketch.json b/tests/plugins/AutoShift/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/AutoShift/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/AutoShift/basic/test.ktest b/tests/plugins/AutoShift/basic/test.ktest new file mode 100644 index 00000000..1a336e26 --- /dev/null +++ b/tests/plugins/AutoShift/basic/test.ktest @@ -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 diff --git a/tests/plugins/SpaceCadet/no-delay/no-delay.ino b/tests/plugins/SpaceCadet/no-delay/no-delay.ino new file mode 100644 index 00000000..7881a202 --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/no-delay.ino @@ -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 . + */ + +#include +#include + +// *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(); +} diff --git a/tests/plugins/SpaceCadet/no-delay/sketch.json b/tests/plugins/SpaceCadet/no-delay/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/SpaceCadet/no-delay/test.ktest b/tests/plugins/SpaceCadet/no-delay/test.ktest new file mode 100644 index 00000000..ba35c59a --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/test.ktest @@ -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