Merge pull request #1036 from gedankenexperimenter/plugin/autoshift
Add AutoShift pluginpull/1045/head
commit
72d4ac8124
@ -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,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,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
|
Loading…
Reference in new issue