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