Merge branch 'master' of https://github.com/GazHank/Kaleidoscope
commit
d28a9fdcf1
@ -0,0 +1,261 @@
|
||||
# How to write a Kaleidoscope plugin
|
||||
|
||||
This is a brief guide intended for those who want to write custom Kaleidoscope plugins. It covers basic things you'll need to know about how Kaleidoscope calls plugin event handlers, and how it will respond to actions taken by those plugins.
|
||||
|
||||
## What can a plugin do?
|
||||
|
||||
There are many things that Kaleidoscope plugins are capable of, from LED effects, serial communication with the host, altering HID reports, and interacting with other plugins. It's useful to break these capabilities down into some broad categories, based on the types of input a plugin can respond to.
|
||||
|
||||
- Key events (key switches toggling on and off)
|
||||
- Focus commands (sent to the keyboard from software on the host via the serial port)
|
||||
- LED updates
|
||||
- Keymap layer changes
|
||||
- Timers
|
||||
|
||||
## An example plugin
|
||||
|
||||
To make a Kaleidoscope plugin, we create a subclass of the `kaleidoscope::Plugin` class, usually in the `kaleidoscope::plugin` namespace:
|
||||
|
||||
```c++
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
class MyPlugin : public Plugin {};
|
||||
|
||||
} // namespace kaleidoscope
|
||||
} // namespace plugin
|
||||
```
|
||||
|
||||
This code can be placed in a separate C++ source file, but it's simplest to just define it right in the sketch's \*.ino file for now.
|
||||
|
||||
By convention, we create a singleton object named like the plugin's class in the global namespace. This is typical of Arduino code.
|
||||
|
||||
```c++
|
||||
kaleidoscope::plugin::MyPlugin MyPlugin;
|
||||
```
|
||||
|
||||
Next, in order to connect that plugin to the Kaleidoscope event handler system, we need to register it in the call to the preprocessor macro `KALEIDOSCOPE_INIT_PLUGINS()` in the sketch:
|
||||
|
||||
```c++
|
||||
KALEIDOSCOPE_INIT_PLUGINS(MyPlugin, OtherPlugin);
|
||||
```
|
||||
|
||||
To make our plugin do anything useful, we need to add [[event-handler-hooks]] to it. This is how Kaleidoscope delivers input events to its registered plugins. Here's an example:
|
||||
|
||||
```c++
|
||||
class MyPlugin : public Plugin {
|
||||
public:
|
||||
EventHanderResult onKeyEvent(KeyEvent &event);
|
||||
};
|
||||
```
|
||||
|
||||
This will result in `MyPlugin.onKeyEvent()` being called (along with other plugins' `onKeyEvent()` methods) when Kaleidoscope detects a key state change. This function returns one of three `EventHandlerResult` values:
|
||||
|
||||
- `EventHandlerResult::OK` indicates that Kaleidoscope should proceed on to the event handler for the next plugin in the chain.
|
||||
- `EventHandlerResult::ABORT` indicates that Kaleidoscope should stop processing immediately, and treat the event as if it didn't happen.
|
||||
- `EventHandlerResult::EVENT_CONSUMED` stops event processing like `ABORT`, but records that the key is being held.
|
||||
|
||||
The `onKeyEvent()` method takes one argument: a reference to a `KeyEvent` object, which is a simple container for these essential bits of information:
|
||||
|
||||
- `event.addr` — the physical location of the keyswitch, if any
|
||||
- `event.state` — a bitfield containing information on the current and previous state of the keyswitch (from which we can find out if it just toggled on or toggled off)
|
||||
- `event.key` — a 16-bit `Key` value containing the contents looked up from the sketch's current keymap (if the key just toggled on) or the current live value of the key (if the key just toggled off)
|
||||
|
||||
Because the `KeyEvent` parameter is passed by (mutable) reference, our plugin's `onKeyEvent()` method can alter the components of the event, causing subsequent plugins (and, eventually, Kaleidoscope itself) to treat it as if it was a different event. In practice, except in very rare cases, the only member of a `KeyEvent` that a plugin should alter is `event.key`. Here's a very simple `onKeyEvent()` handler that changes all `X` keys into `Y` keys:
|
||||
|
||||
```c++
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X)
|
||||
event.key = Key_Y;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
```
|
||||
|
||||
### The difference between `ABORT` & `EVENT_CONSUMED`
|
||||
|
||||
Here's a plugin that will suppress all `X` key events:
|
||||
|
||||
```c++
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X)
|
||||
return EventHandlerResult::ABORT;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
```
|
||||
|
||||
Here's an almost identical plugin that has an odd failure mode:
|
||||
|
||||
```c++
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X)
|
||||
return EventHandlerResult::EVENT_CONSUMED;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
```
|
||||
|
||||
In this case, when an `X` key is pressed, no Keyboard HID report will be generated and sent to the host, but the key will still be recorded by Kaleidoscope as "live". If we hold that key down and press a `Y` key, we will suddenly see both `x` _and_ `y` in the output on the host. This is because returning `ABORT` suppresses the key event entirely, as if it never happened, whereas `EVENT_CONSUMED` signals to Kaleidoscope that the key should still become "live", but that no further processing is necessary. In this case, since we want to suppress all `X` keys entirely, we should return `ABORT`.
|
||||
|
||||
### A complete in-sketch plugin
|
||||
|
||||
Here's an example of a very simple plugin, defined as it would be in a firmware sketch (e.g. a `*.ino` file):
|
||||
|
||||
```c++
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
class KillX : public Plugin {
|
||||
public:
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X)
|
||||
return EventHandlerResult::ABORT;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace kaleidoscope
|
||||
} // namespace plugin
|
||||
|
||||
kaleidoscope::plugin::KillX;
|
||||
```
|
||||
|
||||
On its own, this plugin won't have any effect unless we register it later in the sketch like this:
|
||||
|
||||
```c++
|
||||
KALEIDOSCOPE_INIT_PLUGINS(KillX);
|
||||
```
|
||||
|
||||
Note: `KALEIDOSCOPE_INIT_PLUGINS()` should only appear once in a sketch, with a list of all the plugins to be registered.
|
||||
|
||||
## Plugin registration order
|
||||
|
||||
Obviously, the `KillX` plugin isn't very useful. But more important, it's got a potential problem. Suppose we had another plugin defined, like so:
|
||||
|
||||
```c++
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
class YtoX : public Plugin {
|
||||
public:
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_Y)
|
||||
event.key = Key_X;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace kaleidoscope
|
||||
} // namespace plugin
|
||||
|
||||
kaleidoscope::plugin::YtoX;
|
||||
```
|
||||
|
||||
`YtoX` changes any `Y` key to an `X` key. These two plugins both work fine on their own, but when we put them together, we get some undesirable behavior. Let's try it this way first:
|
||||
|
||||
```c++
|
||||
KALEIDOSCOPE_INIT_PLUGINS(YtoX, KillX);
|
||||
```
|
||||
|
||||
This registers both plugins' event handlers with Kaleidoscope, in order, so for each `KeyEvent` generated in response to a keyswitch toggling on or off, `YtoX.onKeyEvent(event)` will get called first, then `KillX.onKeyEvent(event)` will get called.
|
||||
|
||||
If we press `X`, the `YtoX` plugin will effectively ignore the event, allowing it to pass through to `KillX`, which will abort the event.
|
||||
|
||||
If we press `Y`, `YtoX.onKeyEvent()` will change `event.key` from `Key_Y` to `Key_X`. Then, `KillX.onKeyEvent()` will abort the event. As a result, both `X` and `Y` keys will be suppressed by the combination of the two plugins.
|
||||
|
||||
---
|
||||
|
||||
Now, let's try the same two plugins in the other order:
|
||||
|
||||
```c++
|
||||
KALEIDOSCOPE_INIT_PLUGINS(KillX, YtoX);
|
||||
```
|
||||
|
||||
If we press `X`, its keypress event will get aborted by `KillX.onKeyEvent()`, and that key will not become live, so when it gets released, the event generated won't have the value `Key_X`, but will instead by `Key_Inactive`, which will not result in anything happening, either from the plugins or from Kaleidoscope itself.
|
||||
|
||||
Things get interesting if we press and release `Y`, though. First, `KillX.onKeyEvent()` will simply return `OK`, allowing `YtoX.onKeyEvent()` to change `event.key` from `Key_Y` to `Key_X`, causing that `Key_X` to become live, and sending its keycode to the host in the Keyboard USB HID report. That's all as expected, but then we release the key, and that's were it goes wrong.
|
||||
|
||||
`KillX.onKeyEvent()` doesn't distinguish between presses and releases. When a key toggles off, rather than looking up that key's value in the keymap, Kaleidoscope takes it from the live keys array. That means that `event.key` will be `Key_X` when `KillX.onKeyEvent()` is called, which will result in that event being aborted. And when an event is aborted, the key's entry in the live keys array doesn't get updated, so Kaleidoscope will treat it as if the key is still held after release. Thus, far from preventing the keycode for `X` getting to the host, it keeps that key pressed forever! The `X` key becomes "stuck on" because the plugin suppresses both key _presses_ and key _releases_.
|
||||
|
||||
### Differentiating between press and release events
|
||||
|
||||
There is a solution to this problem, which is to have `KillX` suppress `Key_X` toggle-on events, but not toggle-off events:
|
||||
|
||||
```c++
|
||||
EventHandlerResult KillX::onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X && keyToggledOn(event.state))
|
||||
return EventHandlerResult::ABORT;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
```
|
||||
|
||||
Kaleidoscope provides `keyToggledOn()` and `keyToggledOff()` functions that operate on the `event.state` bitfield, allowing plugins to differentiate between the two different event states. With this new version of the `KillX` plugin, it won't keep an `X` key live, but it will stop one from _becoming_ live.
|
||||
|
||||
Our two plugins still yield results that depend on registration order in `KALEIDOSCOPE_INIT_PLUGINS()`, but the bug where the `X` key becomes "stuck on" is gone.
|
||||
|
||||
It is very common for plugins to only act on key toggle-on events, or to respond differently to toggle-on and toggle-off events.
|
||||
|
||||
## Timers
|
||||
|
||||
Another thing that many plugins need to do is handle timeouts. For example, the OneShot plugin keeps certain keys live for a period of time after those keys are released. Kaleidoscope provides some infrastructure to help us keep track of time, starting with the `afterEachCycle()` "event" handler function.
|
||||
|
||||
The `onKeyEvent()` handlers only get called in response to keyswitches toggling on and off (or as a result of plugins calling `Runtime.handleKeyEvent()`). If the user isn't actively typing for a period, its `onKeyEvent()` handler won't get called at all, so it's not very useful to check timers in that function. Instead, if we need to know if a timer has expired, we need to do it in a function that gets called regularly, regardless of input. The `afterEachCycle()` handler gets called once per cycle, guaranteed.
|
||||
|
||||
This is what an `afterEachCycle()` handler looks like:
|
||||
|
||||
```c++
|
||||
EventHandlerResult afterEachCycle() {
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
```
|
||||
|
||||
It returns an `EventHandlerResult`, like other event handlers, but this one's return value is ignored by Kaleidoscope; returning `ABORT` or `EVENT_CONSUMED` has no effect on other plugins.
|
||||
|
||||
In addition to this, we need a way to keep track of time. For this, Kaleidoscope provides the function `Runtime.millisAtCycleStart()`, which returns an unsigned integer representing the number of milliseconds that have elapsed since the keyboard started. It's a 32-bit integer, so it won't overflow until about one month has elapsed, but we usually want to use as few bytes of RAM as possible on our MCU, so most timers store only as many bytes as needed, usually a `uint16_t`, which overflows after about one minute, or even a `uint8_t`, which is good for up to a quarter of a second.
|
||||
|
||||
We need to use an integer type that's at least as big as the longest timeout we expect to be used, but integer overflow can still give us the wrong answer if we check it by naïvely comparing the current time to the time at expiration, so Kaleidoscope provides a timeout-checking service that's handles the integer overflow properly: `Runtime.hasTimeExpired(start_time, timeout)`. To use it, your plugin should store a timestamp when the timer begins, using `Runtime.millisAtCycleStart()` (usually set in response to an event in `onKeyEvent()`). Then, in its `afterEachCycle()` call `hasTimeExpired()`:
|
||||
|
||||
```c++
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
class MyPlugin : public Plugin {
|
||||
public:
|
||||
constexpr uint16_t timeout = 500;
|
||||
|
||||
EventHandlerResult onKeyEvent(KeyEvent &event) {
|
||||
if (event.key == Key_X && keyToggledOn(event.state)) {
|
||||
start_time_ = Runtime.millisAtCycleStart();
|
||||
timer_running_ = true;
|
||||
}
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
EventHandlerResult afterEachCycle() {
|
||||
if (Runtime.hasTimeExpired(start_time_, timeout)) {
|
||||
timer_running_ = false;
|
||||
// do something...
|
||||
}
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
private:
|
||||
bool timer_running_ = false;
|
||||
uint16_t start_time_;
|
||||
};
|
||||
|
||||
} // namespace kaleidoscope
|
||||
} // namespace plugin
|
||||
|
||||
kaleidoscope::plugin::MyPlugin;
|
||||
```
|
||||
|
||||
In the above example, the private member variable `start_time_` and the constant `timeout` are the same type of unsigned integer (`uint16_t`), and we've used the additional boolean `timer_running_` to keep from checking for timeouts when `start_time_` isn't valid. This plugin does something (unspecified) 500 milliseconds after a `Key_X` toggles on.
|
||||
|
||||
## Creating artificial events
|
||||
|
||||
## Controlling LEDs
|
||||
|
||||
## HID reports
|
||||
|
||||
## Physical keyswitch events
|
||||
|
||||
## Layer changes
|
@ -0,0 +1,75 @@
|
||||
// -*- mode: c++ -*-
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
|
||||
#include <Kaleidoscope-AutoShift.h>
|
||||
#include <Kaleidoscope-EEPROM-Settings.h>
|
||||
#include <Kaleidoscope-EEPROM-Keymap.h>
|
||||
#include <Kaleidoscope-FocusSerial.h>
|
||||
#include <Kaleidoscope-Macros.h>
|
||||
|
||||
enum {
|
||||
TOGGLE_AUTOSHIFT,
|
||||
};
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
||||
|
||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
||||
XXX,
|
||||
|
||||
M(TOGGLE_AUTOSHIFT), Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
||||
|
||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
||||
XXX
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
// Defining a macro (on the "any" key: see above) to turn AutoShift on and off
|
||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
||||
switch (macro_id) {
|
||||
case TOGGLE_AUTOSHIFT:
|
||||
if (keyToggledOn(event.state))
|
||||
AutoShift.toggle();
|
||||
break;
|
||||
}
|
||||
return MACRO_NONE;
|
||||
}
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(
|
||||
EEPROMSettings, // for AutoShiftConfig
|
||||
EEPROMKeymap, // for AutoShiftConfig
|
||||
Focus, // for AutoShiftConfig
|
||||
FocusEEPROMCommand, // for AutoShiftConfig
|
||||
FocusSettingsCommand, // for AutoShiftConfig
|
||||
AutoShift,
|
||||
AutoShiftConfig, // for AutoShiftConfig
|
||||
Macros // for toggle AutoShift Macro
|
||||
);
|
||||
|
||||
void setup() {
|
||||
// Enable AutoShift for letter keys and number keys only:
|
||||
AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys());
|
||||
// Add symbol keys to the enabled categories:
|
||||
AutoShift.enable(AutoShift.symbolKeys());
|
||||
// Set the AutoShift long-press time to 150ms:
|
||||
AutoShift.setTimeout(150);
|
||||
// Start with AutoShift turned off:
|
||||
AutoShift.disable();
|
||||
|
||||
Kaleidoscope.setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:avr:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-LeaderPrefix -- Prefix arg for Leader plugin
|
||||
* Copyright (C) 2021 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-Leader.h>
|
||||
#include <Kaleidoscope-Macros.h>
|
||||
|
||||
#include <Kaleidoscope-Ranges.h>
|
||||
#include "kaleidoscope/KeyEventTracker.h"
|
||||
#include "kaleidoscope/LiveKeys.h"
|
||||
#include "kaleidoscope/plugin.h"
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
||||
|
||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
||||
LEAD(0),
|
||||
|
||||
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
||||
|
||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
||||
LEAD(0)),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
// =============================================================================
|
||||
/// Plugin to supply a numeric prefix argument to Leader key functions
|
||||
///
|
||||
/// This plugin lets the user type a numeric prefix after a Leader key is
|
||||
/// pressed, but before the rest of the Leader sequence is begun, storing the
|
||||
/// "prefix argument" and making it available to functions called from the
|
||||
/// leader dictionary. LeaderPrefix allows us to define keys other than the
|
||||
/// ones on the number row to be interpreted as the "digit" keys, because
|
||||
/// whatever we use will need to be accessed without a layer change.
|
||||
class LeaderPrefix : public Plugin {
|
||||
public:
|
||||
// We need to define `onKeyswitchEvent()` instead of `onKeyEvent()` because we
|
||||
// need to intercept events before Leader sees them, and the Leader plugin
|
||||
// uses the former.
|
||||
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
|
||||
// Every `onKeyswitchEvent()` function should begin with this to prevent
|
||||
// re-processing events that it has already seen.
|
||||
if (event_tracker_.shouldIgnore(event))
|
||||
return EventHandlerResult::OK;
|
||||
|
||||
// `Active` means that we're actively building the prefix argument. If the
|
||||
// plugin is not active, we're looking for a Leader key toggling on.
|
||||
if (!active_) {
|
||||
if (keyToggledOn(event.state) && isLeaderKey(event.key)) {
|
||||
// A Leader key toggled on, so we set our state to "active", and set the
|
||||
// arg value to zero.
|
||||
active_ = true;
|
||||
leader_arg_ = 0;
|
||||
}
|
||||
// Whether or not the plugin just became active, there's nothing more to
|
||||
// do for this event.
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
// The plugin is "active", so we're looking for a "digit" key that just
|
||||
// toggled on.
|
||||
if (keyToggledOn(event.state)) {
|
||||
// We search our array of digit keys to find one that matches the event.
|
||||
// These "digit keys" are defined by their `KeyAddr` because they're
|
||||
// probably independent of keymap and layer, and because a `KeyAddr` only
|
||||
// takes one byte, whereas a `Key` takes two.
|
||||
for (uint8_t i{0}; i < 10; ++i) {
|
||||
if (digit_addrs_[i] == event.addr) {
|
||||
// We found a match, which means that one of our "digit keys" toggled
|
||||
// on. If this happens more than once, the user is typing a number
|
||||
// with multiple digits, so we multiply the current value by ten
|
||||
// before adding the new digit to the total.
|
||||
leader_arg_ *= 10;
|
||||
leader_arg_ += i;
|
||||
// Next, we mask the key that was just pressed, so that nothing will
|
||||
// happen when it is released.
|
||||
live_keys.mask(event.addr);
|
||||
// We return `ABORT` so that no other plugins (i.e. Leader) will see
|
||||
// this keypress event.
|
||||
return EventHandlerResult::ABORT;
|
||||
}
|
||||
}
|
||||
}
|
||||
// No match was found, so the key that toggled on was not one of our "digit
|
||||
// keys". Presumably, this is the first key in the Leader sequence that is
|
||||
// being typed. We leave the prefix argument at its current value so that
|
||||
// it will still be set when the sequence is finished, and allow the event
|
||||
// to pass through to the next plugin (i.e. Leader).
|
||||
active_ = false;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
uint16_t arg() const {
|
||||
return leader_arg_;
|
||||
}
|
||||
|
||||
private:
|
||||
// The "digit keys": these are the keys on the number row of the Model01.
|
||||
KeyAddr digit_addrs_[10] = {
|
||||
KeyAddr(0, 14), KeyAddr(0, 1), KeyAddr(0, 2), KeyAddr(0, 3), KeyAddr(0, 4),
|
||||
KeyAddr(0, 5), KeyAddr(0, 10), KeyAddr(0, 11), KeyAddr(0, 12), KeyAddr(0, 13),
|
||||
};
|
||||
|
||||
// This event tracker is necessary to prevent re-processing events. Any
|
||||
// plugin that defines `onKeyswitchEvent()` should use one.
|
||||
KeyEventTracker event_tracker_;
|
||||
|
||||
// The current state of the plugin. It determines whether we're looking for a
|
||||
// Leader keypress or building a prefix argument.
|
||||
bool active_{false};
|
||||
|
||||
// The prefix argument itself.
|
||||
uint16_t leader_arg_{0};
|
||||
|
||||
// Leader should probably provide this test, but since it doesn't, we add it
|
||||
// here to determine if a key is a Leader key.
|
||||
bool isLeaderKey(Key key) {
|
||||
return (key >= ranges::LEAD_FIRST && key <= ranges::LEAD_LAST);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace kaleidoscope
|
||||
|
||||
// This creates our plugin object.
|
||||
kaleidoscope::plugin::LeaderPrefix LeaderPrefix;
|
||||
|
||||
auto &serial_port = Kaleidoscope.serialPort();
|
||||
|
||||
static void leaderTestX(uint8_t seq_index) {
|
||||
serial_port.println(F("leaderTestX"));
|
||||
}
|
||||
|
||||
static void leaderTestXX(uint8_t seq_index) {
|
||||
serial_port.println(F("leaderTestXX"));
|
||||
}
|
||||
|
||||
// This demonstrates how to use the prefix argument in a Leader function. In
|
||||
// this case, our function just types as many `x` characters as specified by the
|
||||
// prefix arg.
|
||||
void leaderTestPrefix(uint8_t seq_index) {
|
||||
// Read the prefix argument into a temporary variable:
|
||||
uint8_t prefix_arg = LeaderPrefix.arg();
|
||||
// Use a Macros helper function to tap the `X` key repeatedly.
|
||||
while (prefix_arg-- > 0)
|
||||
Macros.tap(Key_X);
|
||||
}
|
||||
|
||||
static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM =
|
||||
LEADER_DICT({LEADER_SEQ(LEAD(0), Key_X), leaderTestX},
|
||||
{LEADER_SEQ(LEAD(0), Key_X, Key_X), leaderTestXX},
|
||||
{LEADER_SEQ(LEAD(0), Key_Z), leaderTestPrefix});
|
||||
|
||||
// The order matters here; LeaderPrefix won't work unless it precedes Leader in
|
||||
// this list. If there are other plugins in the list, these two should ideally
|
||||
// be next to each other, but that's not necessary.
|
||||
KALEIDOSCOPE_INIT_PLUGINS(LeaderPrefix, Leader);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
|
||||
Leader.dictionary = leader_dictionary;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:avr:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
# AutoShift
|
||||
|
||||
AutoShift allows you to type shifted characters by long-pressing a key, rather
|
||||
than chording it with a modifier key.
|
||||
|
||||
## Using the plugin
|
||||
|
||||
Using the plugin with its defaults is as simple as including the header, and
|
||||
enabling the plugin:
|
||||
|
||||
```c++
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-AutoShift.h>
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(AutoShift);
|
||||
```
|
||||
|
||||
With AutoShift enabled, when you first press a key that AutoShift acts on, its
|
||||
output will be delayed. If you hold the key down long enough, you will see the
|
||||
shifted symbol appear in the output. If you release the key before the timeout,
|
||||
the output will be unshifted.
|
||||
|
||||
## Turning AutoShift on and off
|
||||
|
||||
The `AutoShift` object provides three methods for turning itself on and off:
|
||||
|
||||
- To turn the plugin off, call `AutoShift.enable()`.
|
||||
- To turn the plugin on, call `AutoShift.disable()`.
|
||||
- To toggle the plugin's state, call `AutoShift.toggle()`.
|
||||
|
||||
Note: Disabling the AutoShift plugin does not affect which `Key` categories it
|
||||
will affect when it is re-enabled.
|
||||
|
||||
## Setting the AutoShift long-press delay
|
||||
|
||||
To set the length of time AutoShift will wait before adding the `shift` modifier
|
||||
to the key's output, use `AutoShift.setTimeout(t)`, where `t` is a number of
|
||||
milliseconds.
|
||||
|
||||
## Configuring which keys get auto-shifted
|
||||
|
||||
AutoShift provides a set of key categories that can be independently added or
|
||||
removed from the set of keys that will be auto-shifted when long-pressed:
|
||||
|
||||
- `AutoShift.letterKeys()`: Letter keys
|
||||
- `AutoShift.numberKeys()`: Number keys (number row, not numeric keypad)
|
||||
- `AutoShift.symbolKeys()`: Other printable symbols
|
||||
- `AutoShift.arrowKeys()`: Navigational arrow keys
|
||||
- `AutoShift.functionKeys()`: All function keys (F1-F24)
|
||||
- `AutoShift.printableKeys()`: Letters, numbers, and symbols
|
||||
- `AutoShift.allKeys()`: All non-modifier USB Keyboard keys
|
||||
|
||||
These categories are restricted to USB Keyboard-type keys, and any modifier
|
||||
flags attached to the key is ignored when determining if it will be
|
||||
auto-shifted. Any of the above expressions can be used as the `category` parameter in the functions described below.
|
||||
|
||||
- To include a category in the set that will be auto-shifted, call `AutoShift.enable(category)`
|
||||
- To remove a category from the set that will be auto-shifted, call `AutoShift.disable(category)`
|
||||
- To set the full list of categories that will be auto-shifted, call `AutoShift.setEnabled(categories)`, where `categories` can be either a single category from the above list, or list of categories combined using the `|` (bitwise-or) operator (e.g. `AutoShift.setEnabled(AutoShift.letterKeys() | AutoShift.numberKeys())`).
|
||||
|
||||
## Advanced customization of which keys get auto-shifted
|
||||
|
||||
If the above categories are not sufficient for your auto-shifting needs, it is
|
||||
possible to get even finer-grained control of which keys are affected by
|
||||
AutoShift, by overriding the `isAutoShiftable()` method in your sketch. For
|
||||
example, to make AutoShift only act on keys `A` and `Z`, include the following
|
||||
code in your sketch:
|
||||
|
||||
```c++
|
||||
bool AutoShift::isAutoShiftable(Key key) {
|
||||
if (key == Key_A || key == key_Z)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
As you can see, this method takes a `Key` as its input and returns either `true`
|
||||
(for keys eligible to be auto-shifted) or `false` (for keys AutoShift will leave
|
||||
alone).
|
||||
|
||||
## Further reading
|
||||
|
||||
Starting from the [example][plugin:example] is the recommended way of getting
|
||||
started with the plugin.
|
||||
|
||||
[plugin:example]: /examples/Keystrokes/AutoShift/AutoShift.ino
|
@ -0,0 +1,7 @@
|
||||
name=Kaleidoscope-AutoShift
|
||||
version=0.0.0
|
||||
sentence=Automatic shift on long press
|
||||
maintainer=Kaleidoscope's Developers <jesse@keyboard.io>
|
||||
url=https://github.com/keyboardio/Kaleidoscope
|
||||
author=Michael Richters <gedankenexperimenter@gmail.com>
|
||||
paragraph=
|
@ -0,0 +1,20 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-AutoShift -- Automatic shift on long press
|
||||
* Copyright (C) 2021 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <kaleidoscope/plugin/AutoShift.h>
|
@ -0,0 +1,212 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-AutoShift -- Automatic shift on long press
|
||||
* Copyright (C) 2021 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "kaleidoscope/plugin/AutoShift.h"
|
||||
|
||||
#include "kaleidoscope/KeyAddr.h"
|
||||
#include "kaleidoscope/key_defs.h"
|
||||
#include "kaleidoscope/KeyEvent.h"
|
||||
#include "kaleidoscope/keyswitch_state.h"
|
||||
#include "kaleidoscope/Runtime.h"
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
// =============================================================================
|
||||
// AutoShift static class variables
|
||||
|
||||
// Configuration settings that can be saved to persistent storage.
|
||||
AutoShift::Settings AutoShift::settings_ = {
|
||||
.enabled = true,
|
||||
.timeout = 175,
|
||||
.enabled_categories = AutoShift::Categories::printableKeys(),
|
||||
};
|
||||
|
||||
// The event tracker ensures that the `onKeyswitchEvent()` handler will follow
|
||||
// the rules in order to avoid interference with other plugins and prevent
|
||||
// processing the same event more than once.
|
||||
KeyEventTracker AutoShift::event_tracker_;
|
||||
|
||||
// If there's a delayed keypress from AutoShift, this stored event will contain
|
||||
// a valid `KeyAddr`. The default constructor produces an event addr of
|
||||
// `KeyAddr::none()`, so the plugin will start in an inactive state.
|
||||
KeyEvent pending_event_;
|
||||
|
||||
// =============================================================================
|
||||
// AutoShift functions
|
||||
|
||||
void AutoShift::disable() {
|
||||
settings_.enabled = false;
|
||||
if (pending_event_.addr.isValid()) {
|
||||
Runtime.handleKeyswitchEvent(pending_event_);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Test for whether or not to apply AutoShift to a given `Key`. This function
|
||||
// can be overridden from the user sketch.
|
||||
__attribute__((weak))
|
||||
bool AutoShift::isAutoShiftable(Key key) {
|
||||
return enabledForKey(key);
|
||||
}
|
||||
|
||||
// The default method that determines whether a particular key is an auto-shift
|
||||
// candidate. Used by `isAutoShiftable()`, separate to allow re-use when the
|
||||
// caller is overridden.
|
||||
bool AutoShift::enabledForKey(Key key) {
|
||||
// We only support auto-shifting keyboard keys. We could also explicitly
|
||||
// ignore modifier keys, but there's no need to do so, as none of the code
|
||||
// below matches modifiers.
|
||||
if (!key.isKeyboardKey())
|
||||
return false;
|
||||
|
||||
// We compare only the keycode, and disregard any modifier flags applied to
|
||||
// the key. This simplifies the comparison, and also allows AutoShift to
|
||||
// apply to keys like `RALT(Key_E)`.
|
||||
uint8_t keycode = key.getKeyCode();
|
||||
|
||||
if (isEnabled(Categories::allKeys())) {
|
||||
if (keycode < HID_KEYBOARD_FIRST_MODIFIER)
|
||||
return true;
|
||||
}
|
||||
if (isEnabled(Categories::letterKeys())) {
|
||||
if (keycode >= Key_A.getKeyCode() && keycode <= Key_Z.getKeyCode())
|
||||
return true;
|
||||
}
|
||||
if (isEnabled(Categories::numberKeys())) {
|
||||
if (keycode >= Key_1.getKeyCode() && keycode <= Key_0.getKeyCode())
|
||||
return true;
|
||||
}
|
||||
if (isEnabled(Categories::symbolKeys())) {
|
||||
if ((keycode >= Key_Minus.getKeyCode() && keycode <= Key_Slash.getKeyCode()) ||
|
||||
(keycode == Key_NonUsBackslashAndPipe.getKeyCode()))
|
||||
return true;
|
||||
}
|
||||
if (isEnabled(Categories::arrowKeys())) {
|
||||
if (keycode >= Key_RightArrow.getKeyCode() &&
|
||||
keycode <= Key_LeftArrow.getKeyCode())
|
||||
return true;
|
||||
}
|
||||
if (isEnabled(Categories::functionKeys())) {
|
||||
if ((keycode >= Key_F1.getKeyCode() && keycode <= Key_F12.getKeyCode()) ||
|
||||
(keycode >= Key_F13.getKeyCode() && keycode <= Key_F24.getKeyCode()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Event handler hook functions
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
EventHandlerResult AutoShift::onKeyswitchEvent(KeyEvent &event) {
|
||||
// If AutoShift has already processed and released this event, ignore it.
|
||||
// There's no need to update the event tracker in this one case.
|
||||
if (event_tracker_.shouldIgnore(event)) {
|
||||
// We should never get an event that's in our queue here, but just in case
|
||||
// some other plugin sends one, abort.
|
||||
if (queue_.shouldAbort(event))
|
||||
return EventHandlerResult::ABORT;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
// If event.addr is not a physical key, ignore it; some other plugin injected
|
||||
// it. This check should be unnecessary.
|
||||
if (!event.addr.isValid() || (event.state & INJECTED) != 0) {
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
// Do nothing if disabled.
|
||||
if (!settings_.enabled)
|
||||
return EventHandlerResult::OK;
|
||||
|
||||
if (!queue_.isEmpty()) {
|
||||
// There's an unresolved AutoShift key press.
|
||||
if (queue_.isFull()) {
|
||||
flushQueue();
|
||||
} else if (event.addr == queue_.addr(0)) {
|
||||
flushEvent(false);
|
||||
flushQueue();
|
||||
}
|
||||
if (queue_.isEmpty())
|
||||
return EventHandlerResult::OK;
|
||||
queue_.append(event);
|
||||
return EventHandlerResult::ABORT;
|
||||
}
|
||||
|
||||
if (keyToggledOn(event.state) && isAutoShiftable(event.key)) {
|
||||
queue_.append(event);
|
||||
return EventHandlerResult::ABORT;
|
||||
}
|
||||
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
EventHandlerResult AutoShift::afterEachCycle() {
|
||||
// If there's a pending AutoShift event, and it has timed out, we need to
|
||||
// release the event with the `shift` flag applied.
|
||||
if (!queue_.isEmpty() &&
|
||||
Runtime.hasTimeExpired(queue_.timestamp(0), settings_.timeout)) {
|
||||
// Toggle the state of the `SHIFT_HELD` bit in the modifier flags for the
|
||||
// key for the pending event.
|
||||
flushEvent(true);
|
||||
flushQueue();
|
||||
}
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
void AutoShift::flushQueue() {
|
||||
while (!queue_.isEmpty()) {
|
||||
if (queue_.isRelease(0) || checkForRelease()) {
|
||||
flushEvent(false);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AutoShift::checkForRelease() const {
|
||||
KeyAddr queue_head_addr = queue_.addr(0);
|
||||
for (uint8_t i = 1; i < queue_.length(); ++i) {
|
||||
if (queue_.addr(i) == queue_head_addr) {
|
||||
// This key's release is also in the queue
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AutoShift::flushEvent(bool is_long_press) {
|
||||
if (queue_.isEmpty())
|
||||
return;
|
||||
KeyEvent event = queue_.event(0);
|
||||
if (is_long_press) {
|
||||
event.key = Runtime.lookupKey(event.addr);
|
||||
uint8_t flags = event.key.getFlags();
|
||||
flags ^= SHIFT_HELD;
|
||||
event.key.setFlags(flags);
|
||||
}
|
||||
queue_.shift();
|
||||
Runtime.handleKeyswitchEvent(event);
|
||||
}
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace kaleidoscope
|
||||
|
||||
kaleidoscope::plugin::AutoShift AutoShift;
|
@ -0,0 +1,278 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-AutoShift -- Automatic shift on long press
|
||||
* Copyright (C) 2021 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "kaleidoscope/Runtime.h"
|
||||
#include "kaleidoscope/KeyEventTracker.h"
|
||||
#include "kaleidoscope/KeyAddrEventQueue.h"
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
// =============================================================================
|
||||
/// Kaleidoscope plugin for long-press auto-shift keys
|
||||
///
|
||||
/// This plugin allows the user to "long-press" keys to automatically apply the
|
||||
/// `shift` modifier to the keypress. By enabling AutoShift, it's possible to
|
||||
/// produce capital letters (for example) without holding a separate modifier
|
||||
/// key first.
|
||||
class AutoShift : public Plugin {
|
||||
|
||||
public:
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inner class for `Key` categories that can be configured to be auto-shifted
|
||||
// by long-pressing. Most of this class is purely internal, but user code
|
||||
// that enables or disables these auto-shift categories might use the
|
||||
// following as constants:
|
||||
//
|
||||
// - `AutoShift::Categories::letterKeys()`
|
||||
// - `AutoShift::Categories::numberKeys()`
|
||||
// - `AutoShift::Categories::symbolKeys()`
|
||||
// - `AutoShift::Categories::arrowKeys()`
|
||||
// - `AutoShift::Categories::functionKeys()`
|
||||
// - `AutoShift::Categories::printableKeys()`
|
||||
// - `AutoShift::Categories::allKeys()`
|
||||
//
|
||||
// The first two ("letter keys" and "number keys") are self-explanatory. The
|
||||
// third ("symbol keys") includes keys that produce symbols other than letters
|
||||
// and numbers, but not whitespace characters, modifiers, et cetera. We could
|
||||
// perhaps add another category for function keys.
|
||||
class Categories {
|
||||
private:
|
||||
// raw bitfield data
|
||||
uint8_t raw_bits_{0};
|
||||
|
||||
// constants for bits in the `raw_bits_` bitfield
|
||||
static constexpr uint8_t LETTERS = 1 << 0;
|
||||
static constexpr uint8_t NUMBERS = 1 << 1;
|
||||
static constexpr uint8_t SYMBOLS = 1 << 2;
|
||||
static constexpr uint8_t ARROWS = 1 << 3;
|
||||
static constexpr uint8_t FUNCTIONS = 1 << 4;
|
||||
static constexpr uint8_t ALL = 1 << 7;
|
||||
|
||||
public:
|
||||
// Basic un-checked constructor
|
||||
constexpr Categories(uint8_t raw_bits) : raw_bits_(raw_bits) {}
|
||||
|
||||
static constexpr Categories letterKeys() {
|
||||
return Categories(LETTERS);
|
||||
}
|
||||
static constexpr Categories numberKeys() {
|
||||
return Categories(NUMBERS);
|
||||
}
|
||||
static constexpr Categories symbolKeys() {
|
||||
return Categories(SYMBOLS);
|
||||
}
|
||||
static constexpr Categories arrowKeys() {
|
||||
return Categories(ARROWS);
|
||||
}
|
||||
static constexpr Categories functionKeys() {
|
||||
return Categories(FUNCTIONS);
|
||||
}
|
||||
static constexpr Categories printableKeys() {
|
||||
return Categories(LETTERS | NUMBERS | SYMBOLS);
|
||||
}
|
||||
static constexpr Categories allKeys() {
|
||||
return Categories(ALL);
|
||||
}
|
||||
|
||||
constexpr void set(uint8_t raw_bits) {
|
||||
raw_bits_ = raw_bits;
|
||||
}
|
||||
constexpr void add(Categories categories) {
|
||||
this->raw_bits_ |= categories.raw_bits_;
|
||||
}
|
||||
constexpr void remove(Categories categories) {
|
||||
this->raw_bits_ &= ~(categories.raw_bits_);
|
||||
}
|
||||
constexpr bool contains(Categories categories) const {
|
||||
return (this->raw_bits_ & categories.raw_bits_) != 0;
|
||||
// More correct test:
|
||||
// return (~(this->raw_bits_) & categories.raw_bits_) == 0;
|
||||
}
|
||||
|
||||
// Shorthand for combining categories:
|
||||
// e.g. `Categories::letterKeys() | Categories::numberKeys()`
|
||||
constexpr Categories operator|(Categories other) const {
|
||||
return Categories(this->raw_bits_ | other.raw_bits_);
|
||||
}
|
||||
|
||||
// A conversion to integer is provided for the sake of interactions with the
|
||||
// Focus plugin.
|
||||
explicit constexpr operator uint8_t() {
|
||||
return raw_bits_;
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// This lets the AutoShiftConfig plugin access the internal config variables
|
||||
// directly. Mainly useful for calls to `Runtime.storage.get()`.
|
||||
friend class AutoShiftConfig;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Configuration functions
|
||||
|
||||
/// Returns `true` if AutoShift is active, `false` otherwise
|
||||
static bool enabled() {
|
||||
return settings_.enabled;
|
||||
}
|
||||
/// Activates the AutoShift plugin (held keys will trigger auto-shift)
|
||||
static void enable() {
|
||||
settings_.enabled = true;
|
||||
}
|
||||
/// Deactivates the AutoShift plugin (held keys will not trigger auto-shift)
|
||||
static void disable();
|
||||
/// Turns AutoShift on if it's off, and vice versa
|
||||
static void toggle() {
|
||||
if (settings_.enabled) {
|
||||
disable();
|
||||
} else {
|
||||
enable();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the hold time required to trigger auto-shift (in ms)
|
||||
static uint16_t timeout() {
|
||||
return settings_.timeout;
|
||||
}
|
||||
/// Sets the hold time required to trigger auto-shift (in ms)
|
||||
static void setTimeout(uint16_t new_timeout) {
|
||||
settings_.timeout = new_timeout;
|
||||
}
|
||||
|
||||
/// Returns the set of categories currently eligible for auto-shift
|
||||
static Categories enabledCategories() {
|
||||
return settings_.enabled_categories;
|
||||
}
|
||||
/// Adds `category` to the set eligible for auto-shift
|
||||
///
|
||||
/// Possible values for `category` are:
|
||||
/// - `AutoShift::Categories::letterKeys()`
|
||||
/// - `AutoShift::Categories::numberKeys()`
|
||||
/// - `AutoShift::Categories::symbolKeys()`
|
||||
/// - `AutoShift::Categories::arrowKeys()`
|
||||
/// - `AutoShift::Categories::functionKeys()`
|
||||
/// - `AutoShift::Categories::printableKeys()`
|
||||
/// - `AutoShift::Categories::allKeys()`
|
||||
static void enable(Categories category) {
|
||||
settings_.enabled_categories.add(category);
|
||||
}
|
||||
/// Removes a `Key` category from the set eligible for auto-shift
|
||||
static void disable(Categories category) {
|
||||
settings_.enabled_categories.remove(category);
|
||||
}
|
||||
/// Replaces the list of `Key` categories eligible for auto-shift
|
||||
static void setEnabled(Categories categories) {
|
||||
settings_.enabled_categories = categories;
|
||||
}
|
||||
/// Returns `true` if the given category is eligible for auto-shift
|
||||
static bool isEnabled(Categories category) {
|
||||
return settings_.enabled_categories.contains(category);
|
||||
}
|
||||
|
||||
/// The category representing letter keys
|
||||
static constexpr Categories letterKeys() {
|
||||
return Categories::letterKeys();
|
||||
}
|
||||
/// The category representing number keys (on the number row)
|
||||
static constexpr Categories numberKeys() {
|
||||
return Categories::numberKeys();
|
||||
}
|
||||
/// The category representing other printable symbol keys
|
||||
static constexpr Categories symbolKeys() {
|
||||
return Categories::symbolKeys();
|
||||
}
|
||||
/// The category representing arrow keys
|
||||
static constexpr Categories arrowKeys() {
|
||||
return Categories::arrowKeys();
|
||||
}
|
||||
/// The category representing function keys
|
||||
static constexpr Categories functionKeys() {
|
||||
return Categories::functionKeys();
|
||||
}
|
||||
/// Letters, numbers, and other symbols
|
||||
static constexpr Categories printableKeys() {
|
||||
return Categories::printableKeys();
|
||||
}
|
||||
/// All non-modifier keyboard keys
|
||||
static constexpr Categories allKeys() {
|
||||
return Categories::allKeys();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/// Determines which keys will trigger auto-shift if held long enough
|
||||
///
|
||||
/// This function can be overridden by the user sketch to configure which keys
|
||||
/// can trigger auto-shift.
|
||||
static bool isAutoShiftable(Key key);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Event handlers
|
||||
EventHandlerResult onKeyswitchEvent(KeyEvent &event);
|
||||
EventHandlerResult afterEachCycle();
|
||||
|
||||
private:
|
||||
// ---------------------------------------------------------------------------
|
||||
/// A container for AutoShift configuration settings
|
||||
struct Settings {
|
||||
/// The overall state of the plugin (on/off)
|
||||
bool enabled;
|
||||
/// The length of time (ms) a key must be held to trigger auto-shift
|
||||
uint16_t timeout;
|
||||
/// The set of `Key` categories eligible to be auto-shifted
|
||||
Categories enabled_categories;
|
||||
};
|
||||
static Settings settings_;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Key event queue state variables
|
||||
|
||||
// A device for processing only new events
|
||||
static KeyEventTracker event_tracker_;
|
||||
|
||||
// The maximum number of events in the queue at a time.
|
||||
static constexpr uint8_t queue_capacity_{4};
|
||||
|
||||
// The event queue stores a series of press and release events.
|
||||
KeyAddrEventQueue<queue_capacity_> queue_;
|
||||
|
||||
void flushQueue();
|
||||
void flushEvent(bool is_long_press = false);
|
||||
bool checkForRelease() const;
|
||||
|
||||
/// The default function for `isAutoShiftable()`
|
||||
static bool enabledForKey(Key key);
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
/// Configuration plugin for persistent storage of settings
|
||||
class AutoShiftConfig : public Plugin {
|
||||
public:
|
||||
EventHandlerResult onSetup();
|
||||
EventHandlerResult onFocusEvent(const char *command);
|
||||
|
||||
private:
|
||||
// The base address in persistent storage for configuration data
|
||||
static uint16_t settings_base_;
|
||||
};
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace kaleidoscope
|
||||
|
||||
extern kaleidoscope::plugin::AutoShift AutoShift;
|
||||
extern kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig;
|
@ -0,0 +1,121 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-AutoShift -- Automatic shift on long press
|
||||
* Copyright (C) 2021 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "kaleidoscope/plugin/AutoShift.h"
|
||||
|
||||
#include <Kaleidoscope-EEPROM-Settings.h>
|
||||
#include <Kaleidoscope-FocusSerial.h>
|
||||
|
||||
#include "kaleidoscope/Runtime.h"
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
// =============================================================================
|
||||
// AutoShift configurator
|
||||
|
||||
uint16_t AutoShiftConfig::settings_base_;
|
||||
|
||||
EventHandlerResult AutoShiftConfig::onSetup() {
|
||||
settings_base_ = ::EEPROMSettings.requestSlice(sizeof(AutoShift::settings_));
|
||||
uint32_t checker;
|
||||
|
||||
Runtime.storage().get(settings_base_, checker);
|
||||
|
||||
// Check if we have an empty eeprom...
|
||||
if (checker == 0xffffffff) {
|
||||
// ...if the eeprom was empty, store the default settings.
|
||||
Runtime.storage().put(settings_base_, AutoShift::settings_);
|
||||
Runtime.storage().commit();
|
||||
}
|
||||
|
||||
Runtime.storage().get(settings_base_, AutoShift::settings_);
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
EventHandlerResult AutoShiftConfig::onFocusEvent(const char *command) {
|
||||
enum {
|
||||
ENABLED,
|
||||
TIMEOUT,
|
||||
CATEGORIES,
|
||||
} subCommand;
|
||||
|
||||
if (::Focus.handleHelp(command, PSTR("autoshift.enabled\n"
|
||||
"autoshift.timeout\n"
|
||||
"autoshift.categories")))
|
||||
return EventHandlerResult::OK;
|
||||
|
||||
if (strncmp_P(command, PSTR("autoshift."), 10) != 0)
|
||||
return EventHandlerResult::OK;
|
||||
if (strcmp_P(command + 10, PSTR("enabled")) == 0)
|
||||
subCommand = ENABLED;
|
||||
else if (strcmp_P(command + 10, PSTR("timeout")) == 0)
|
||||
subCommand = TIMEOUT;
|
||||
else if (strcmp_P(command + 10, PSTR("categories")) == 0)
|
||||
subCommand = CATEGORIES;
|
||||
else
|
||||
return EventHandlerResult::OK;
|
||||
|
||||
switch (subCommand) {
|
||||
case ENABLED:
|
||||
if (::Focus.isEOL()) {
|
||||
::Focus.send(::AutoShift.enabled());
|
||||
} else {
|
||||
uint8_t v;
|
||||
::Focus.read(v);
|
||||
// if (::Focus.readUint8() != 0) {
|
||||
if (v != 0) {
|
||||
::AutoShift.enable();
|
||||
} else {
|
||||
::AutoShift.disable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TIMEOUT:
|
||||
if (::Focus.isEOL()) {
|
||||
::Focus.send(::AutoShift.timeout());
|
||||
} else {
|
||||
uint16_t t;
|
||||
::Focus.read(t);
|
||||
// auto t = ::Focus.readUint16();
|
||||
::AutoShift.setTimeout(t);
|
||||
}
|
||||
break;
|
||||
|
||||
case CATEGORIES:
|
||||
if (::Focus.isEOL()) {
|
||||
::Focus.send(uint8_t(::AutoShift.enabledCategories()));
|
||||
} else {
|
||||
uint8_t v;
|
||||
::Focus.read(v);
|
||||
auto categories = AutoShift::Categories(v);
|
||||
// auto categories = AutoShift::Categories(::Focus.readUint8());
|
||||
::AutoShift.setEnabled(categories);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Runtime.storage().put(settings_base_, AutoShift::settings_);
|
||||
Runtime.storage().commit();
|
||||
return EventHandlerResult::EVENT_CONSUMED;
|
||||
}
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace kaleidoscope
|
||||
|
||||
kaleidoscope::plugin::AutoShiftConfig AutoShiftConfig;
|
@ -0,0 +1,69 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
|
||||
#include <Kaleidoscope-Syster.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
SYSTER, ___, ___, ___, ___, ___, ___,
|
||||
Key_A, Key_B, Key_C, ___, ___, ___, ___,
|
||||
Key_0, Key_1, ___, ___, ___, ___,
|
||||
Key_Spacebar, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___,
|
||||
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
void systerAction(kaleidoscope::plugin::Syster::action_t action, const char *symbol) {
|
||||
switch (action) {
|
||||
case kaleidoscope::plugin::Syster::StartAction:
|
||||
break;
|
||||
case kaleidoscope::plugin::Syster::EndAction:
|
||||
break;
|
||||
case kaleidoscope::plugin::Syster::SymbolAction:
|
||||
if (strcmp(symbol, "abc") == 0) {
|
||||
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | IS_PRESSED, Key_X));
|
||||
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | WAS_PRESSED, Key_X));
|
||||
}
|
||||
if (strcmp(symbol, "a0") == 0) {
|
||||
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | IS_PRESSED, Key_Y));
|
||||
Kaleidoscope.handleKeyEvent(KeyEvent(KeyAddr::none(), INJECTED | WAS_PRESSED, Key_Y));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(Syster);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH SYSTER 0 0
|
||||
KEYSWITCH A 1 0
|
||||
KEYSWITCH B 1 1
|
||||
KEYSWITCH C 1 2
|
||||
KEYSWITCH ZERO 2 0
|
||||
KEYSWITCH ONE 2 1
|
||||
KEYSWITCH SPACE 3 0
|
||||
|
||||
# ==============================================================================
|
||||
NAME Syster sequence without zero
|
||||
|
||||
RUN 4 ms
|
||||
PRESS SYSTER
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SYSTER
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_A # The report should contain only `A`
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS B
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_B # The report should contain only `B`
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE B
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS C
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_C # The report should contain only `C`
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE C
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
EXPECT keyboard-report Key_X # The report should contain `X`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SPACE
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 10 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME Syster sequence with zero
|
||||
|
||||
RUN 4 ms
|
||||
PRESS SYSTER
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SYSTER
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_A # The report should contain only `A`
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS ZERO
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_0 # The report should contain only `0`
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE ZERO
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
EXPECT keyboard-report Key_Backspace # The report should contain `backspace`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
EXPECT keyboard-report Key_Y # The report should contain `Y`
|
||||
EXPECT keyboard-report empty # The report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SPACE
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 10 ms
|
@ -0,0 +1,147 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Kaleidoscope-Leader -- VIM-style leader keys
|
||||
* Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-Leader.h>
|
||||
#include <Kaleidoscope-Macros.h>
|
||||
|
||||
#include <Kaleidoscope-Ranges.h>
|
||||
#include "kaleidoscope/KeyEventTracker.h"
|
||||
#include "kaleidoscope/LiveKeys.h"
|
||||
#include "kaleidoscope/plugin.h"
|
||||
|
||||
#include <Kaleidoscope-Devel-ArduinoTrace.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(LEAD(0), Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey,
|
||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
||||
|
||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
||||
LEAD(0),
|
||||
|
||||
Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip,
|
||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
||||
Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
|
||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
||||
|
||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
||||
LEAD(0)),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace plugin {
|
||||
|
||||
class LeaderPrefix : public Plugin {
|
||||
public:
|
||||
EventHandlerResult onKeyswitchEvent(KeyEvent &event) {
|
||||
// DUMP(int(event.id()));
|
||||
// DUMP(int(event.key.getRaw()));
|
||||
if (event_tracker_.shouldIgnore(event))
|
||||
return EventHandlerResult::OK;
|
||||
|
||||
if (!active_) {
|
||||
if (keyToggledOn(event.state) && isLeaderKey(event.key)) {
|
||||
active_ = true;
|
||||
leader_arg_ = 0;
|
||||
}
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
if (keyToggledOn(event.state)) {
|
||||
for (uint8_t i{0}; i < 10; ++i) {
|
||||
if (digit_keys_[i] == event.key) {
|
||||
leader_arg_ *= 10;
|
||||
leader_arg_ += i;
|
||||
live_keys.mask(event.addr);
|
||||
return EventHandlerResult::ABORT;
|
||||
}
|
||||
}
|
||||
// DUMP(active_);
|
||||
active_ = false;
|
||||
}
|
||||
if (keyToggledOff(event.state) && event.key == Key_Masked)
|
||||
return EventHandlerResult::ABORT;
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
EventHandlerResult afterEachCycle() {
|
||||
// int timestamp = Runtime.millisAtCycleStart();
|
||||
// DUMP(timestamp);
|
||||
return EventHandlerResult::OK;
|
||||
}
|
||||
|
||||
uint16_t arg() const {
|
||||
return leader_arg_;
|
||||
}
|
||||
|
||||
private:
|
||||
Key digit_keys_[10] = {
|
||||
Key_P, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Y, Key_U, Key_I, Key_O,
|
||||
};
|
||||
|
||||
KeyEventTracker event_tracker_;
|
||||
bool active_{false};
|
||||
uint16_t leader_arg_{0};
|
||||
bool isLeaderKey(Key key) {
|
||||
return (key >= ranges::LEAD_FIRST && key <= ranges::LEAD_LAST);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace plugin
|
||||
} // namespace kaleidoscope
|
||||
|
||||
kaleidoscope::plugin::LeaderPrefix LeaderPrefix;
|
||||
|
||||
auto &serial_port = Kaleidoscope.serialPort();
|
||||
|
||||
static void leaderTestX(uint8_t seq_index) {
|
||||
Macros.tap(Key_A);
|
||||
}
|
||||
|
||||
static void leaderTestXX(uint8_t seq_index) {
|
||||
Macros.tap(Key_B);
|
||||
}
|
||||
|
||||
void leaderTestPrefix(uint8_t seq_index) {
|
||||
uint8_t prefix_arg = LeaderPrefix.arg();
|
||||
// DUMP(prefix_arg);
|
||||
Macros.tap(Key_Y);
|
||||
while (prefix_arg-- > 0)
|
||||
Macros.tap(Key_X);
|
||||
}
|
||||
|
||||
static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM =
|
||||
LEADER_DICT({LEADER_SEQ(LEAD(0), Key_X), leaderTestX},
|
||||
{LEADER_SEQ(LEAD(0), Key_X, Key_X), leaderTestXX},
|
||||
{LEADER_SEQ(LEAD(0), Key_Z), leaderTestPrefix});
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(LeaderPrefix, Leader);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
|
||||
Leader.dictionary = leader_dictionary;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:avr:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH LEAD 0 0
|
||||
KEYSWITCH Q 1 1
|
||||
KEYSWITCH W 1 2
|
||||
KEYSWITCH E 1 3
|
||||
KEYSWITCH R 1 4
|
||||
KEYSWITCH T 1 5
|
||||
KEYSWITCH Z 3 1
|
||||
|
||||
# ==============================================================================
|
||||
NAME Leader prefix sequence
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LEAD
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE LEAD
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS Q
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE Q
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS W
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE W
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS Z
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Y
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_X
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE Z
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS W
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_W
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE W
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
@ -0,0 +1,86 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-Qukeys.h>
|
||||
#include <Kaleidoscope-OneShot.h>
|
||||
|
||||
#include "./common.h"
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
OSL(1), Key_1, Key_2, Key_3, Key_4, Key_5, XXX,
|
||||
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab,
|
||||
Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G,
|
||||
Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
|
||||
|
||||
Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift,
|
||||
Key_Q,
|
||||
|
||||
XXX, OSM(LeftGui), LSHIFT(Key_LeftShift), LSHIFT(Key_RightShift), Key_9, Key_0, XXX,
|
||||
Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals,
|
||||
Key_H, SFT_T(J), CTL_T(K), ALT_T(L), GUI_T(Semicolon), Key_Quote,
|
||||
Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
|
||||
|
||||
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
|
||||
LT(1,E)
|
||||
),
|
||||
[1] = KEYMAP_STACKED
|
||||
(
|
||||
___, SFT_T(A), Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
|
||||
Key_1, Key_2, Key_3, Key_4,
|
||||
___,
|
||||
|
||||
|
||||
___, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F,
|
||||
Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G,
|
||||
|
||||
Key_1, Key_2, Key_3, Key_4,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(Qukeys, OneShot);
|
||||
|
||||
void setup() {
|
||||
QUKEYS(
|
||||
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 1), Key_LeftGui), // A/cmd
|
||||
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 2), Key_LeftAlt), // S/alt
|
||||
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 3), Key_LeftControl), // D/ctrl
|
||||
kaleidoscope::plugin::Qukey(0, KeyAddr(2, 4), Key_LeftShift), // F/shift
|
||||
kaleidoscope::plugin::Qukey(0, KeyAddr(3, 6), ShiftToLayer(1)) // Q/layer-shift (on `fn`)
|
||||
)
|
||||
Qukeys.setHoldTimeout(kaleidoscope::testing::QUKEYS_HOLD_TIMEOUT);
|
||||
Qukeys.setOverlapThreshold(kaleidoscope::testing::QUKEYS_OVERLAP_THRESHOLD);
|
||||
Qukeys.setMinimumHoldTime(kaleidoscope::testing::QUKEYS_MINIMUM_HOLD_TIME);
|
||||
Qukeys.setMinimumPriorInterval(kaleidoscope::testing::QUKEYS_MIN_PRIOR_INTERVAL);
|
||||
Qukeys.setMaxIntervalForTapRepeat(kaleidoscope::testing::QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT);
|
||||
|
||||
Kaleidoscope.setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
// -*- mode: c++ -*-
|
||||
|
||||
/* Kaleidoscope - Firmware for computer input devices
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace kaleidoscope {
|
||||
namespace testing {
|
||||
|
||||
constexpr uint16_t QUKEYS_HOLD_TIMEOUT = 200;
|
||||
constexpr uint8_t QUKEYS_OVERLAP_THRESHOLD = 0;
|
||||
constexpr uint8_t QUKEYS_MINIMUM_HOLD_TIME = 0;
|
||||
constexpr uint8_t QUKEYS_MIN_PRIOR_INTERVAL = 0;
|
||||
constexpr uint8_t QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT = 0;
|
||||
|
||||
} // namespace testing
|
||||
} // namespace kaleidoscope
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH OSL 0 0 # 0: OSL(1)
|
||||
KEYSWITCH QK 0 1 # 1: SFT_T(A)
|
||||
KEYSWITCH C 0 2 # 1: Key_C
|
||||
KEYSWITCH A 2 1 # 0: Key_A, Qukey(Key_LeftGui)
|
||||
KEYSWITCH H 2 10 # 0: Key_H
|
||||
KEYSWITCH Y 1 10 # 0: Key_Y
|
||||
KEYSWITCH K 2 12 # 0: CTL_T(K)
|
||||
|
||||
KEYSWITCH OSG 0 10 # 0: OSM(LeftGui)
|
||||
KEYSWITCH LS 0 11 # 0: LSHIFT(Key_LeftShift)
|
||||
KEYSWITCH RS 0 12 # 0: LSHIFT(Key_RightShift)
|
||||
|
||||
# ==============================================================================
|
||||
NAME Chrysalis 566 and 605
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OSL # OSL(1)
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OSL
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS QK # SFT_T(A)
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS C # 1: Key_C
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE C
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift
|
||||
EXPECT keyboard-report Key_LeftShift Key_C
|
||||
EXPECT keyboard-report Key_LeftShift
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE QK
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME Chrysalis 688
|
||||
|
||||
# plain key press
|
||||
RUN 4 ms
|
||||
PRESS H # Key_H
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_H
|
||||
|
||||
# qukey press
|
||||
RUN 4 ms
|
||||
PRESS K # CTL_T(K)
|
||||
RUN 1 cycle
|
||||
|
||||
# plain key release
|
||||
RUN 9 ms
|
||||
RELEASE H
|
||||
RUN 1 cycle
|
||||
# There should be no report here
|
||||
|
||||
# plain key press
|
||||
RUN 4 ms
|
||||
PRESS Y # Key_Y
|
||||
RUN 1 cycle
|
||||
|
||||
# plain key release
|
||||
RUN 4 ms
|
||||
RELEASE Y
|
||||
RUN 1 cycle
|
||||
# This event resolves the qukey's state and flushes the queue
|
||||
EXPECT keyboard-report Key_H Key_LeftControl
|
||||
EXPECT keyboard-report Key_LeftControl
|
||||
EXPECT keyboard-report Key_LeftControl Key_Y
|
||||
EXPECT keyboard-report Key_LeftControl
|
||||
|
||||
# qukey release
|
||||
RUN 4 ms
|
||||
RELEASE K
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME Chrysalis 427 workaround
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OSG # OSM(LeftGui)
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftGui
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OSG
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LS # LSHIFT(Key_LeftShift)
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftGui Key_LeftShift
|
||||
|
||||
RUN 4 ms
|
||||
PRESS H
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftGui Key_LeftShift Key_H
|
||||
EXPECT keyboard-report Key_LeftShift Key_H
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE H
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE LS
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
@ -0,0 +1,78 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-Macros.h>
|
||||
#include <Kaleidoscope-OneShot.h>
|
||||
#include <Kaleidoscope-TapDance.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
Key_Spacebar, Key_A, ___, ___, ___, ___, ___,
|
||||
TD(0), OSM(LeftShift), ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___,
|
||||
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
void tapDanceAction(uint8_t tap_dance_index,
|
||||
KeyAddr key_addr,
|
||||
uint8_t tap_count,
|
||||
kaleidoscope::plugin::TapDance::ActionType tap_dance_action) {
|
||||
switch (tap_dance_index) {
|
||||
case 0:
|
||||
return tapDanceActionKeys(tap_count, tap_dance_action,
|
||||
Key_Period, M(0), LSHIFT(Key_1));
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
|
||||
if (keyToggledOn(event.state)) {
|
||||
switch (macro_id) {
|
||||
case 0:
|
||||
Macros.type(PSTR("abc"));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return MACRO_NONE;
|
||||
}
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(Macros, OneShot, TapDance);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
TapDance.time_out = 25;
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,177 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH SPACE 0 0
|
||||
KEYSWITCH A 0 1
|
||||
KEYSWITCH TD_0 1 0
|
||||
KEYSWITCH OS_SHIFT 1 1
|
||||
|
||||
# ==============================================================================
|
||||
NAME Back and forth
|
||||
|
||||
RUN 4 ms
|
||||
PRESS TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OS_SHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Period # report: { 37 }
|
||||
EXPECT keyboard-report empty
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OS_SHIFT
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Next, we press `space`, triggering both the resolution of the TapDance key and
|
||||
# the release of the OneShot key, in that order.
|
||||
RUN 4 ms
|
||||
PRESS SPACE
|
||||
RUN 1 cycle
|
||||
# First, the TapDance key is resolved, adding `.` to the report. This event also
|
||||
# triggers the release of the OneShot key, which shouldn't happen until after
|
||||
# the `.` press is processed.
|
||||
EXPECT keyboard-report Key_LeftShift Key_Period # report: { 37 e1 }
|
||||
# Now the OneShot key is released, removing `shift` from the report.
|
||||
EXPECT keyboard-report Key_Period # report: { 37 }
|
||||
# The TapDance `.` key has been released, so its release comes next.
|
||||
EXPECT keyboard-report empty # report: { }
|
||||
# Finally, we get the report for the press of the `space` key.
|
||||
EXPECT keyboard-report Key_Spacebar # report: { 2c }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OS_SHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OS_SHIFT
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
|
||||
EXPECT keyboard-report Key_A # report: { 4 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME Single rollover
|
||||
|
||||
RUN 4 ms
|
||||
PRESS TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Period # report: { 37 }
|
||||
EXPECT keyboard-report Key_Period Key_Spacebar # report: { 37 2c }
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OS_SHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Period Key_Spacebar Key_LeftShift # report: { 2c 37 e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE TD_0
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_Spacebar Key_LeftShift # report: { 2c e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OS_SHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_A # report: { 4 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME OSM applies to whole Macro
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OS_SHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OS_SHIFT
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE TD_0
|
||||
RUN 1 cycle
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Next, we press `space`, triggering both the resolution of the TapDance key and
|
||||
# the release of the OneShot key, in that order.
|
||||
RUN 4 ms
|
||||
PRESS SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
EXPECT keyboard-report Key_LeftShift Key_B # report: { 5 e1 }
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
EXPECT keyboard-report Key_LeftShift Key_C # report: { 6 e1 }
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
EXPECT keyboard-report empty # report: { }
|
||||
EXPECT keyboard-report Key_Spacebar # report: { 2c }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE SPACE
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
|
||||
|
||||
RUN 5 ms
|
@ -0,0 +1,50 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-OneShot.h>
|
||||
#include <Kaleidoscope-Escape-OneShot.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
OSM(LeftShift), ___, ___, ___, ___, ___, ___,
|
||||
Key_A, Key_B, ___, ___, ___, ___, ___,
|
||||
Key_Escape, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___,
|
||||
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(OneShot, EscapeOneShot);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH OSM 0 0
|
||||
KEYSWITCH A 1 0
|
||||
KEYSWITCH B 1 1
|
||||
KEYSWITCH ESC 2 0
|
||||
|
||||
# ==============================================================================
|
||||
NAME Escape OneShot modifier
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OSM
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OSM
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS ESC
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE ESC
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME Escape sticky OneShot modifier
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OSM
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OSM
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
PRESS OSM
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE OSM
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 5000 ms
|
||||
|
||||
RUN 4 ms
|
||||
PRESS ESC
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE ESC
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 5 ms
|
@ -0,0 +1,50 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-AutoShift.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
Key_LeftShift, Key_RightShift, ___, ___, ___, ___, ___,
|
||||
Key_A, Key_B, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___,
|
||||
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(AutoShift);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
AutoShift.setTimeout(20);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH LSHIFT 0 0
|
||||
KEYSWITCH RSHIFT 0 1
|
||||
KEYSWITCH A 1 0
|
||||
KEYSWITCH B 1 1
|
||||
|
||||
# ==============================================================================
|
||||
NAME AutoShift tap
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_A # report: { 4 }
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME AutoShift long press
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
|
||||
# Timeout is 20ms
|
||||
RUN 20 ms
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 }
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
EXPECT keyboard-report empty
|
||||
|
||||
RUN 5 ms
|
@ -0,0 +1,68 @@
|
||||
/* -*- mode: c++ -*-
|
||||
* Copyright (C) 2021 Keyboard.io, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License as published by the Free Software
|
||||
* Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Kaleidoscope.h>
|
||||
#include <Kaleidoscope-SpaceCadet.h>
|
||||
|
||||
// *INDENT-OFF*
|
||||
KEYMAPS(
|
||||
[0] = KEYMAP_STACKED
|
||||
(
|
||||
Key_LeftShift, Key_RightShift, ___, ___, ___, ___, ___,
|
||||
Key_A, Key_B, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___,
|
||||
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___, ___, ___, ___,
|
||||
___, ___, ___, ___,
|
||||
___
|
||||
),
|
||||
)
|
||||
// *INDENT-ON*
|
||||
|
||||
KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet);
|
||||
|
||||
void setup() {
|
||||
Kaleidoscope.setup();
|
||||
|
||||
//Set the SpaceCadet map
|
||||
//Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS}
|
||||
//Note: must end with the SPACECADET_MAP_END delimiter
|
||||
static kaleidoscope::plugin::SpaceCadet::KeyBinding spacecadetmap[] = {
|
||||
{Key_LeftShift, Key_X, 10},
|
||||
{Key_RightShift, Key_Y, 0},
|
||||
{Key_LeftGui, Key_LeftCurlyBracket, 10},
|
||||
{Key_RightAlt, Key_RightCurlyBracket, 10},
|
||||
{Key_LeftAlt, Key_RightCurlyBracket, 10},
|
||||
{Key_LeftControl, Key_LeftBracket, 10},
|
||||
{Key_RightControl, Key_RightBracket, 10},
|
||||
SPACECADET_MAP_END
|
||||
};
|
||||
//Set the map.
|
||||
SpaceCadet.map = spacecadetmap;
|
||||
SpaceCadet.time_out = 20;
|
||||
|
||||
SpaceCadet.enableWithoutDelay();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
Kaleidoscope.loop();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"cpu": {
|
||||
"fqbn": "keyboardio:virtual:model01",
|
||||
"port": ""
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
VERSION 1
|
||||
|
||||
KEYSWITCH LSHIFT 0 0
|
||||
KEYSWITCH RSHIFT 0 1
|
||||
KEYSWITCH A 1 0
|
||||
KEYSWITCH B 1 1
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet tap
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
EXPECT keyboard-report Key_X # Report should contain `X` (0x1B)
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet hold
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
|
||||
RUN 10 ms # timeout = 10 ms (for this key)
|
||||
RELEASE LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet hold with global timeout
|
||||
|
||||
RUN 4 ms
|
||||
PRESS RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5)
|
||||
|
||||
RUN 20 ms # timeout = 20 ms
|
||||
RELEASE RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet interrupt
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
|
||||
RUN 4 ms
|
||||
PRESS A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_A # Report should add `A` (0x04, 0xE1)
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_A # Report should contain only `A` (0x04)
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE A
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet interrupt SpaceCadet with tap
|
||||
|
||||
RUN 4 ms
|
||||
PRESS LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
|
||||
RUN 4 ms
|
||||
PRESS RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_RightShift # Report should add `shift` (0xE5)
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
EXPECT keyboard-report Key_LeftShift Key_Y # Report should add `Y` (0x1C)
|
||||
EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1)
|
||||
|
||||
RUN 4 ms
|
||||
RELEASE LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
||||
|
||||
# ==============================================================================
|
||||
NAME SpaceCadet interrupt SpaceCadet with hold
|
||||
|
||||
# First, press left shift. It takes effect immediately, because SpaceCadet is in
|
||||
# "no-delay" mode.
|
||||
RUN 4 ms
|
||||
PRESS LSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift # report: { e1 }
|
||||
|
||||
# Before left shift times out (timeout=10ms), press right shift, which also
|
||||
# takes effect without delay.
|
||||
RUN 4 ms
|
||||
PRESS RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report Key_LeftShift Key_RightShift # report: { e1 e5 }
|
||||
|
||||
# Next, release left shift after it times out (so it's not a "tap"), but before
|
||||
# the right shift times out. This does not generate a report, because the right
|
||||
# shift might still become a "tap" if it's released soon enough.
|
||||
RUN 10 ms
|
||||
RELEASE LSHIFT
|
||||
|
||||
# Next, the right shift times out, resolving to its modifier state. This allows
|
||||
# the left shift to be released, because the right shift can't be a "tap".
|
||||
RUN 10 ms
|
||||
EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5)
|
||||
|
||||
# Last, release the right shift as normal.
|
||||
RUN 4 ms
|
||||
RELEASE RSHIFT
|
||||
RUN 1 cycle
|
||||
EXPECT keyboard-report empty # Report should be empty
|
||||
|
||||
RUN 5 ms
|
Loading…
Reference in new issue